tidyr unnest_longer() in R: Expand Vector List Columns Into Rows
The unnest_longer() function in tidyr expands a list-column where each cell is an ATOMIC VECTOR into multiple rows, one per element. It is the row-wise unnest specialized for vectors.
df |> unnest_longer(col) # one row per element df |> unnest_longer(col, indices_to = "i") # add 1, 2, ... index per row df |> unnest_longer(col, values_to = "v") # custom value column name df |> unnest(col) # different: for tibble lists df |> unnest_wider(col) # different: spread to columns
Need explanation? Read on for examples and pitfalls.
What unnest_longer() does in one sentence
unnest_longer(data, col, indices_to = NULL, values_to = NULL) expands a list-column of atomic vectors into multiple rows; each element of each vector becomes its own row. Other columns are duplicated.
Syntax
unnest_longer(data, col, ..., indices_to = NULL, values_to = NULL, keep_empty = FALSE).
unnest_longer() with indices_to = "pos" to track which element each row came from. Useful for time-series-like data inside list columns.Five common patterns
1. Standard vector unnest
2. With position index
3. From strsplit results
(For this specific case, separate_longer_delim is cleaner.)
4. Custom column name
5. Empty cells
unnest_longer is for VECTOR list columns; unnest is for TIBBLE list columns. They look similar but expect different cell types. The right one to call depends on what's IN the list column.unnest_longer() vs unnest() vs unnest_wider()
| Function | List column contains | Output |
|---|---|---|
unnest() |
Tibbles | Rows |
unnest_longer() |
Atomic vectors | Rows |
unnest_wider() |
Named lists | Columns |
When to use which:
- unnest for nested data frames.
- unnest_longer for vectors (each element a row).
- unnest_wider for named lists (each name a column).
A practical workflow
Use unnest_longer for tag-list columns or time-series-like list cells.
Each tag becomes a row; tag_position tracks where it appeared in the original.
Common pitfalls
Pitfall 1: confusing with separate_longer_delim. For string columns with delimiters, separate_longer_delim is more direct (no list column needed first).
Pitfall 2: empty list cells dropped silently. Use keep_empty = TRUE to preserve as NA rows.
unnest_longer() can produce huge output if list columns have large vectors. A list column of 1000-element vectors with 1000 rows = 1M output rows.Try it yourself
Try it: Expand a list column of numeric vectors with position indices. Save to ex_long.
Click to reveal solution
Explanation: Each vector element becomes a row; pos tracks its index within the original vector.
Related tidyr functions
After mastering unnest_longer, look at:
unnest(): for tibble list columnsunnest_wider(): named lists to columnsseparate_longer_delim(): split string column into rowspurrr::map(): per-cell transformationnest(): opposite (collapse to list column)
FAQ
What does unnest_longer do in tidyr?
unnest_longer(data, col) expands a list column of atomic vectors into rows; each element becomes its own row. Other columns are duplicated.
What is the difference between unnest_longer and unnest?
unnest_longer expects vectors in cells. unnest expects tibbles. Different input shapes.
How do I track position with unnest_longer?
Pass indices_to = "position" to add a position column with 1, 2, 3, ... per row indicating where each element came from in the original vector.
Does unnest_longer drop empty cells?
Yes by default. Pass keep_empty = TRUE to preserve them as NA rows.
What is the difference between unnest_longer and separate_longer_delim?
unnest_longer expects a list-column of vectors already. separate_longer_delim splits a string column directly. For strings, separate_longer_delim is one step shorter.