We'll use the Palmer Penguins dataset - real measurements of 344 penguins from three species, collected at the Palmer Station research base in Antarctica. It's deliberately friendly to start on, but every move you make here is the same move you'll make on a gene expression table: load, inspect, clean, group, summarise, plot. Learn the pattern on penguins, apply it to biology.
Why not jump straight to gene data?
Because the skill you're building - wrangling a real table and seeing structure in it - is identical, and penguins won't drown you in biological jargon while you're still learning the verbs. Once this clicks, swapping in an expression matrix is a small step.
1Load the data and look at it
library(tidyverse) library(palmerpenguins) penguins # the dataset comes with the package glimpse(penguins) # columns, types, and a peek at values
Always look before you leap. glimpse() shows you the columns (species, island, bill length, flipper length, body mass, sexโฆ) and their types. Notice some values are NA - missing. Real data is always a little messy.
2Clean it
clean <- penguins |> drop_na(bill_length_mm, bill_depth_mm, species) # remove rows missing these nrow(penguins) # before nrow(clean) # after
Decode the jargon: NA
NA means "not available" - a missing value. Many calculations refuse to run with NAs present (on purpose, so you don't get silently wrong answers). Deciding what to do with them is a real and important judgment call in every analysis.
3Summarise by group
This is the question-answering step. How do the species differ?
clean |>
group_by(species) |>
summarise(
n = n(),
avg_bill_length = mean(bill_length_mm),
avg_body_mass = mean(body_mass_g, na.rm = TRUE)
)
In one short block you've just computed, per species, how many penguins there are and their average measurements. That group_by() |> summarise() combination is one of the most useful patterns in all of data analysis.
4Visualize the structure
ggplot(clean, aes(x = bill_length_mm, y = bill_depth_mm, color = species)) + geom_point(size = 2, alpha = 0.8) + labs( title = "Bill shape separates penguin species", x = "Bill length (mm)", y = "Bill depth (mm)" ) + theme_minimal()
Look at the result: the three species form distinct clusters. You've just discovered something real from data - that bill shape alone largely tells the species apart. That move, finding group structure in measurements, is exactly what clustering gene expression does later.
5One more: a boxplot
ggplot(clean, aes(x = species, y = body_mass_g, fill = species)) + geom_boxplot() + labs(title = "Body mass by species", y = "Body mass (g)") + theme_minimal()
๐ Make it your own
- Use
filter()to look at just one island, then re-make the scatter plot. Do the clusters change? - Add
facet_wrap(~ island)to the scatter plot - what does splitting by island reveal? - Group by species and sex together and compare average body mass. Which is the bigger driver?
- Bridge to biology: download any gene-length or expression CSV from Resources,
read_csv()it, and run the same load โ clean โ group โ plot pattern. The skill transfers directly. - Portfolio move: knit your script into an HTML report (RStudio โ Knit) and put it on GitHub.
What you just did
You took a real, imperfect dataset and walked the full loop: load โ inspect โ clean โ summarise โ visualize. You handled missing data, computed group summaries, and made two figures that reveal genuine structure. That is data analysis - and it's the identical workflow behind a published RNA-seq figure, just with genes in place of penguins.
Why na.rm = TRUE inside mean()?
It tells mean() to ignore missing values when averaging. Without it, a single NA in the column makes the whole result NA - R's way of refusing to guess.
