Nesting and Unnesting Data Frames
Use nest() and unnest() to work with list-columns in tidy workflows.
Nesting and Unnesting Data Frames is a free R Academy lesson on CoddyKit. This is lesson 4 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, and your progress syncs across the web and the CoddyKit app. The R Academy course includes 4 lessons in total.
List-Columns and Nested Data
A list-column is a column in a data frame where each cell contains an R object (a vector, data frame, model, or list). This makes it possible to store structured sub-data alongside other columns, enabling powerful group-wise workflows.
library(tidyr)
library(dplyr)
# A simple data frame with a list-column
df <- data.frame(
group = c('A','B','C'),
n = c(3, 2, 4)
)
df$values <- list(c(1,2,3), c(4,5), c(6,7,8,9))
print(df)
print(df$values[[1]]) # Access the first list elementnest() — Creating Nested Data Frames
nest(df, data = c(col1, col2)) groups rows by the non-nested columns and packs the specified columns into a list-column of data frames. One row per group, each containing a mini data frame.
library(tidyr)
library(dplyr)
df <- data.frame(
region = c('East','East','East','West','West'),
month = c(1,2,3,1,2),
sales = c(100,120,110,200,190)
)
nested <- df %>%
nest(data = c(month, sales))
print(nested)
print(nested$data[[1]]) # East region's datanest() with group_by()
The most common pattern: use group_by() then nest() to create one row per group, with all remaining columns packed into a data list-column. Equivalent to nest(.by = group_col) in newer tidyr versions.
library(tidyr)
library(dplyr)
df <- data.frame(
category = c('A','A','A','B','B','C','C','C','C'),
x = c(1,2,3,4,5,6,7,8,9),
y = c(2,4,3,8,7,5,6,9,8)
)
nested <- df %>%
group_by(category) %>%
nest()
print(nested)
cat('Rows per category B:', nrow(nested$data[[2]]))map() with Nested Data Frames
Once data is nested, use purrr::map() to apply a function to each mini data frame. The function receives one data frame at a time, and the results become a new list-column — often a fitted model.
library(tidyr)
library(dplyr)
library(purrr)
df <- data.frame(
group = c('A','A','A','B','B','B'),
x = c(1,2,3,1,2,3),
y = c(2.1,4.0,5.9,3.2,5.8,9.1)
)
models <- df %>%
group_by(group) %>%
nest() %>%
mutate(model = map(data, ~lm(y ~ x, data = .x)))
print(models)Extracting Model Results with map()
After fitting models per group, use map() with functions like broom::tidy() or coef() to extract results from each model. The output is another list-column that can later be unnested.
library(tidyr)
library(dplyr)
library(purrr)
df <- data.frame(
group = c('A','A','A','B','B','B'),
x = c(1,2,3,1,2,3),
y = c(2.1,4.0,5.9,3.2,5.8,9.1)
)
df %>%
group_by(group) %>%
nest() %>%
mutate(
model = map(data, ~lm(y ~ x, data = .x)),
r_squared = map_dbl(model, ~summary(.x)$r.squared),
intercept = map_dbl(model, ~coef(.x)[1]),
slope = map_dbl(model, ~coef(.x)[2])
) %>%
select(group, r_squared, intercept, slope)unnest() — Expanding List-Columns
unnest(df, cols = data) is the inverse of nest(). It expands the list-column back into regular columns, repeating the id columns as needed. Use cols to specify which list-column to unnest.
library(tidyr)
library(dplyr)
nested <- data.frame(
region = c('East','West')
)
nested$data <- list(
data.frame(month=1:3, sales=c(100,120,110)),
data.frame(month=1:2, sales=c(200,190))
)
unnested <- unnest(nested, cols = data)
print(unnested)unnest_longer() — Expand a List to Rows
unnest_longer(df, col) expands a list-column so each element of the list becomes a row. Unlike unnest() which expects data frames, unnest_longer() works with lists of vectors or scalars.
library(tidyr)
df <- data.frame(
person = c('Alice','Bob','Carol')
)
df$skills <- list(
c('R','Python','SQL'),
c('Excel','Tableau'),
c('R','Julia','Stan','BUGS')
)
unnest_longer(df, skills)unnest_wider() — Expand a List to Columns
unnest_wider(df, col) expands each list element into a set of new columns. The list must have named elements — the names become column names. Useful for nested JSON-like structures.
library(tidyr)
df <- data.frame(id = 1:3)
df$info <- list(
list(name='Alice', age=30, city='NYC'),
list(name='Bob', age=25, city='LA'),
list(name='Carol', age=35, city='Chicago')
)
unnest_wider(df, info)Full Nested Workflow Pattern
The complete nested data frame workflow: group_by() + nest() → apply functions with map() → extract results → unnest() to get back a flat data frame. This pattern enables running any analysis per group.
library(tidyr)
library(dplyr)
library(purrr)
df <- data.frame(
region = c('East','East','East','West','West','West'),
month = c(1,2,3,1,2,3),
revenue = c(100,120,115,200,195,210)
)
df %>%
group_by(region) %>%
nest() %>%
mutate(
mean_rev = map_dbl(data, ~mean(.x$revenue)),
trend = map_dbl(data, ~coef(lm(revenue~month, data=.x))[2])
) %>%
select(region, mean_rev, trend)Nesting Multiple Columns
You can be selective about which columns go into the nested data frame. Specify them explicitly in nest(data = c(...)). Columns not specified stay as regular columns in the outer data frame, alongside the list-column.
library(tidyr)
library(dplyr)
df <- data.frame(
region = c('East','East','West','West'),
year = c(2023,2024,2023,2024),
q1 = c(100,110,200,210),
q2 = c(105,115,195,215),
target = c(105,112,198,212)
)
# Only nest quarterly data, keep target as outer column
nested <- df %>%
group_by(region, year) %>%
nest(quarters = c(q1, q2))
print(nested)When to Use Nested Data Frames
Nested data frames excel when you need to: fit the same model across groups, run per-group simulations, apply complex transformations to subsets, or work with JSON-like hierarchical data. They keep group-specific data organized alongside group-level metadata.
library(tidyr)
library(dplyr)
library(purrr)
# Bootstrap confidence interval per group
df <- data.frame(
group = c('A','A','A','A','B','B','B','B'),
value = c(10,12,11,13,20,22,19,21)
)
set.seed(42)
df %>%
group_by(group) %>%
nest() %>%
mutate(
boot_means = map(data, function(d) {
replicate(100, mean(sample(d$value, replace=TRUE)))
}),
ci_low = map_dbl(boot_means, ~quantile(.x, 0.025)),
ci_high = map_dbl(boot_means, ~quantile(.x, 0.975))
) %>%
select(group, ci_low, ci_high)Quick Check
What is the correct way to expand a list-column of data frames back into a flat data frame?
Recap: Nesting and Unnesting
Key takeaways for nested data frame workflows:
nest(data = c(cols))— packs columns into a list-column of data frames, one per groupgroup_by() + nest()— the standard pattern to nest by groupmap(nested$data, fn)— apply any function to each nested data frameunnest(cols = data)— expand list-column of data frames back to flat formatunnest_longer(col)— expand a list of vectors/scalars to rowsunnest_wider(col)— expand a named list to columns- Ideal for per-group modeling, bootstrapping, and JSON data processing
library(tidyr)
library(dplyr)
library(purrr)
data.frame(
team = c('A','A','B','B'),
x = c(1,2,1,2),
y = c(2,4,3,6)
) %>%
group_by(team) %>%
nest() %>%
mutate(
model = map(data, ~lm(y ~ x, data = .x)),
slope = map_dbl(model, ~coef(.x)['x'])
) %>%
select(team, slope)Learn R with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 43
- Lessons
- 159
Frequently Asked Questions
Is the “Nesting and Unnesting Data Frames” lesson free?
Yes — any 3 lessons of the R Academy learning path, including “Nesting and Unnesting Data Frames”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The R Academy course includes 4 lessons in total.
What will I learn in “Nesting and Unnesting Data Frames”?
Use nest() and unnest() to work with list-columns in tidy workflows. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start R Academy?
No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.
How long does the “Nesting and Unnesting Data Frames” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this R Academy lesson?
Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Wide to Long with pivot_longer()
- Long to Wide with pivot_wider()
- separate() and unite() for String Columns
- Nesting and Unnesting Data Frames