tidyr & Pipes
Reviewed & published by Brayan K
tidyr is a tidyverse package for reshaping data into "tidy" form — one variable per column, one observation per row — which is the shape that dplyr, ggplot2, and models expect.
Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll reshape data between wide and long with pivot_longer() and pivot_wider(), clean it with separate() and drop_na(), and chain tidyr and dplyr together with the pipe.
What You'll Learn in This Lesson
- What 'tidy data' means and why it matters
- How to reshape wide to long with pivot_longer()
- How to reshape long to wide with pivot_wider()
- How to split a column with separate()
- How to drop missing rows with drop_na()
- How to chain tidyr and dplyr with the pipe
1️⃣ Wide to Long with pivot_longer()
When several columns really represent the same variable (subjects, months, quarters), pivot_longer() stacks them into two tidy columns: one for the old names, one for the values.
library(tidyr)
# "Wide" data: one row per student, a column per subject.
wide <- data.frame(
student = c("Ada", "Bob"),
math = c(90, 70),
art = c(60, 85)
)
# pivot_longer() reshapes wide -> long: one row per observation.
long <- pivot_longer(wide,
cols = c(math, art),
names_to = "subject",
values_to = "score")
long
# student subject score
# 1 Ada math 90
# 2 Ada art 60
# 3 Bob math 70
# 4 Bob art 852️⃣ Long to Wide with pivot_wider()
pivot_wider() is the inverse — it spreads a name column and a value column out into multiple columns. Handy for building presentation tables from tidy data.
library(tidyr)
long <- data.frame(
student = c("Ada", "Ada", "Bob", "Bob"),
subject = c("math", "art", "math", "art"),
score = c(90, 60, 70, 85)
)
# pivot_wider() reshapes long -> wide: spread a column into many columns.
wide <- pivot_wider(long,
names_from = subject,
values_from = score)
wide
# student math art
# 1 Ada 90 60
# 2 Bob 70 853️⃣ Cleaning in a Pipeline
Because tidyr and dplyr share the pipe, you can clean and reshape in one flow. drop_na() removes missing rows and separate() splits a packed column into several.
library(tidyr)
library(dplyr)
# A messy frame with a missing value and a combined column.
df <- data.frame(
id = c(1, 2, 3),
city_country = c("Paris_FR", "Berlin_DE", "Rome_IT"),
sales = c(100, NA, 250)
)
df |>
drop_na(sales) |> # remove rows with NA sales
separate(city_country, # split one column into two
into = c("city", "country"),
sep = "_")
# id city country sales
# 1 1 Paris FR 100
# 2 3 Rome IT 250Your turn. Fill in the ___ blank, run it, and compare with the expected output.
library(tidyr)
# 🎯 YOUR TURN — replace each ___ then run it.
# Quarterly revenue in WIDE form:
wide <- data.frame(
region = c("North", "South"),
q1 = c(100, 120),
q2 = c(150, 130)
)
# 1) Pivot it to LONG form: one row per region-quarter
long <- pivot_longer(wide,
cols = c(q1, q2),
names_to = "quarter",
values_to = ___) # 👉 replace ___ with "revenue"
print(long)
# ✅ Expected output:
# # A tibble: 4 × 3
# region quarter revenue
# <chr> <chr> <dbl>
# 1 North q1 100
# 2 North q2 150
# 3 South q1 120
# 4 South q2 130Mini-Challenge: Tidy a Survey
Write the pipeline from the outline, run it, and check it against the example output. Reshaping then filtering is the everyday tidyverse rhythm.
# 🎯 MINI-CHALLENGE: tidy a survey.
#
# Start from this WIDE data frame:
# survey <- data.frame(
# person = c("A", "B"),
# mon = c(8, 6),
# tue = c(7, 9),
# wed = c(5, 8)
# )
#
# 1. Pivot longer: cols mon, tue, wed -> names_to = "day",
# values_to = "hours".
# 2. With dplyr, filter() to keep hours >= 7.
# 3. Print the result.
#
# ✅ Example output (a tibble): person day hours, rows where hours >= 7
# your code hereCommon Errors (and the fix)
- "could not find function 'pivot_longer'" — load tidyr with library(tidyr) (it's not base R).
- Wrong cols selection — list the columns to stack, e.g. cols = c(q1, q2) or cols = -region (everything but region).
- separate() warning about pieces — the separator didn't split into the expected number of parts. Check the sep and the into length.
- pivot_wider() makes list columns — duplicate identifiers can't spread cleanly. Aggregate first (e.g. with summarise()) so each cell has one value.
Pro Tips
- 💡 Tidy first: get data into long form early — most tidyverse and plotting steps assume it.
- 💡 Select helpers in cols: starts_with("q") grabs all quarter columns at once.
- 💡 tibbles print nicely: the type row (<chr>, <dbl>) tells you each column's type at a glance.
📋 Quick Reference — tidyr
| Task | R Syntax |
|---|---|
| Wide → long | pivot_longer(df, cols, names_to, values_to) |
| Long → wide | pivot_wider(df, names_from, values_from) |
| Split a column | separate(df, col, into, sep) |
| Drop missing rows | drop_na(df, col) |
| Load tidyr | library(tidyr) |
| Pipe steps | df |> step1() |> step2() |
🎉 Lesson Complete!
- ✅ Tidy data = one variable per column, one observation per row
- ✅ pivot_longer() reshapes wide to long
- ✅ pivot_wider() reshapes long to wide
- ✅ separate() and drop_na() clean data
- ✅ The pipe joins tidyr and dplyr into one flow
Practice quiz
Which package provides pivot_longer() and pivot_wider()?
- base R
- tidyr
- ggplot2
- stringr
Answer: tidyr. pivot_longer() and pivot_wider() come from the tidyr package.
What does pivot_longer() do?
- Deletes columns
- Sorts rows
- Stacks several columns into longer, tidy form
- Spreads one column into many
Answer: Stacks several columns into longer, tidy form. pivot_longer() reshapes wide data into long, tidy form.
In pivot_longer(), what does names_to specify?
- The values column
- The rows to keep
- The file name
- The new column that holds the old column names
Answer: The new column that holds the old column names. names_to names the new column receiving the former column names.
What does pivot_wider() do?
- Spreads a name column and value column into multiple columns
- Stacks columns into two
- Filters rows
- Removes NA
Answer: Spreads a name column and value column into multiple columns. pivot_wider() is the inverse of pivot_longer(): long to wide.
Which arguments does pivot_wider() use to spread data?
- cols and into
- names_from and values_from
- sep and remove
- by and on
Answer: names_from and values_from. pivot_wider() takes names_from and values_from.
What does separate() do?
- Drops rows
- Renames a frame
- Splits one column into several using a separator
- Joins two frames
Answer: Splits one column into several using a separator. separate() splits a packed column (e.g. 'Paris_FR') into multiple columns.
What does drop_na() do?
- Fills NA with 0
- Replaces NA with the mean
- Counts NA values
- Removes rows that contain missing values
Answer: Removes rows that contain missing values. drop_na() removes rows with NA, optionally only in named columns.
What does 'tidy data' mean?
- Each variable a column, each observation a row
- All numeric columns
- No missing values allowed
- Data stored in wide form
Answer: Each variable a column, each observation a row. Tidy data: one variable per column, one observation per row.
How do tidyr and dplyr work together cleanly?
- They cannot be combined
- Only via for-loops
- Through the pipe, passing a data frame between steps
- By exporting to CSV
Answer: Through the pipe, passing a data frame between steps. Each takes a data frame first and returns one, so the pipe chains them.
tidyr functions like pivot_longer() return what kind of object?
- A matrix
- A tibble
- A plain vector
- A list of lists
Answer: A tibble. tidyr returns tibbles, which print with a type row under each column.
Continue this course
- Previous: dplyr Basics
- Next: Strings & Dates — Clean text and work with real dates and times
- Quick reference: R cheat sheet
Frequently asked questions
What does it mean for data to be 'tidy'?
Tidy data follows three rules: each variable is a column, each observation is a row, and each type of observational unit forms a table. In practice this means 'long' format — for example, instead of separate columns for January, February, March sales, you have one 'month' column and one 'sales' column. Tidy data is the format dplyr, ggplot2, and most modeling functions expect, so reshaping into tidy form is often the first real step of an analysis.
What's the difference between pivot_longer() and pivot_wider()?
pivot_longer() takes several columns and stacks them into two: one holding the old column names and one holding the values — turning wide data into long, tidy form. pivot_wider() does the reverse, spreading a name column and a value column back out into multiple columns — useful for presentation tables or when a function needs wide input. You'll mostly pivot longer to tidy raw data, and occasionally pivot wider to format results.
How does the pipe make tidyr and dplyr work together?
tidyr and dplyr are both part of the tidyverse and share the same pipe-friendly design. You can chain them: pivot your data with tidyr, then immediately filter, mutate, and summarise with dplyr — all in one readable pipeline. Because each function takes a data frame as its first argument and returns one, the pipe carries the data from a tidyr reshape straight into a dplyr transformation without intermediate variables.
What do separate() and drop_na() do?
separate() splits a single column into several based on a separator — turning 'Paris_FR' into a city column 'Paris' and a country column 'FR'. drop_na() removes rows that contain missing values, either across all columns or just the ones you name. Both are common cleaning steps: real datasets often cram multiple values into one field or contain gaps, and these functions get the data into shape before analysis.