Intro to Pandas and Matplotlib

UBDS 2026: Basic Python

Day 4 : Pandas & Matplotlib

Topics covered

  1. Learn to handle large datasets using Pandas. / Вивчимо як працювати з великими датасетами використовуючи пандас
  2. Perform basic numerical analyses with NumPy. / Проведемо базовий стат аналіз викорстовуючи numpy
  3. Visualise data using Matplotlib. / Візуалізуємо дані використовуючи matplotlib
  4. Explore real-world biological data from the AnAge database. / Дослідимо реальні біологічна данні використовуючи AnAge датасет

Resources

Pandas documentation: https://pandas.pydata.org/docs/user_guide/index.html

Graph examples: https://python-graph-gallery.com

We will use the AnAge dataset (‘anage_data.tsv’); a curated database of ageing and life history in animals, including extensive longevity records / Ми будемо використовувати датасет AnAge, що є очищеною базою даних про старіння і довголіття у тварин.

Step 0: Import the tools we need

  • pandas helps us work with tables of data, given the alias; pd / допомогає нам працювати з таблицями, коротка назва pd
  • numpy is needed to work with numerical data, given the alias; np / потрібенн для роботи з числовими данними, коротка назва np
  • matplotlib helps us make plots, given the alias; plt / потрібен для роботи з графіками, коротка назва plt
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

Step 1: Load the Dataset

  • Make sure the file is in the same folder as your notebook / Перевірте що файл знаходиться в потрібній папці
# Read in the dataframe - use separator=tab as we have a tab separated file / Читаємо файл вказуючи що розділовий знак це Tab
df = pd.read_csv("anage_data.tsv", sep='\t') 

# Filter the dataframe columns to the ones we are interested in / Фільтруємо потрібні колонки
df = df[['Class', 'Order', 'Family', 'Genus', 'Species', 'Common name', 'Female maturity (days)', 'Male maturity (days)', 'Gestation/Incubation (days)', 'Weaning (days)', 'Litter/Clutch size', 'Litters/Clutches per year', 'Inter-litter/Interbirth interval', 'Birth weight (g)', 'Weaning weight (g)', 'Adult weight (g)', 'Growth rate (1/days)', 'Maximum longevity (yrs)', 'Source', 'Specimen origin', 'Sample size', 'Data quality', 'IMR (per yr)', 'MRDT (yrs)', 'Metabolic rate (W)', 'Body mass (g)', 'Temperature (K)']]
df.head(5)  # Show first 5 rows

EXERCISE 1

  1. What happens if you forget to specify sep=‘ when reading a tab delimited dataset? / Перевірте що буде якщо не вкажете sep=’
  2. As well as the head() method there is a tail() method. What do you think it does? / Що робить метод tail?
  3. Both methods accept a single numeric parameter. What do you think it does? / Обидва методи приймають параметр, що він робить?
# Your code here

Step 2: Explore the Dataset

  • Get basic information about the dataset / Дослідіть базову інформацію про датасет
# How many rows?
print("Dataset length: ", len(df))

# How many rows and columns?
print("Dataset shape:", df.shape)

# How many cells in the table?
print("Dataset size: ", df.size)

# Column names
print("Columns:", df.columns.tolist())

# What are the data types of the columns?
print("Column data types: ", df.dtypes)

# Quick statistics of numeric columns
df.describe()

# Check how many Species are available in the dataset 
Species = df['Common name'].to_list()
print("Number of species: ", len(set(Species)))

# Check how many Orders are available in the dataset 
Order = df['Order'].to_list()
print("Number of orders: ", len(set(Order)))

# Longest lived species 
print(df.loc[df['Maximum longevity (yrs)'].idxmax()])

EXERCISE 2

  1. Name the species with the largest body mass. / Назвіть вид з найбільшою масою тіла
  2. What is the largest litter size? / А тепер з найбільшою кількістю потомства
  3. What is the shortest lived species? / А тепер найбільш короткоживучий вид
# Your code here

Step 3: Accessing the data

  • Selecting columns and rows / Вибираємо колонки та строчки
