purrr list_rbind() in R: Row-Bind a List of Data Frames
purrr list_rbind() row-binds a list of data frames into a single data frame, stacking each element top to bottom. It is the modern, type-safe building block behind the purrr 1.0 replacement for map_dfr().
list_rbind(dfs) # row-bind a list of data frames list_rbind(dfs, names_to = "source") # keep list names in a column map(x, f) |> list_rbind() # apply f, then stack the results list_rbind(list(df1, NULL, df2)) # NULL elements are skipped split(df, df$g) |> map(fn) |> list_rbind() # split then recombine list_cbind(dfs) # column-bind a list instead
Need explanation? Read on for examples and pitfalls.
What list_rbind() does
list_rbind() stacks a list of data frames into one. It takes a single list whose elements are data frames and binds them row-wise into one combined data frame. Columns are matched by name, so the input frames can carry their columns in any order. The function is built on vctrs::vec_rbind() and arrived with purrr 1.0.0 in late 2022.
The job it does is the back half of split-apply-combine work. You split data into pieces, transform each piece, and list_rbind() reassembles the pieces into one tidy table. It also stacks frames read from many files into a single result.
list_rbind() syntax and arguments
The function takes one list plus two optional arguments. Here is the full call shape.
x: a list. Every element must be a data frame orNULL. AnyNULLelement is silently dropped before binding....: reserved for future extensions and must stay empty. Passing anything here raises an error.names_to: optional string. When supplied, the names ofxare saved into a new column with that name. The default discards the names.ptype: optional prototype data frame. It forces the output column types, which is useful when you need a stable schema even ifxis empty.
The contract is strict on purpose: list_rbind() only ever accepts a list of data frames, never loose frames passed as separate arguments. That single-list rule is what makes it compose cleanly after map().
list_rbind is not found, your purrr is pre-1.0. Run install.packages("purrr") to update. The function also has a column-binding twin, list_cbind(), and a vector version, list_c().list_rbind() examples
Each example feeds list_rbind() a list and gets one frame back. Start with two small quarterly tables collected in a named list.
The result has four rows and the list names are gone. To keep track of which rows came from which element, pass names_to and the names become a real column.
The most common use is after map(). Split mtcars by cylinder count, pick the most efficient car in each group, and list_rbind() stacks the three one-row results.
When the input frames do not share the same columns, list_rbind() keeps every column and fills the gaps with NA rather than failing.
map_dfr() welded iteration and row-binding into one call. purrr 1.0 split them back apart so each step is visible: map() does the work, list_rbind() does the combine. Seeing the two stages separately makes pipelines easier to debug.list_rbind() vs other row-binding tools
Pick the binder that matches what you already hold. list_rbind() is purpose-built for a list, while bind_rows() is more flexible and do.call(rbind, ...) is pure base R.
| Approach | Input | Needs a package | Notes |
|---|---|---|---|
list_rbind(x) |
one list of data frames | purrr | pairs with map(), has names_to |
bind_rows(...) |
frames or a list | dplyr | flexible, uses .id for names |
do.call(rbind, x) |
one list of data frames | base R | no NA-fill for new columns |
map_dfr(x, f) |
a vector to map over | purrr + dplyr | superseded, avoid in new code |
The decision rule is short. If you have a list and want it stacked, reach for list_rbind(); it is the natural end of a map() pipeline and drops the dplyr dependency. Use dplyr::bind_rows() when you have loose frames as separate arguments or want its .id shortcut. Keep do.call(rbind, ...) for base-only scripts, but remember it errors when columns differ instead of filling NA.
Common pitfalls
Most list_rbind() errors come from what is inside the list. Three mistakes cover almost all of them.
The first is putting a non-data-frame element in the list. list_rbind() accepts only data frames and NULL, so a stray vector stops the call.
The fix is to make sure every element is a data frame, or use list_c() if you actually meant to combine vectors. The second mistake is passing frames as separate arguments, as in list_rbind(df_a, df_b). That fails because the extra frame lands in the reserved .... Wrap them in list() first. The third is forgetting names_to, which silently drops the list names and leaves an output table you cannot trace back to its source.
map_dfr() is superseded as of purrr 1.0.0. It still runs, but the recommended pattern is map(x, f) |> list_rbind(). The newer form removes the hard dplyr dependency and keeps the mapping and binding steps separate.Try it yourself
Try it: Split airquality by Month, take the first 2 rows of each month with head(), and row-bind them back with list_rbind(), keeping the month label in a column called month. Save the result to ex_air.
Click to reveal solution
Explanation: split() makes a named list of monthly data frames, map() trims each to two rows, and list_rbind(names_to = "month") stacks the five pieces into a 10-row frame while saving each list name into the month column.
Related purrr functions
These functions sit next to list_rbind() in everyday purrr work:
list_cbind(): column-binds a list of data frames instead of row-binding them.list_c(): combines a list of vectors into one vector, the atomic counterpart.map(): the base iterator that produces the listlist_rbind()consumes.map_dfr(): the superseded map-and-row-bind shortcut thatlist_rbind()replaces.list_flatten(): removes one level of nesting from a list before you bind it.
For the full argument reference, see the official purrr list-combine documentation.
FAQ
What is the difference between list_rbind() and bind_rows()?
Both stack data frames row-wise, but they expect different inputs. list_rbind() takes exactly one list of data frames and nothing else, which makes it a clean fit at the end of a map() pipeline. dplyr::bind_rows() is looser: it accepts frames passed as separate arguments or as a list. list_rbind() also avoids a dplyr dependency, since it runs on vctrs.
Does list_rbind() handle data frames with different columns?
Yes. When the input frames do not share the same columns, list_rbind() keeps the union of all columns and fills the missing cells with NA. Column matching is by name, not by position, so frames can list their columns in any order. This is the same outer-join behavior that vctrs::vec_rbind() provides under the hood.
Is map_dfr() deprecated in favor of list_rbind()?
map_dfr() is superseded, not deprecated. It still works and receives critical fixes, but the tidyverse now recommends map(x, f) |> list_rbind() for new code. The newer pattern separates the mapping step from the binding step and drops the required dplyr dependency. Existing scripts using map_dfr() are safe and need no urgent rewrite.
How do I keep the names of the list in the output?
Pass the names_to argument with a column name, such as list_rbind(x, names_to = "source"). The names of the list x are then written into a new column called source, with one value per row identifying which element it came from. Without names_to, the list names are discarded and the output has no source column.
What package and version do I need for list_rbind()?
list_rbind() lives in the purrr package and was introduced in purrr 1.0.0, released in December 2022. If R reports that it cannot find list_rbind, your purrr is older than 1.0. Run install.packages("purrr") to update. The companion functions list_cbind() and list_c() shipped in the same release.