tidyr unnest() in R: Flatten List Columns Into Rows

The unnest() function in tidyr flattens list-columns of tibbles into multiple rows, expanding each cell into its constituent rows. It is the opposite of nest().

⚡ Quick Answer
df |> unnest(data)                      # flatten the data list column
df |> unnest(c(col1, col2))              # multiple list cols
df |> unnest(data, keep_empty = TRUE)   # keep empty cells as NA rows
df |> unnest_longer(col)                 # for vector list cols
df |> unnest_wider(col)                  # spread named list cols to columns

Need explanation? Read on for examples and pitfalls.

📊 Is unnest() the right tool?
STARTlist column of tibbles -> rowsunnest(col)list column of vectors -> rowsunnest_longer(col)list column of named lists -> columnsunnest_wider(col)preserve empty cellskeep_empty = TRUEper-row keep raw matchesnest_join (different)

What unnest() does in one sentence

unnest(data, cols) expands list-columns where each cell is a tibble (or vector) into multiple rows; other columns are duplicated. It reverses nest().

Syntax

unnest(data, cols, ..., keep_empty = FALSE, ptype = NULL, names_sep = NULL). cols are the list columns to unnest.

Run live
Run live, no install needed. Every R block on this page runs in your browser. Click Run, edit the code, re-run instantly. No setup.
RRound-trip nest then unnest
library(tidyr) library(dplyr) library(broom) library(purrr) # nest creates list column: nested <- mtcars |> nest(.by = cyl) # unnest flattens it back: flat <- nested |> unnest(data) nrow(flat) #> [1] 32 (matches original mtcars rows)

  
Tip
Use unnest() for tibbles in list columns; unnest_longer() for vectors; unnest_wider() for named lists turning into columns.

Five common patterns

1. Round-trip with nest

Rnest then unnest = original
mtcars |> nest(.by = cyl) |> unnest(data)

  

2. Many-models extract

RUnnest fitted summaries
mtcars |> nest(.by = cyl) |> mutate(glanced = purrr::map(data, ~ broom::glance(lm(mpg ~ wt, .x)))) |> unnest(glanced) |> select(cyl, r.squared, p.value)

  

3. Keep empty cells

REmpty list cells become NA rows
df <- tibble(id = 1:2, x = list(c(1,2), integer(0))) df |> unnest(x, keep_empty = TRUE) #> id x #> 1 1 1 #> 2 1 2 #> 3 2 NA <-- empty cell preserved as NA

  

4. Multiple list columns

RUnnest two parallel list cols
df |> unnest(c(a, b))

  

Both must have the same length per cell.

5. Nest then transform

RPer-group transformation then flatten
df |> nest(.by = g) |> mutate(data = map(data, ~ mutate(.x, scaled = scale(value)))) |> unnest(data)

  
Key Insight
unnest() family has 3 variants for different list-column shapes: tibbles, vectors, named lists. unnest = tibbles, unnest_longer = vectors, unnest_wider = named lists to columns. Pick by what's in the list column.

unnest() vs unnest_longer() vs unnest_wider()

Function List column contains Output
unnest() Tibbles / data frames Multiple rows
unnest_longer() Atomic vectors Multiple rows
unnest_wider() Named lists Multiple columns

When to use which:

  • unnest for nested data frames (most common after nest()).
  • unnest_longer for vector list columns (e.g., from str_split).
  • unnest_wider for named list columns (e.g., from JSON).

A practical workflow

The "many-models extract" pattern is unnest's most common use.

RInteractive R
result <- df |> nest(.by = group) |> mutate(model = map(data, ~ lm(y ~ x, data = .x)), tidied = map(model, broom::tidy)) |> unnest(tidied)

  

Per-group model coefficients in long format.

Common pitfalls

Pitfall 1: forgetting keep_empty. Empty list cells (length 0) are silently dropped. Use keep_empty = TRUE to preserve them as NA rows.

Pitfall 2: list column with mixed shapes. unnest expects each cell to be the same type (all tibbles or all vectors). Mixed types may error.

Warning
unnest() can produce HUGE output for large nested tibbles. A single nested tibble of 1M rows unnested becomes 1M output rows. Check sizes before running.

Try it yourself

Try it: Nest mtcars by cyl, fit lm(mpg ~ wt) per group, then unnest the tidy coefficients. Save to ex_coef.

RYour turn: many models tidy
ex_coef <- mtcars |> # your code here names(ex_coef) #> Expected: cyl + tidy columns (term, estimate, std.error, statistic, p.value)

  
Click to reveal solution
RSolution
ex_coef <- mtcars |> nest(.by = cyl) |> mutate(tidied = purrr::map(data, ~ broom::tidy(lm(mpg ~ wt, .x)))) |> unnest(tidied) |> select(-data) head(ex_coef) #> # A tibble: 6 x 6 #> cyl term estimate std.error statistic p.value #> ...

  

Explanation: nest by cyl, fit lm, tidy each model, unnest the tidied coefficients.

After mastering unnest, look at:

  • unnest_longer(): vectors to rows
  • unnest_wider(): named lists to columns
  • nest(): opposite (collapse to list column)
  • hoist(): extract specific elements from list cols
  • purrr::map(): per-cell transformation

FAQ

What does unnest do in tidyr?

unnest(data, cols) flattens list-columns where each cell is a tibble (or vector) into multiple rows. Reverses nest().

What is the difference between unnest and unnest_longer?

unnest expects each list cell to be a TIBBLE (data frame). unnest_longer expects each cell to be an atomic VECTOR. Different input shapes.

How do I keep empty list cells with unnest?

Pass keep_empty = TRUE. Empty cells become NA rows.

Does unnest preserve other columns?

Yes. Other columns are duplicated to match the unnested rows.

What is the difference between unnest and unite?

unnest expands list-columns into rows. unite combines multiple columns into one. Different operations.