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

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    85

2️⃣ 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  85

3️⃣ 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   250

Your 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          130

Mini-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 here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — tidyr

TaskR Syntax
Wide → longpivot_longer(df, cols, names_to, values_to)
Long → widepivot_wider(df, names_from, values_from)
Split a columnseparate(df, col, into, sep)
Drop missing rowsdrop_na(df, col)
Load tidyrlibrary(tidyr)
Pipe stepsdf |> step1() |> step2()

🎉 Lesson Complete!

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

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.