library(tidyverse) # readr, dplyr, tidyr, stringr, etc.
library(readxl) # read .xlsx files
library(janitor) # clean_names()Data Skills for Psychology — Module 7
These slides are adapted from Applied Data Skills Ch. 8: Data Tidying.
By the end of this class, you will be able to:
|>)Make sure these packages are loaded:
Tip
Grab the Data tidying cheat sheet — it’s a great reference for the pivot_* and separate*() functions.
Save these into your data/ folder:
Both contain the same information — proportion of women in parliaments across countries and years — but stored very differently.
“Every messy dataset is messy in its own way.” — Hadley Wickham
Data tidying is often the most time-consuming part of an analysis.
Moving between formats that are easy for humans to read and formats that are easy for computers to use takes practice — that conceptual shift is the real skill of this chapter.
Observation: all the information about a single “thing” in a single condition (e.g., one country in one year).
Variable: one type of information about that observation (e.g., year, number of MPs).
Value: the data for one variable for one observation (e.g., 2025).
For data to be “tidy”:
When data is tidy, you can keep using the same code patterns even when the dataset grows or changes shape.
What’s “off” about this table?
country column squeezes in two values (name and code)1950, 1975, 2000, 2025)Suppose you want the average proportion of women per year. In Excel you would:
women_* and members_*If a new year of data arrives, you redo every step. Painful.
Each row is one country in one year. Five columns: country_name, country_code, year, members, women.
The same code works whether you have 4 years or 40. That’s the power of tidy.
Wide format: all measurements about one thing on the same row.
Long format: each measurement on its own row.
You’ll often need to convert between them. Think of it as the R version of a pivot table in Excel.
pivot_longer(): Wide → Longpivot_longer() takes several columns and stacks them into two new columns:
Three key arguments:
cols — which columns to “fold up”names_to — name of the new column for the old headersvalues_to — name of the new column for the old valuesNow there’s one row per country-year, with year as a real variable.
Warning
Because the column names are numbers, you must wrap them in backticks. Without them, R thinks 1950:2025 means “columns 1950 through 2025.”
Error in `pivot_longer()`:
! Can't select columns past the end.
ℹ Locations 1950, 1951, …, 2025 don't exist.
Try it!
Convert this table to long format. You don’t need to write code — just sketch it on paper or in a spreadsheet.
| band | ||
|---|---|---|
| The Beatles | 3.8 | 3.8 |
| The Rolling Stones | 3.4 | 3.1 |
| One Direction | 31.3 | 22.8 |
youtube column?pivot_wider(): Long → Widepivot_wider() is the reverse — it spreads one column’s values into new column names.
Three key arguments:
id_cols — column(s) that uniquely identify each new rownames_from — column whose values become the new headersvalues_from — column whose values fill the new cellsNow each row is a year, and each country has its own column. Useful for some kinds of comparison or display.
Look back at our long data — the women_members column still holds two values in each cell, like "3 / 257".
Tidy data needs one value per cell, so we need to split this column.
separate() to the Rescueconvert = TRUE is handy — it’ll turn "3" and "257" into actual numbers automatically.
country ColumnTry it!
The country column still combines two pieces of information ("Argentina (ARG)").
separate() to split it into country_name and country_codeseparate() splits at any non-letter, non-number characterextra = "drop" argument to handle the trailing )Here’s everything we did, written as separate steps:
untidy <- read_csv("data/untidy.csv", show_col_types = FALSE)
long <- pivot_longer(untidy,
cols = `1950`:`2025`,
names_to = "year",
values_to = "women_members")
split_data <- separate(long,
col = women_members,
into = c("women", "members"),
sep = " / ",
convert = TRUE)
tidy_data <- separate(split_data,
col = country,
into = c("country_name", "country_code"),
extra = "drop")Four intermediate objects. Easy to lose track of which one is “current.”
tidy_data <- read_csv("data/untidy.csv", show_col_types = FALSE) |>
pivot_longer(
cols = `1950`:`2025`,
names_to = "year",
values_to = "women_members"
) |>
separate(
col = women_members,
into = c("women", "members"),
sep = " / ",
convert = TRUE
) |>
separate(
col = country,
into = c("country_name", "country_code"),
extra = "drop"
)One object, read top-to-bottom: read it, then pivot, then separate, then separate again.
read_csv() — and thenpivot_longer() — and thenwomen_members into two columns — and thencountry into two columnsNote
You can debug a pipe by highlighting from the start to just before the pipe you want to stop at, and running it. The output is the data at that step.
Don’t feel obligated to fit every step into one pipeline.
Break it up when:
Pipes are a tool for clarity, not a contest.
pivot_longer() stacks wide columns into a single column (great for plotting and summarising)pivot_wider() does the reverse (great for display)separate() splits a column with multiple values per cell into several columns|> to keep your code readableTidying is a conceptual skill — practice with real datasets is the only way to get fluent.