# Select one column
lifespan = df['Maximum longevity (yrs)']
print(lifespan.head())

# Select multiple columns
subset = df[['Common name', 'Maximum longevity (yrs)', 'Body mass (g)']]
subset.head()

# Select rows by index
print(df.iloc[0:5])

# Select rows by condition (species with max longevity > 100)
long_lived = df[df['Maximum longevity (yrs)'] > 100]
long_lived[['Common name', 'Maximum longevity (yrs)']]

EXERCISE 3: What happens if you:

  1. List the columns you want out of order from the way they appear in the file? / Спробуйте отримати доступ до колонок перелічивши їх в іншому порядку.
  2. Put the same column name in twice? / Якщо ви вкажете ім’я колонки двічі
  3. Put in a non-existing column name? (a.k.a Typo) / Якщо ви вкажете неіснуючу колонку
# Your code here

Step 4: Filtering data

  • Filter the rows of the dataframe
  • Print the values to get the answer for each filter
# Filter by the index of each row - the range is from ro 1 to row 5
df_filtered = df[1:5]

# Using a criteria to filter rows 
df_old = df[df['Maximum longevity (yrs)'] > 30]

# We can filter by more than one attribute
df_old_and_fat = df[(df['Maximum longevity (yrs)'] > 30) & (df['Body mass (g)'] > 30000)]

EXERCISE 4

  1. What happens if we ask for a single row instead of a range? / Що станеться якщо ви попросите одну колонку, а не ряд
  2. What species have a temperature greater than 300K and a metabolic rate greater than 200W? /Який вид має температуру більше 300К і метаболічну активність більше 200 Ват
# Your code here

Step 5: Handling missing data

  • Filter out rows with missing data in the specified columns
  • print the values to get the answers
# Check for missing values
missing = df.isna().sum()

# Drop rows with missing MaxLongevity or BodyWeight
df_clean = df.dropna(subset=['Maximum longevity (yrs)', 'Body mass (g)'])

print("Rows after cleaning:", df_clean.shape)

EXERCISE 5

  1. How many missing values are there in each column? Скільки відсутніх значень у кожній колонці
  2. How many rows remain after removing missing values? / Скільки лишається строчок після прибирання відсутніх значень
  3. What happens if you drop rows based only on one column instead of two? / Що буде якщо ви приберете відсутні значення лише в одній колонці замість двох
# Your code here

Step 6: Working with large amounts of data with groupby

  • When we want summary statistics for different groups in our dataframe
  • For example calculating the mean lifespan per taxonomic class

Tip:

  • GroupBy is very powerful in Pandas for summarizing data by categories.
# Group by Class and calculate mean MaxLongevity
mean_lifespan = df_clean.groupby('Class')['Maximum longevity (yrs)'].mean()
print(mean_lifespan)

EXERCISE 6: GroupBy

  1. Which class has the highest average lifespan? / У якого класу найдовщий час життя
  2. How many species are in each class? / Скільки видів в кожному класі
  3. What is the average body mass per class? / Якою є середня маса в кожному класі
# Your code here

Step 7: Simple Numerical Operations with NumPy

  • NumPy allows us to query our data with mathematical functions
  • Convert lifespan to a NumPy array for calculations
# Convert lifespan to a NumPy array for calculations
lifespan_array = df_clean['Maximum longevity (yrs)'].to_numpy()

print("Mean lifespan:", np.mean(lifespan_array))
print("Median lifespan:", np.median(lifespan_array))
print("Standard deviation:", np.std(lifespan_array))

EXERCISE 7: Numpy operations

  1. What is the mean lifespan of the dataset? / Якою є середня тривалість життя в усьому датасеті
  2. Is the median higher or lower than the mean? What does this suggest? / Чи є медіана вище чи нижче за середнє? Що це значить?
  3. What happens to the standard deviation if you remove extreme values? Use row filtering to remove the longest lived and shortest lived species. / Що стається зі стандартним відхиленням якщо ми приберемо екстримальні значення? Спробуйте пофільтрувати строчки щоб прибрати найдовші та найкоротші тривалості життя.
