Week 4 · Thursday, Oct 15 · Lecture
| Time | What |
|---|---|
| 1:30 | Why plot? Data + aes() + geom |
| 1:55 | Lines, groups and layers |
| 2:15 | Mapped vs. fixed; scales and trend lines |
| 2:35 | Facets, labels, saving a figure |
| 2:45 | Back to the survey: a plot is a check, too |
| 3:00 | Tuesday: how the afternoon will run |
Learning goals: build a plot layer by layer, decide what is mapped to the data and what is fixed, and say in one sentence why a plot type suits a question.
Laptops open from the start: the DS 3 discussion check moves to Tuesday afternoon, so today is all ggplot2. Still no AI.
anscombe comes with R. Four small datasets, x1/y1 to x4/y4:

Same means, same SDs, same correlation, same regression line. Only one of them is a straight-line relationship. Look at your data before you summarize it.
Create week04/ggplot2.R:
ggplot2 came with the tidyverse you installed in Week 1. library() goes at the top of the script, every timethere is no package called 'ggplot2': install.packages("ggplot2") in the console, never in the scriptTip
Every figure you make today stays in this file. A plot you can’t re-make from a script is a screenshot, not an analysis.
aes() + geom+| Part | Says | Here |
|---|---|---|
| data | which data frame | gapminder |
aes() |
which column goes to which visual property | gdpPercap → x, lifeExp → y |
| geom | how to draw each row | a point |
This is the grammar of graphics: every ggplot, however fancy, is these three plus optional extras, added with +.
+ goes at the end of the lineR ran the first line as a complete command (an empty plot), then hit a line starting with +.
Tip
Same rule as Week 2: R reads a line as finished unless something tells it more is coming. A + at the end of a line says “more is coming”.
| Your question is about… | Geom | Example |
|---|---|---|
| a relationship between two numbers | geom_point() |
GDP vs. life expectancy |
| change over time | geom_line() |
life expectancy, 1952-2007 |
| the distribution of one number | geom_histogram() |
commute times |
| a number across groups | geom_boxplot() |
commute by site |
| counts of a category | geom_bar() |
survey responses per site |
Before you write any ggplot code for your project, write the question in one sentence. The geom usually follows from it.
⏱ 5 min
On paper first: what does each one draw? What’s on each axis, and roughly how many marks?
Then run them in your script. For each, write one comment with the question it answers.
Distribution of life expectancy in 2007 · how it differs across continents · how many countries per continent (52 Africa, 25 Americas, 33 Asia, 30 Europe, 2 Oceania). geom_bar() needs only x: it counts for you.
A saw. geom_line() connected all 36 rows in year order, jumping between the three countries at every year.
group says which rows form one lineAdd + geom_point() and the points sit on top of the lines. Put geom_point() first and the lines cover them. Later layers win.
Where you write aes() |
Applies to |
|---|---|
inside ggplot() |
every layer |
inside one geom_*() |
that layer only |
This matters in a minute, when we add trend lines.
⏱ 7 min
Last week’s %in%, now with a picture:
andes, only the rows for Bolivia, Chile and Peru. Check: 36 rowscolour = country from ggplot() into geom_line() only. What changes, and why?aes(): it depends on the dataSalmon points, and a legend titled colour with one entry, blue. Inside aes(), "blue" is treated as data: a column whose every value is the word “blue”. ggplot gives that category its first default colour.
aes(): the same for every point| Property | Example |
|---|---|
colour |
"steelblue", "#204779" |
size |
3 |
alpha |
0.5 (0 invisible, 1 solid) |
shape |
17 (filled triangle) |
linewidth |
1.2 (lines) |
alpha is how you see 1704 overlapping points. Shapes are numbered; ?points shows them all.
Depends on a column? Inside aes(). Same for everyone? Outside.
⏱ 6 min
Each has one mistake. Fix it, and write the mistake in a comment.
# a) all points orange
ggplot(gm07, aes(x = gdpPercap, y = lifeExp, colour = "orange")) + geom_point()
# b) points coloured by continent
ggplot(gm07, aes(x = gdpPercap, y = lifeExp)) + geom_point(colour = continent)
# c) one line per country, thicker
ggplot(andes, aes(x = year, y = lifeExp, group = country)) + geom_line(aes(linewidth = 2))colour = "orange" goes in geom_point(), outside aes() · b) continent is a column, so aes(colour = continent). As written: object 'continent' not found, because outside aes() R looks for a variable called continent, not a column · c) a fixed width: geom_line(linewidth = 2). Inside aes(), 2 is treated as data: you get a pointless legend, and the lines aren’t even width 2 (ggplot scales them to 3.5).GDP runs from about 240 to 113,000. On a log scale each step on the axis is ×10, and the cloud straightens out. The message is information, not an error.

