Intro to data wrangling and visualization with the tidyverse
Practice the single-table dplyr verbs and the fundamentals of plotting with ggplot2.
Slides
Exercise 1: Data wrangling
Mode setup. The room is split into Group A and Group B. For Exercise 1, Group A works agentic — Claude Code reads your notebook, writes code, and runs it. Group B works manual — you write each step yourself; you may ask a chat LLM conceptual questions, but it does not act in your environment. Exercise 2 flips the modes.
Open notebooks/03-wrangling.qmd in your Codespace and work through the parts below.
Part 1 — Predict (no code)
Look at table_02 and answer in the notebook (in plain words, no code yet):
- How many rows? What does one row represent?
- After
table_02 |> filter(time_point == "baseline"), how many rows? What does each row represent now? - After
table_02 |> count(arm), how many rows? What does each row represent now?
We’ll briefly compare answers across the room before moving to code.
Part 2 — Build the pipeline
Build one growing pipeline on table_02, one verb per step. Whatever your assigned mode, the goal is the same.
- Pipe
table_02intohead(). - Filter to the baseline time point.
- Add
select()to keeppid,arm,ph,nugent_score,crp_blood. - Add
mutate()to createhigh_ph = if_else(ph >= 4.5, "yes", "no"). - Add
arrange(desc(ph)). - Assign the final pipeline to
early_visit. - As a separate one-liner, run
table_02 |> count(arm)and confirm it matches your Part 1 prediction.
Part 3 — Reach (optional)
Classify Nugent score into clinical categories with case_when():
- 0–3 →
"normal" - 4–6 →
"intermediate" - 7–10 →
"BV"
Add the category column to early_visit, then run count(arm, nugent_category) to see the distribution by treatment arm.
Exploring data for patterns
Reproduced from a Schmidt Science Fellowship activity, thanks to Fatima Hussain.
Patterns are the essence of data exploration, and your eye is good at finding them once the data is drawn well. The same numbers can hide or reveal a pattern depending on how you choose to show them. This activity asks you to make those choices by hand, before writing any ggplot code.
The data
Eight Mycobacterium tuberculosis strains (S1–S8) were profiled for five genes involved in drug resistance: rpoB (rifampicin), katG and inhA (isoniazid), embB (ethambutol), and pncA (pyrazinamide).
Table 1 gives the allele each strain carries for each gene, either Wild Type or Mutant. Table 2 gives how strongly each gene is expressed, from 0 (none) to 3 (high).
| gene | S1 | S2 | S3 | S4 | S5 | S6 | S7 | S8 |
|---|---|---|---|---|---|---|---|---|
| rpoB | Wild Type | Mutant | Wild Type | Mutant | Wild Type | Wild Type | Mutant | Mutant |
| katG | Wild Type | Mutant | Wild Type | Mutant | Wild Type | Wild Type | Mutant | Mutant |
| inhA | Wild Type | Wild Type | Wild Type | Mutant | Wild Type | Wild Type | Mutant | Wild Type |
| embB | Wild Type | Mutant | Wild Type | Wild Type | Wild Type | Wild Type | Mutant | Mutant |
| pncA | Wild Type | Wild Type | Mutant | Mutant | Wild Type | Wild Type | Wild Type | Mutant |
| gene | S1 | S2 | S3 | S4 | S5 | S6 | S7 | S8 |
|---|---|---|---|---|---|---|---|---|
| rpoB | 2 | 2 | 1 | 2 | 2 | 1 | 2 | 2 |
| katG | 2 | 1 | 2 | 1 | 2 | 2 | 1 | 1 |
| inhA | 1 | 3 | 1 | 3 | 1 | 1 | 3 | 2 |
| embB | 1 | 2 | 1 | 1 | 2 | 1 | 2 | 2 |
| pncA | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 1 |
Somewhere in here is a group of drug-resistant strains. Printed as plain tables, it is hard to see.
Your turn
Work in a pair or trio with whoever is next to you.
- Open a blank canvas at excalidraw.com. No login needed.
- Sketch one visualization of this data that would let someone spot which strains are drug-resistant.
- Describe your sketch in the vocabulary from the slides:
- What is your mark, the thing you draw for each piece of data?
- What are your mappings: which column controls position, which controls color or fill, which controls shape or size?
- What geom would draw this in ggplot?
- Export the canvas as an image (top-left menu, then “Export image”) and post it, with your three answers, to #module-3-sketches on Discord.
We will put a few sketches on the screen and talk through the choices behind them.
Exercise 2: Data visualization
Mode flip. If you were agentic in Exercise 1, you are manual now; if you were manual, you are agentic. Same definitions as before — manual means you may ask a chat LLM conceptual questions but it does not touch your notebook.
Open notebooks/03-visualization.qmd in your Codespace.
Part 1 — Read two plots (no code)
The notebook shows two pre-built plots: a scatter (ph vs nugent_score, colored by arm) and a boxplot (ph by arm). For each plot, answer in plain words:
- What is the mark?
- What property of the mark comes from what column?
- How many rows of data made each mark?
Then sketch (excalidraw or paper) a plot that would answer “does pH differ between treatment arms?” Name the mark, the mappings, and how many rows make each mark.
Part 2 — Build the plot
Build one ggplot that grows step by step, on the raw table_02. Whatever your assigned mode, the goal is the same.
ggplot(table_02, aes(x = ph, y = nugent_score)) + geom_point(). Confirm each row is one point.- Swap
geom_point()forgeom_jitter(). - Add
aes(color = arm). - Switch to a boxplot:
aes(x = arm, y = ph) + geom_boxplot(). Notice the row-to-mark ratio changes. - Compare
aes(fill = arm)(mapping) withfill = "navyblue"(parameter). Run both. - Add
labs(x = "Treatment arm", y = "Vaginal pH", fill = "Arm"). - Add
facet_wrap(~ time_point).
Part 3 — Reach (optional)
Produce a polished final figure for the question. Facet by visit, color by arm, axes labeled, time_point panels in chronological order (baseline, week_1, week_7), and a clean theme.