Activity 04 - GGPlot II

More geoms, zooming safely, and picking a theme

ggplot
tidyverse

Hands-on companion to the GGPlot II lecture. Map shape alongside color, add a trend line, compare coord_cartesian() to xlim()/ylim(), and pick and save a themed figure.

Author

Bill Perry

Published

September 10, 2026

More Geoms, Coordinates, and Themes

Recap from Activity 03

  • Piped leaf_df straight into ggplot()
  • Assigned exact colors with scale_fill_manual() / scale_color_manual()
  • Split plots with facet_wrap() and facet_grid()
  • Built a mean ± SE plot with stat_summary()

Today’s Objectives

  1. Map shape alongside color, and add a geom_smooth() trend line
  2. Compare coord_cartesian() to xlim()/ylim()
  3. Pick a built-in theme and adjust it with theme()
  4. Save a plot as both PNG and PDF

How this activity works

You are building one R script this whole class, and you turn it in. Create it now: scripts/04_ggplot_2.R.

  • Every line you run goes in that script — not in the Console, not typed into this page. Type it, don’t paste it.
  • Start every code chunk in your script with a short # comment that says what it does. The comments are part of the grade.
  • The top of your script, in this order: a title comment, then your library() calls, then the line that loads the data into leaf_df.
  • Code marked ▶ Run this is typed into your script exactly as shown. Code marked ✏️ Your turn is a change you make in that same script and run.

🔮 Predict before you run

Before you run any ▶ Run this block, cover the output and predict what R will draw. Predicting is what turns “I saw it on a slide” into “I can write it.”


Part 1 · Load libraries and data

Tip📂 Get the data

Same leaf file as Activity 02–03 — it should already be in your project’s data/ folder. If not: 2026_09_03_data_sci_leaf_area.xlsx → put it in data/.

▶ Type this at the very top of scripts/04_ggplot_2.R:

# ---- Activity 04: GGPlot II ------------------------------
# your name, today's date

# ---- Libraries -------------------------------------------
library(readxl)      # read Excel files
library(tidyverse)   # dplyr + ggplot2
library(janitor)     # clean_names()
# ---- Load data ------------------------------------------
leaf_df <- read_excel("data/2026_09_03_data_sci_leaf_area.xlsx") %>%
  clean_names()

glimpse(leaf_df)     # look at the data right after loading

Part 2 · Shape, color, and a trend line

🔮 Predict first: We map color = shade and shape = shade to the same variable. One legend or two?

▶ Run this:

# scatter of mass vs petiole length, one color + shape per shade
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()

✏️ Your turn — in your script: Add a geom_smooth(method = "lm") layer to the plot above (no color/fill needed — let it default). Run it.

Does the trend line slope up or down?

Part 3 · coord_cartesian() vs. xlim()/ylim()

▶ Run this — Version A, coord_cartesian():

# zoom the view WITHOUT dropping data: coord_cartesian()
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()

🔮 Predict first: Will the trend line in Version B look the same as Version A, or different? Why?

▶ Run this — Version B, xlim()/ylim():

# zoom the view by DELETING rows outside the window: xlim() / ylim()
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()", x = "Petiole Length (mm)", y = "Leaf Mass (g)") +
  theme_minimal()

✏️ Your turn: Compare the two trend lines. Are they the same? Explain why, in terms of what each function does to the data before the line is fit.

Same or different?
Why:

Part 4 · Pick a theme

▶ Run this, trying each theme in turn:

# build the base plot once, then add each theme on top
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)", title = "Leaf Mass by Shade")

leaf_plot + theme_grey()
leaf_plot + theme_bw()
leaf_plot + theme_minimal()
leaf_plot + theme_classic()
leaf_plot + theme_light()
Which theme do you like best for this plot, and why?

Adjust it with theme()

▶ Run this:

# start from theme_classic(), then override a few individual elements
leaf_plot_final <- leaf_plot +
  theme_classic() +
  theme(
    legend.position = "none",
    axis.text  = element_text(size = 12),
    axis.title = element_text(size = 13)
  )

leaf_plot_final

✏️ Your turn — in your script: Change axis.text to size = 16 and run it.

Too big, too small, or just right?

Part 5 · Save in two formats

▶ Run this:

# PNG for slides and web, PDF for print / vector editing
ggsave("figures/leaf_mass_theme.png",
       plot = leaf_plot_final, width = 5, height = 5,
       units = "in", dpi = 300)

ggsave("figures/leaf_mass_theme.pdf",
       plot = leaf_plot_final, width = 5, height = 5, units = "in")
Open both files. When would you send a colleague the .pdf instead of the .png?

Part 6 · Review and checkpoint

At this point you should be able to:

✏️ Your turn — before you move on: Run your entire script top to bottom with Ctrl/Cmd + Shift + Enter (Source). Does it complete without errors?

Ran cleanly?  Y / N
If not, what error appeared:

What your project folder should contain

tree_project/
├── data/
│   └── 2026_09_03_data_sci_leaf_area.xlsx   <- never edit this
├── figures/
│   ├── leaf_mass_theme.png                  <- Part 5
│   └── leaf_mass_theme.pdf                  <- Part 5
└── scripts/
    └── 04_ggplot_2.R                        <- your script (turn this in)

Extension — out of class (~30–40 min)

Add this to the bottom of scripts/04_ggplot_2.R and turn it in with the rest. Put your written answers in # comments right under the code they go with. In class you zoomed a fixed window on mass_g vs petiole_mm. Now the variable pair and the window are yours.

E1 · Your own zoom window (4 pts)

  1. Pick your own pair of numeric variables from leaf_df that class did not use together (not mass_g vs petiole_mm). thickness_mm is available.
  2. Look at the range of your x-variable (range() / summary()), then pick a zoom window that cuts the data roughly in half — a real cut, not a guess.
  3. Build the coord_cartesian() version and the xlim() version of a scatter with geom_smooth(method = "lm"), exactly like the in-class Part 3.
  4. Run nrow(leaf_df), then filter() leaf_df to your window and check nrow() again — that’s the row count xlim() is silently using.

Record your exact window and both row counts in comments.

E2 · Predict, then check (3 pts)

Before running E1: in a comment, write your variable pair and window, and predict — will the xlim() trend line be the same slope as the coord_cartesian() one, steeper, or shallower? Also predict, as a number, how many rows land in your window. Run the code, then write how close your row-count guess was and whether the trend lines differed as predicted.

E3 · Explain it, using YOUR numbers (3 pts)

  1. Using your before/after row counts, explain why xlim() gave a different line than coord_cartesian() for your window. If the counts came out equal, explain how that can happen.
  2. In your own words, why is it dangerous that xlim()/ylim() delete rows silently? Give a realistic case where that silent deletion leads to a wrong conclusion.
  3. Name the theme you chose in Part 4 and say, in a sentence or two, why you’d pick it over theme_grey() for a figure in a report.

Getting unstuck

  1. Read the error message out loud. R usually names the line and the problem.
  2. Line looks wrong after zooming? Check whether you used xlim()/ylim() (deletes data) instead of coord_cartesian() (keeps it).
  3. Legend didn’t merge? color and shape (or the matching labs() titles) must reference the exact same variable name.
  4. Cheat sheetshttps://posit.co/resources/cheatsheets/
  5. Bring the exact error (copy-paste it) to class, Canvas, or office hours.

End of the GGplot II activity. Next: wrangling with filter, select, mutate, and arrange.