More geoms, zooming safely, and picking a theme
2026-09-10
leaf_df %>% ggplot(...)scale_color_manual() / scale_fill_manual() — colors you choosefacet_wrap() and facet_grid() — one panel per groupstat_summary() — mean and mean ± SE, drawn from raw dataNote
✅ Key idea from Lecture 03
You can already build a grouped, colored, faceted figure. Today: one more geom, a safer way to zoom, and picking a theme that looks intentional instead of default.
geom_smooth()coord_cartesian()theme()Tip
🖐 Try it yourself
By the end you will recognize the single most common ggplot mistake — zooming with xlim()/ylim() — before you ever make it yourself.
References:
Both PDFs are in the course readings/ folder.
This lecture runs in three short chunks. After each chunk you switch to the activity and type the code yourself into your R script.
Note
✅ Why bother?
xlim() and coord_cartesian() produce plots that look identical for simple scatter plots — the difference only shows up once you compute something (a smooth line, a boxplot). Typing both yourself and comparing outputs is the only way this sticks.
We will cover: mapping shape alongside color, and geom_smooth().
🛑 After this chunk you will do Activity Parts 1–2.
Note
🔮 Predict first: We map both color = shade and shape = shade to the same variable. Will ggplot draw one legend or two?
# Map color AND shape to the same variable -------------
leaf_df %>%
ggplot(aes(x = petiole_mm, y = mass_g, color = shade, shape = shade)) +
geom_point(size = 2.5, alpha = 0.8) +
scale_color_manual(values = c("sunny" = "goldenrod", "shady" = "forestgreen")) +
labs(
title = "Leaf Mass vs. Petiole Length",
x = "Petiole Length (mm)", y = "Leaf Mass (g)",
color = "Shade", shape = "Shade"
) +
theme_minimal()
color and shape merges them into one legendcolor and shape legend titles differ in labs(), you get two legends instead — usually not what you wantgeom_smooth()# geom_smooth() fits a line straight from the data -----
leaf_df %>%
ggplot(aes(x = petiole_mm, y = mass_g)) +
geom_point(alpha = 0.6, color = "grey40") +
geom_smooth(method = "lm", color = "tomato", fill = "tomato", alpha = 0.15) +
labs(
title = "Leaf Mass vs. Petiole Length, with Trend Line",
x = "Petiole Length (mm)", y = "Leaf Mass (g)"
) +
theme_minimal()
method = "lm" fits a straight line (linear model)lm() in the Regression unit later this semesterTip
geom_smooth() is exploration, not a hypothesis test — it shows you whether a line is worth fitting formally before you commit to one.
🛑 Do Activity Parts 1–2 now
Load the data, map shape and color to shade, then add a geom_smooth() trend line. Predict, type, run.
We will cover: coord_cartesian() vs. xlim()/ylim() — they look the same until they don’t.
🛑 After this chunk you will do Activity Part 3.
coord_cartesian() — Zoom Without Removing Data# coord_cartesian() zooms the VIEW, keeps all the data -
leaf_df %>%
ggplot(aes(x = petiole_mm, y = mass_g)) +
geom_point(alpha = 0.6, color = "grey40") +
geom_smooth(method = "lm", color = "tomato", fill = "tomato", alpha = 0.15) +
coord_cartesian(xlim = c(50, 70), ylim = c(0.4, 0.6)) +
labs(
title = "Zoomed with coord_cartesian()",
x = "Petiole Length (mm)", y = "Leaf Mass (g)"
) +
theme_minimal()
xlim() / ylim() — Removes Data FirstNote
🔮 Predict first: If we drop the leaves outside our zoom window before fitting the trend line, will the line look the same as before, or different?
# xlim()/ylim() DELETE rows outside the range first ----
leaf_df %>%
ggplot(aes(x = petiole_mm, y = mass_g)) +
geom_point(alpha = 0.6, color = "grey40") +
geom_smooth(method = "lm", color = "tomato", fill = "tomato", alpha = 0.15) +
xlim(50, 70) +
ylim(0.4, 0.6) +
labs(
title = "Zoomed with xlim() / ylim() — different line!",
x = "Petiole Length (mm)", y = "Leaf Mass (g)"
) +
theme_minimal()
Warning
⚠️ Watch out!
xlim()/ylim() silently delete rows outside the range before anything is computed. The trend line above is fit only on the leaves that remain — a different line than coord_cartesian() produced, with no warning that data went missing.
Rule of thumb: use coord_cartesian() to zoom. Only use xlim()/ylim() when you actually want those rows excluded from the analysis, not just out of view.
🛑 Do Activity Part 3 now
Zoom the same plot two ways and compare the trend lines. Predict, type, run.
We will cover: picking a built-in theme, theme() tweaks, and choosing a file format with ggsave().
🛑 After this chunk you will do Activity Parts 4–5.
# theme_classic() — clean, close to journal defaults ---
leaf_df %>%
ggplot(aes(x = shade, y = mass_g, fill = shade)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
scale_fill_manual(values = c("sunny" = "goldenrod", "shady" = "forestgreen")) +
labs(x = "Shade", y = "Leaf Mass (g)", title = "Leaf Mass by Shade") +
theme_classic() +
theme(legend.position = "none")
Five built-in themes to try on the same plot:
| Theme | Look |
|---|---|
theme_grey() |
default — grey background |
theme_bw() |
white background, grey gridlines |
theme_minimal() |
no background, subtle gridlines |
theme_classic() |
white background, axis lines only |
theme_light() |
light grey lines and border |
Tip
theme_classic() is a safe default for scientific figures — clean, uncluttered, close to what most journals expect.
theme()# theme() overrides individual elements afterward -----
leaf_df %>%
ggplot(aes(x = shade, y = mass_g, fill = shade)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
scale_fill_manual(values = c("sunny" = "goldenrod", "shady" = "forestgreen")) +
labs(x = "Shade", y = "Leaf Mass (g)", title = "Leaf Mass by Shade") +
theme_classic() +
theme(
legend.position = "none",
axis.text = element_text(size = 12),
axis.title = element_text(size = 13)
)
theme_classic()), then add theme() afterward to tweak individual pieceslegend.position = "none" — drop a redundant legendaxis.text / axis.title — control tick labels and axis titles separatelyNote
✅ Key idea
You rarely need to build a theme from scratch. Start from a built-in theme and override only what looks wrong.
leaf_plot <- leaf_df %>%
ggplot(aes(x = shade, y = mass_g, fill = shade)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
scale_fill_manual(values = c("sunny" = "goldenrod", "shady" = "forestgreen")) +
labs(x = "Shade", y = "Leaf Mass (g)") +
theme_classic() +
theme(legend.position = "none")
# PNG — good for Word docs, slides, web
ggsave("figures/leaf_mass_theme.png",
plot = leaf_plot, width = 5, height = 5,
units = "in", dpi = 300)
# PDF — vector, scales to any size, good for print
ggsave("figures/leaf_mass_theme.pdf",
plot = leaf_plot, width = 5, height = 5, units = "in")| Format | Best for | Scales? |
|---|---|---|
.png |
Word docs, slides, web | ❌ fixed pixels |
.pdf |
Print, journals | ✅ infinite |
.tiff |
Journal submission | ❌ fixed pixels |
Warning
⚠️ Watch out! ggsave() takes the filename first, then plot =. Always name the plot object explicitly — do not rely on ggsave grabbing “the last plot shown.”
🛑 Go to Activity 4 — Parts 4–5
Close the slides. Pick a theme, tweak it, and save your plot as both PNG and PDF.
shape alongside color to merge legendsgeom_smooth(method = "lm") — a quick trend line, not a hypothesis testcoord_cartesian() zooms safely; xlim()/ylim() silently deletes rows firsttheme_classic(), etc.) + theme() for small overrides.png for documents/slides, .pdf for print/journalsReferences:
Up next — Lecture 05, Wrangling:
filter(), select(), mutate(), arrange()