Left: colour in ggplot(), so geom_smooth() inherits it and fits five lines. Right: colour only on the points, so one line for everyone. Which is right depends on your question.
One panel per continent, same axes, so you can compare them. facet_grid(rows ~ cols) does two variables at once. Facets beat five colours when the groups overlap a lot.
ggplot(gapminder, aes(x = gdpPercap, y = lifeExp, colour = continent)) +
geom_point(alpha = 0.4) +
scale_x_log10() +
labs(x = "GDP per capita (USD, log scale)", y = "Life expectancy (years)",
colour = "Continent", title = "Richer countries live longer",
subtitle = "Gapminder, 142 countries, 1952-2007") +
theme_minimal()p_le_gdp + facet_wrap(~ continent))ggsave() writes a file. Sizes are in inches; the same code gives the same figure every timefigures/ must exist first: create it once in Positron’s file paneImportant
Never “Export → Save as” from the plot pane for your project. If a reviewer asks you to change the axis label, you want to change one line and re-run, not redo it by hand.
theme_*() first, then theme(...) to adjust. The other way round, the full theme resets your tweak. Later layers win, again.
Tuesday’s cleaning script, then one boxplot:

NA commute (id 35). Expected: colSums(is.na(survey)) said 1NA box: the 1 row with no site (id 27). A single value, so a flat lineCount the NAs before you plot. Then the warning tells you nothing new.
If the warning says Removed 40 rows and you expected 1, stop. That’s a cleaning bug, not a plotting one.
To drop the NA site from the plot on purpose: ggplot(survey[!is.na(survey$site), ], ...). To keep it, label it (“no site recorded”) and say so in the caption. Either way, it’s a decision you write down.
⏱ 8 min
For the survey, write the code and a one-sentence comment saying why this plot type fits:
commute_min and visits)Then save your favorite with ggsave() into figures/, restart R, and source week04/ggplot2.R to check it re-makes the file.
geom_histogram(): one number, its distribution · 2. geom_point(): relationship between two numbers (try alpha, many share values) · 3. geom_bar(): counts of a category. The sentence is the habit we’re after: you’ll be asked it in every oral component of the project.aes() + geom, joined with + at the end of each linegroup says which rows make one line; a mapped category does it tooaes() in ggplot() is inherited by every layeraes(): depends on the data. Outside: fixed for every markscale_x_log10() for wide ranges; geom_smooth(method = "lm") follows the groupingfacet_wrap() for small multiples; labs() for readers; ggsave() from the scriptNA counts you already know9:50-10:55 am · DS 4
Lesson 8: Graphics with ggplot2, alone, no AI. Bring the printout to Thursday, Oct 22.
1:30-3:05 pm · DS 3 check + proposals
Bring your DS 3 printout (Lesson 6) and a pencil: the discussion check is first. Then every group presents. Vectorization moves to Thursday, Oct 22.
Proposal: 3 minutes plus questions, each partner answers. Guidelines.
Optional but useful: one exploratory plot of your data, made with today’s code. “Here’s what I already see” is a strong way to explain your plan.
Tonight or tomorrow: read the Lesson 8 page before Tuesday’s DS. The exercises will feel familiar after today.
Next: Tuesday, Oct 20 · DS 4 in the morning, proposals in the afternoon.

STAT 266A · Fall 2026 · UC Santa Cruz