Project: Explore a Real Dataset with R

Take a real, published dataset from messy to insight: load it, clean it, summarise it by group, and produce a polished figure - the exact workflow you'll later run on gene expression data.

๐ŸŸขโ†’๐ŸŸก Beginner project โฑ๏ธ ~1.5 hr ๐Ÿ“Š R ยท tidyverse ๐Ÿ“ˆ Real data

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.

Check your understanding

The R project analyzes which friendly real dataset?
Computing a value per group (like the mean per species) is called:
Why does the lesson start you on penguins instead of gene expression data?
The wrangling skill is the same on penguins as on an expression table; penguins just let you learn the steps without extra jargon, then the skill transfers.
What does NA mean in the penguins dataset?
NA marks a missing value; many calculations refuse to run with NAs present so you do not get silently wrong answers, which is why the project drops or handles them.
Which function in the project draws the scatter and box plots?
The project builds its figures with ggplot(), adding layers like geom_point() and geom_boxplot() to reveal the structure in the data.
Next foundation

Start the Bash series: setting up a terminal โ†’