# Your code here

Step 8: Visualizing Data with Matplotlib

  • The histogram is a simple yet useful plot for understanding the distribution of values in a column.
# Histogram of maximum lifespans
plt.figure(figsize=(10,6))
plt.hist(df_clean['Maximum longevity (yrs)'], bins=30, color='skyblue', edgecolor='black')
plt.title("Distribution of Maximum Longevity Across Species")
plt.xlabel("Max Longevity (years)")
plt.ylabel("Number of Species")
plt.show()

EXERCISE 8: Histogram (Matplotlib)

  1. What does the shape of the distribution look like (e.g. skewed or normal)? / Як виглядає форма розподілу даних?
  2. What happens if you increase or decrease the number of bins? / Що стається якщо ви збільшуєте або зменшуєте кількість стовпців?
  3. Which lifespan range contains the most species? / Який діапазон тривалості життя включає в себе більшість видів?
# Your code here

Step 9: Scatter Plot

  • Scatter plots allow us to compare values from two columns against each other.
# Scatter Plot - Body Weight vs Max Longevity
plt.figure(figsize=(10,6))
plt.scatter(df_clean['Body mass (g)'], df_clean['Maximum longevity (yrs)'], alpha=0.6, color='green')
plt.title("Body Weight vs Max Longevity")
plt.xlabel("Body Weight (kg)")
plt.ylabel("Max Longevity (years)")
plt.show()

EXERCISE 9.1: Simple Scatter

Create a scatter plot for each of the following questions to find the answers. / Створіть діаграму розсіювання для кожного з наступних питань, щоб знайти відповіді. 1. Do larger animals tend to live longer? / Чи живуть більші тварини довше? 2. Are there any obvious outliers? / Чи є якість очевидні аутлаєри? 3. What happens if you change the transparency (alpha)? / Що стається якщо ви змінюєте прозорість точок?

# Your code here

Improving our figures

  • Adding annotations with colour and labelling certain datapoints makes our figures more informative
  • Visualising points on the log scale can help by separating our data and making it more understandable
# Step 8.2: Scatter Plot - colored by Order and label longest-lived species
plt.figure(figsize=(12,8))

# Get unique orders and assign colors <- Colour each point by the phylogenetic tree
orders = df_clean['Order'].unique()
colors = plt.cm.tab20(np.linspace(0, 1, len(orders)))
order_color_map = dict(zip(orders, colors))

# Plot each point with color based on Order
for idx, row in df_clean.iterrows():
    plt.scatter(row['Body mass (g)'], row['Maximum longevity (yrs)'], 
                color=order_color_map[row['Order']], alpha=0.6)

# Label the top 5 longest-lived species
top_longest = df_clean.nlargest(5, 'Maximum longevity (yrs)')
for idx, row in top_longest.iterrows():
    plt.text(row['Body mass (g)'], row['Maximum longevity (yrs)'], 
             row['Common name'], fontsize=9, ha='right', va='bottom')

# Plot the layers for the Matplotlib figure - Title & Axis 
plt.xscale('log')  # log scale for better visualization
plt.title("Body Weight vs Max Longevity")
plt.xlabel("Body Weight (g, log scale)")
plt.ylabel("Maximum Longevity (yrs)")

# Create a legend for Orders
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor=color, label=order) for order, color in order_color_map.items()]
plt.legend(handles=legend_elements, bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=8)

plt.show()

EXERCISE 9.2: Annotated scatter plot

  1. Which order contains the longest-lived species? / В якому порядку найбільш довгоживучі види?
  2. Why is a log scale useful for body weight? / Чому логарифмічна шкала для маси є зручною?
  3. What happens if you label more than 5 species? / Що буде якщо ви додасте підписи до більш ніж 5 видів?
  4. Can you identify any clusters of related species? / Чи ви бачите якісь кластери пов’язаних видів?
# Your code here

Step 10: Do heavier animals really live longer?

  • We can add a regression live to out plots to calculate the correlation between two variables.
