EDS 221 Day 7 PM
Reshaping data
August 18th , 2026
Tidy data
A data frame is tidy when:
Every variable is a column
Every observation is a row
Every value is a cell
The same values can be arranged into many different table shapes — only one of them is tidy. Reshaping moves values between rows and columns without changing what they mean.
Meet the data: penguins, summarized
Average body mass by species and sex — one row per group. This is long data: each combination of species and sex gets its own row.
library (tidyverse)
library (palmerpenguins)
mass_by_sex <- penguins |>
filter (! is.na (sex)) |>
summarize (mean_mass = mean (body_mass_g, na.rm = TRUE ), .by = c (species, sex))
mass_by_sex
# A tibble: 6 × 3
species sex mean_mass
<fct> <fct> <dbl>
1 Adelie male 4043.
2 Adelie female 3369.
3 Gentoo female 4680.
4 Gentoo male 5485.
5 Chinstrap female 3527.
6 Chinstrap male 3939.
pivot_wider(): making data wider
pivot_wider() spreads one column’s values into new columns. names_from supplies the new column names; values_from supplies the cells.
mass_wide <- mass_by_sex |>
pivot_wider (names_from = sex, values_from = mean_mass)
mass_wide
# A tibble: 3 × 3
species male female
<fct> <dbl> <dbl>
1 Adelie 4043. 3369.
2 Gentoo 5485. 4680.
3 Chinstrap 3939. 3527.
pivot_wider(): why reshape?
Wide data makes it easy to compare values that were in different rows — now they’re in different columns of the same row.
mass_wide |>
mutate (diff = male - female)
# A tibble: 3 × 4
species male female diff
<fct> <dbl> <dbl> <dbl>
1 Adelie 4043. 3369. 675.
2 Gentoo 5485. 4680. 805.
3 Chinstrap 3939. 3527. 412.
This comparison would need a join if male and female were still in separate rows.
pivot_longer(): making data longer
pivot_longer() is the reverse: it gathers columns into rows. cols says which columns to gather; names_to/values_to name where they land.
mass_wide |>
pivot_longer (
cols = c (female, male),
names_to = "sex" ,
values_to = "mean_mass"
)
# A tibble: 6 × 3
species sex mean_mass
<fct> <chr> <dbl>
1 Adelie female 3369.
2 Adelie male 4043.
3 Gentoo female 4680.
4 Gentoo male 5485.
5 Chinstrap female 3527.
6 Chinstrap male 3939.
pivot_longer(): a messier example
billboard tracks each song’s chart rank in a separate column per week — wk1 through wk76. That’s 76 columns encoding one variable: week .
billboard |>
select (artist, track, wk1, wk2, wk3)
# A tibble: 317 × 5
artist track wk1 wk2 wk3
<chr> <chr> <dbl> <dbl> <dbl>
1 2 Pac Baby Don't Cry (Keep... 87 82 72
2 2Ge+her The Hardest Part Of ... 91 87 92
3 3 Doors Down Kryptonite 81 70 68
4 3 Doors Down Loser 76 76 72
5 504 Boyz Wobble Wobble 57 34 25
6 98^0 Give Me Just One Nig... 51 39 34
7 A*Teens Dancing Queen 97 97 96
8 Aaliyah I Don't Wanna 84 62 51
9 Aaliyah Try Again 59 53 38
10 Adams, Yolanda Open My Heart 76 76 74
# ℹ 307 more rows
pivot_longer(): selecting with starts_with()
cols accepts the same tidyselect helpers as select().
billboard |>
pivot_longer (
cols = starts_with ("wk" ),
names_to = "week" ,
values_to = "rank"
)
# A tibble: 24,092 × 5
artist track date.entered week rank
<chr> <chr> <date> <chr> <dbl>
1 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk1 87
2 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk2 82
3 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk3 72
4 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk4 77
5 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk5 87
6 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk6 94
7 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk7 99
8 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk8 NA
9 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk9 NA
10 2 Pac Baby Don't Cry (Keep... 2000-02-26 wk10 NA
# ℹ 24,082 more rows
Selecting columns to pivot
cols can be written several ways — pick whichever most clearly states your intent.
c(female, male) — name them explicitly
female:male — a range of adjacent columns
starts_with("wk") — match a naming pattern
!year — everything except a column
What to memorize
The same values can live in a wide or a long table — reshaping picks whichever shape fits the next step of your analysis.
Wider
pivot_wider()
names_from, values_from
Good for comparing values across a row
Longer
pivot_longer()
cols, names_to, values_to
Good for plotting and grouped summaries