Week 7 · Tuesday, Nov 10 · Lecture
| Time | What |
|---|---|
| 1:30 | DS 7 check: swap laptops |
| 1:45 | Both waves, by group |
| 2:05 | Reshape: a table for people, long data for ggplot |
| 2:25 | “It runs. Is it right?”: an average of averages |
| 2:45 | Your project: groups, joins, shapes |
Learning goals: rebuild last week’s combined survey with dplyr, summarize it by group, reshape the summary into a table people can read, and catch a summary that weights groups wrongly.
You need survey_wave2.csv and sites.csv in data/, from last week.
⏱ 15 min
Swap laptops with your neighbor and open their week07/ds07_notes.R.
On the Ed thread “DS 7 check”, one post per pair: your topic, and one sentence on why it was hard.
The 1-point discussion check. I read every post, and they decide what we review.
Create week07/combining-practice-ii.R. Last Tuesday’s combine, in four lines:
# Week 7 · Combining data practice II
source("scripts/clean-survey.R")
library(dplyr)
library(tidyr)
wave2 <- read.csv("data/survey_wave2.csv") |> rename(commute_min = commute)
both <- bind_rows(survey, wave2, .id = "wave")
sites <- read.csv("data/sites.csv")
both_sites <- left_join(both, sites, by = "site", relationship = "many-to-one")
if (nrow(both_sites) != 65) stop("expected 65 rows, found ", nrow(both_sites))Last week this took a slide of checks. The checks stay; the code got shorter.
.groups = "drop": we’re done with groups, so say son column is not decoration. A mean of 1 person (row 4) or 6 people (row 7) deserves less trust than one of 18⏱ 8 min
Using both_sites:
building: number of people, median commute_min, and the share who visit 5 or more times a week (hint: mean(visits >= 5))wave and building. How many rows do you expect?mean() of TRUE/FALSE is the proportion of TRUEs, one of R’s most useful tricks. Part 2: 7 rows, for the same reason as by_ws. Part 3: “100% visit 5+ times” is one person.
by_ws is long. People read wideTo compare the waves side by side, you want one row per site and a column per wave:
4 rows, 3 columns. Predict it, then check. The NA is real: nobody without a site in wave 2.
select()?Skip it, and n comes along:
7 rows, half NA. No error.
Every row of a wide result is one combination of the columns you didn’t pivot. n differs between waves, so site + n never repeats. Fix: drop n, or pivot it too with values_from = c(n, mean_visits).
Three variables, one plot? Stack them first:
65 people × 3 metrics = 195 rows. scales = "free": age, minutes and visits don’t share an axis. ggplot will warn about removed rows: the 4 NAs, now in one column.
⏱ 8 min
From long:
metric: how many values, how many are NA, and the medianlong |>
group_by(metric) |>
summarize(n = n(), n_missing = sum(is.na(value)), median = median(value, na.rm = TRUE))
#> 1 age 65 3 29
#> 2 commute_min 65 1 17.5
#> 3 visits 65 0 4
long |>
group_by(wave, metric) |>
summarize(median = median(value, na.rm = TRUE), .groups = "drop") |>
pivot_wider(names_from = wave, values_from = median, names_prefix = "wave_")
#> 1 age 28 30.5
#> 2 commute_min 17.5 17.5
#> 3 visits 4 3A student asked an AI: “What’s the average number of library visits per week in my survey, by site and overall?”
Result. Visits differ by site, from 2.7 (east) to 3.9 (south). Overall, respondents visit the library 3.7 times a week.
⏱ 10 min
In pairs. It runs with no errors or warnings. Find what’s wrong, how you found it, and what you’d do instead. No other AI.
| Problem | How you’d find it |
|---|---|
| “Overall” is an average of 4 averages: each site counts the same, whatever its size | Compare with mean(both$visits): 3.35, not 3.7 |
| One person with no site is a quarter of the “overall” answer | count(site), or n = n() in the summary |
No n column, so nothing on the screen looks off |
Every grouped summary gets n = n() |
| “Respondents visit 3.7 times”: that’s not any respondent’s average | Ask: average over what? People, or sites? |
The mean of group means is the overall mean only if every group is the same size.
NA site on purpose, and report itOn Ed, thread “AI audit: averages”: one post per pair, the problem you found first, and how.
⏱ 15 min
On your project data:
group_by() summary that answers a question from your proposal, with n = n(). Any group too small to report?relationship = "many-to-one", and an anti_join() in both directions. What’s missing, and from where?pivot_longer() + facet_wrap() insteadIf your check-in raised a question about your pipeline, this is the time to work on it. I’ll come around.
n = n() in every grouped summary, and read it before the means.groups = "drop" or ungroup() when you’re done with groupspivot_wider(): one row per combination of the columns you didn’t pivot. An extra column means extra rows and NAspivot_longer() + facet_wrap(): several variables, one plot, one pipemean(x >= 5) is a proportionweighted.mean(means, n)left_join(..., relationship = "many-to-one") + both anti_join()s: every combine, every timeThursday, Nov 12: Reproducible reports with Quarto. The code you’ve written in scripts moves into a document that runs it, and the numbers in your text come from the code.

STAT 266A · Fall 2026 · UC Santa Cruz