correlation = df_clean['Body mass (g)'].corr(df_clean['Maximum longevity (yrs)'])
print(f"Correlation between body weight and longevity: {correlation:.2f}")

# Prepare x and y
x = df_clean['Body mass (g)']
y = df_clean['Maximum longevity (yrs)']

# Take log of x for fitting (because x-axis is log scale)
log_x = np.log10(x)

# Linear regression in log space: y = slope * log10(x) + intercept
slope, intercept = np.polyfit(log_x, y, 1)

# Create points for the regression line
x_fit = np.linspace(x.min(), x.max(), 100)
y_fit = slope * np.log10(x_fit) + intercept

# --- Plot ---
plt.figure(figsize=(12,8))

# Scatter points colored by Order
for idx, row in df_clean.iterrows():
    plt.scatter(row['Body mass (g)'], row['Maximum longevity (yrs)'], 
                color=order_color_map[row['Order']], alpha=0.6)

# Label the top 5 longest-lived species
top_longest = df_clean.nlargest(5, 'Maximum longevity (yrs)')
for idx, row in top_longest.iterrows():
    plt.text(row['Body mass (g)'], row['Maximum longevity (yrs)'], 
             row['Common name'], fontsize=9, ha='right', va='bottom')

# Plot the regression line
plt.plot(x_fit, y_fit, color='red', linewidth=2, label=f'Linear fit (log x)')

plt.xscale('log')  # log scale for better visualization
plt.title("Body Weight vs Max Longevity (colored by Order) with correlation line")
plt.xlabel("Body Weight (g, log scale)")
plt.ylabel("Maximum Longevity (yrs)")

# Legend for both orders and regression line
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor=color, label=order) for order, color in order_color_map.items()]
plt.legend(handles=legend_elements + [plt.Line2D([0], [0], color='red', lw=2, label='Linear fit (log x)')],
           bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=8)

plt.show()

Step 11: What is the distribution of birth weights for animals which live longer than 5 years?

  • Group the distribution by taxonomic class.
# Filter for species with longevity > 5 years
long_lived = df_clean[df_clean['Maximum longevity (yrs)'] > 5]

# Prepare data for each Class
classes = long_lived['Class'].unique()
data = []

filtered_classes = []
for cls in classes:
    birth_weights = long_lived[long_lived['Class'] == cls]['Birth weight (g)'].dropna()
    if len(birth_weights) > 0:  # Only keep non-empty arrays
        data.append(birth_weights)
        filtered_classes.append(cls)

# Create the violin plot
plt.figure(figsize=(12,6))
plt.violinplot(data, showmeans=True, showmedians=True)

# Set x-ticks to class names
plt.xticks(ticks=np.arange(1, len(filtered_classes)+1), labels=filtered_classes, rotation=45)
plt.yscale('log')
plt.ylabel("Birth weight (g)")
plt.title("Distribution of Birth Weight Across Classes (Species > 10 yrs longevity)")
plt.tight_layout()
plt.show()

Step 11: Challenge for Students

For each of the following visualise the answer and summarise the results in 2-3 sentences.

  • Which Class of animals has the highest average longevity? / В якому класі тварини найбілш довгоживучі?
  • Does litter size correlate with longevity? / Чи пов’язана кількість потомства з тривалістю життя?
  • Which Order of animals has the highest average longevity? / В якому порядку тварин найбільша середня тривалість життя?
  • Create a scatter plot of Body Mass vs Maximum Longevity only for mammals. What is the correlation? / Побудуйте графік розкиду масси тіла проти максимальної тривалості життя тільки дял ссавців. Якою є кореляція?
  • Identify species with unexpected lifespans (e.g., small animals that live unusually long). / Визначте види з незвичайною тривалістю життя
  • Compare birds and mammals in terms of average lifespan using a bar chart. / Порівняйте птахів та ссавців за їх середньою тривалістю життя використовуючи стовпчастий графік
  • Does longevity always correlate with Female (or Male) maturity (days)? Identify species who grow up fast despite being long lived. / Чи тривалість життя корелює з дозріванням для самців чи самок? Визначте види які дорослішають швидше.