The AI writes better code when it knows the shape of your data: how many rows, and what one row stands for (a participant, a participant-visit, a smoker-category).
Not sure what a row means? Ask the AI what it thinks each row represents, then check that answer against the table.
Most requests come down to the verbs from this module:
keep some rows (filter)
compute a new column (mutate)
count rows by group (count)
pick and rename columns (select, rename)
reorder rows (arrange)
Exercise 1
Open notebooks/03-wrangling.qmd in your Codespace.
Back of the room — agentic. Work the exercise by chatting: ask for what you want and let the assistant write and run the code for you.
Front of the room — manual. Type the code yourself. Reach for the assistant only when you’re stuck.
“Chatting” means DMing the Workshop Assistant bot on Discord.
countdown::countdown(30)
Visualization
From rows to marks
In the wrangling half, every row of the table meant something specific (a participant, a participant-visit).
In a plot, every row of the data becomes a mark — a point, a bar, a box.
Each of the 8 points above came from 8 rows of table_02. The plotting half asks: what is each mark, and how is each mark connected to a row?
What plot answers your question?
compare a number across groups → boxplot
relate two numbers → scatter
compare counts across categories → bar
Pick the plot to fit the question.
Every plot has three pieces
geom — the kind of mark and the rule for drawing it (geom_point draws round dots; geom_boxplot draws a five-number summary box)
mark — what you see on the plot. Marks are produced by the geom.
mapping — the link from a data column to a property of the mark, written in aes()
Every plot has three pieces
Marks: the coloured dots.
Mappings: ph → x, nugent_score → y, arm → color.
Geom: geom_jitter.
What “aesthetic” means in ggplot
In ggplot, an aesthetic is a visual property of a mark — x position, y position, color, fill, shape, size.
aes() is short for aesthetic mapping and connects a data column to one of these properties.
Aesthetic mappings with aes()
aes(x = ph, y = nugent_score) says: each mark’s x position comes from the row’s ph; its y position comes from the row’s nugent_score.
The same column can be shown on different aesthetics — aes(y = ph) or aes(color = ph).
Variable type matches aesthetic
Continuous variables (ph, crp_blood) suit continuous aesthetics: y position, color gradient.
Each aesthetic is a channel you can map a column to: x position, y position, color, fill, shape, size. The next activity asks you to pick which channel fits which column.
Exploring data for patterns
In pairs or trios, open the data on the module page and sketch a visualization that reveals which TB strains are drug-resistant.
Sketch at excalidraw.com. Name your marks, your mappings, and your geom, then post the image and your answers to #module-3-sketches on Discord.
countdown::countdown(20)
Your first ggplot
ggplot(table_02, aes(x = ph, y = nugent_score)) +geom_point()
Each row of table_02 becomes one point.
Same mapping, swap geoms
ggplot(table_02, aes(x = ph, y = nugent_score)) +geom_jitter()
geom_jitter() adds a small random offset — useful for integer-valued nugent_score.
Same x, same y, different mark.
A third aesthetic via color
ggplot(table_02, aes(x = ph, y = nugent_score, color = arm)) +geom_jitter()
Each mark’s position comes from ph and nugent_score; its color comes from arm.
Different geoms answer different questions
ggplot(table_02, aes(x = arm, y = ph)) +geom_boxplot()
Same data. Now x is categorical (arm).
The mark is a box that summarises many rows. The row-to-mark ratio is not 1:1 — this geom summarises.
color vs fill
ggplot(table_02, aes(x = arm, y = ph)) +geom_boxplot(color ="navyblue", fill ="lightblue")
color is the outline. fill is the inside.
Mapping vs parameter
ggplot(table_02, aes(x = arm, y = ph, fill = arm)) +geom_boxplot()
Inside aes() — the fill varies with the data.
Mapping vs parameter
ggplot(table_02, aes(x = arm, y = ph)) +geom_boxplot(fill ="navyblue")
Outside aes() — the fill is one fixed value.
The single most common beginner mistake.
Bar plot from pre-counted data
table_01 |>count(arm) |>ggplot(aes(x = arm, y = n)) +geom_col()
count() returns one row per category. geom_col() draws one bar per row. The row-to-mark ratio is 1:1 again.
We use geom_col instead of geom_bar so the count → bar mapping is explicit.