How to pivot data from wide to long format in R

· 3 min read · Updated March 14, 2026 · beginner
r tidyr pivot data-reshaping tidyverse

Pivoting from wide to long format is one of the most common data transformations in R. This cookbook shows you how to use tidyr::pivot_longer() for this task.

Basic Wide to Long

The simplest case: you have columns that should become rows:

library(tidyr)
library(dplyr)

# Sample wide data: one row per person, columns for each survey wave
wide <- tibble(
  id = c(1, 2, 3),
  name = c("Alice", "Bob", "Carol"),
  wave1 = c(10, 20, 15),
  wave2 = c(12, 22, 18),
  wave3 = c(14, 25, 20)
)

# Pivot to long format
long <- wide %>%
  pivot_longer(
    cols = c(wave1, wave2, wave3),
    names_to = "wave",
    values_to = "score"
  )

# Result: 9 rows (3 people x 3 waves)
long
# # A tibble: 9 × 4
#      id name   wave   score
#   <dbl> <chr>  <chr>  <dbl>
# 1     1 Alice  wave1     10
# 2     1 Alice  wave2     12
# 3     1 Alice  wave3     14
# ...

Selecting Columns

Instead of listing every column, use tidyr selection helpers:

# Select columns by name range
df %>% pivot_longer(cols = wave1:wave3, names_to = "wave", values_to = "score")

# Select by prefix
df %>% pivot_longer(cols = starts_with("wave"), names_to = "wave", values_to = "score")

# Select by suffix  
df %>% pivot_longer(cols = ends_with("score"), names_to = "metric", values_to = "value")

# Select by pattern (regex)
df %>% pivot_longer(cols = matches("^q[0-9]"), names_to = "quarter", values_to = "value")

# Select all columns except ID columns
df %>% pivot_longer(cols = -c(id, name), names_to = "var", values_to = "val")

Multiple Value Columns

When your wide data has multiple value types (e.g., revenue and units for each month):

wide <- tibble(
  id = 1:2,
  rev_jan = c(100, 200),
  rev_feb = c(110, 220),
  units_jan = c(10, 20),
  units_feb = c(12, 24)
)

# Use .value in names_to to create separate columns
long <- wide %>%
  pivot_longer(
    cols = c(rev_jan:units_feb),
    names_to = c(".value", "month"),
    names_sep = "_"
  )

long
# # A tibble: 4 × 4
#      id month   rev units
#   <int> <chr> <dbl> <dbl>
# 1     1 jan     100    10
# 2     1 feb     110    12
# 3     2 jan     200    20
# 4     2 feb     220    24

The .value special token tells pivot_longer to split the column names and use the first part as column names.

Handling Missing Values

After pivoting, you might have NA values:

# Create sample data with some missing values
wide <- tibble(
  id = 1:2,
  q1 = c(10, NA),
  q2 = c(20, 25),
  q3 = c(NA, 30)
)

# Drop rows with missing values
long <- wide %>%
  pivot_longer(cols = q1:q3, names_to = "quarter", values_to = "value") %>%
  drop_na(value)

# Or fill missing values
long <- wide %>%
  pivot_longer(cols = q1:q3, names_to = "quarter", values_to = "value") %>%
  fill(value, .direction = "downup")

Column Names as Values

Sometimes you want the column names themselves as data:

wide <- tibble(
  id = 1:2,
  treatment_a = c("yes", "no"),
  treatment_b = c("no", "yes")
)

# Use names_transform to convert character to factor
long <- wide %>%
  pivot_longer(
    cols = starts_with("treatment"),
    names_to = "treatment",
    values_to = "applied",
    names_transform = list(treatment = factor)
  )

Common Options Reference

ArgumentDescriptionExample
colsColumns to pivotcols = c(q1, q2, q3)
names_toName for new key columnnames_to = "quarter"
values_toName for new value columnvalues_to = "sales"
names_sepSeparator for splitting namesnames_sep = "_"
names_prefixRemove prefix from namesnames_prefix = "q_"
values_drop_naDrop rows with NA valuesvalues_drop_na = TRUE

See Also