purrr transpose() in R: Restructure Nested Lists by Index
purrr transpose() in R flips a list of lists inside-out, turning a list of records into a list of fields indexed by name or position.
transpose(records) # flip list inside-out transpose(records)$score # pull one field across records transpose(records, .names = c("a","b")) # set output names explicitly transpose(records)$score |> unlist() # collapse a field to a vector transpose(safely_results) # split result/error pairs list_transpose(records) # modern purrr 1.0 replacement
Need explanation? Read on for examples and pitfalls.
What transpose() does
transpose() turns a list of lists "inside-out". You pass a list where every element is itself a list of the same fields, and it returns a new list keyed by those fields. A list of three person records becomes a list with one name element and one score element, each holding three values.
This is the standard way to pull one component out of many similar nested structures at once. It pairs naturally with the output of map(), which often returns a list of small lists you then want to regroup by field.
transpose(transpose(x)) returns the original list. Think of it as rotating a table 90 degrees: rows become columns, and rotating twice lands you back where you started.Syntax
transpose() takes just two arguments. The function signature is short, and only the first is required.
.lis the list to transpose. Every element should be a list with the same fields, named consistently..namesis an optional character vector of names for the transposed output. WhenNULL, names are taken from the first element of.l.
Matching is by name when the first element is named, and by position otherwise. That rule is the source of most surprises, so it is worth keeping in mind.
Examples by use case
Every example below builds on the same record list. Start by loading purrr and building a small list of records to work with.
The most common use is flipping that list so each field becomes its own element. The result has two top-level names, name and score, taken from the first record.
Each field comes back as a list, not a vector. To get a plain numeric vector, pull the field and pass it through unlist().
When the inner lists are unnamed, transpose uses integer positions. Supply .names to label the output instead of relying on [[1]], [[2]].
A classic real-world use is splitting the output of safely(). Mapping a safe function over inputs gives a list of {result, error} pairs, and one transpose() separates them cleanly.
transpose() vs list_transpose()
purrr 1.0.0 superseded transpose() with list_transpose(). The older function still works and is not deprecated, but list_transpose() is the recommended choice for new code. The key difference is that list_transpose() simplifies its output to vectors automatically when it can.
| Feature | transpose() |
list_transpose() |
|---|---|---|
| Status | Superseded (still works) | Recommended since purrr 1.0.0 |
| Output | Always a list of lists | Simplifies to vectors when possible |
| Control | .names only |
simplify, template, default |
| Best for | Maintaining older code | New code, cleaner results |
Use transpose() when you maintain an existing codebase that already depends on it. Reach for list_transpose() in anything new, since it removes the manual unlist() step.
Common pitfalls
A missing field silently becomes NULL. If one record lacks a field that others have, transpose does not error. It inserts NULL in that slot, which can break a later unlist() or arithmetic step.
The second frequent mistake is forgetting that transpose() returns lists. Doing mean(transpose(records)$score) fails because the field is a list, not a numeric vector. Pipe it through unlist() first, or switch to list_transpose(). The third is confusing it with t(), the base R matrix transpose, which is unrelated and works on matrices and data frames, not nested lists.
zip(*records) on a list of dictionaries: it regroups parallel records by key. The named output mirrors a dictionary of lists.Related purrr functions
These functions are commonly used alongside transpose() when reshaping nested data:
- map() builds the list of records that transpose then regroups.
- pluck() reaches into a single deep element instead of reshaping the whole list.
- flatten() removes one level of nesting without flipping the structure.
- list_modify() edits elements of a list in place.
- safely() produces the result/error pairs that transpose splits apart.
Try it yourself
Try it: Transpose the people list below and compute the mean age across all three records. Save the mean to ex_mean_age.
Click to reveal solution
Explanation: transpose() regroups the records so $age holds all three ages as a list. unlist() turns that list into a numeric vector, and mean() averages it.
FAQ
What is the difference between transpose() and list_transpose() in purrr?
Both flip a list of lists inside-out. transpose() always returns a list of lists, so you must call unlist() to get vectors. list_transpose(), added in purrr 1.0.0, simplifies its output to atomic vectors when possible and adds simplify, template, and default arguments for finer control. transpose() is superseded but not removed, so existing code keeps working while new code should use list_transpose().
Is purrr transpose() deprecated?
No, it is superseded, which is a softer status. Superseded functions are not recommended for new code but receive no warnings and are not scheduled for removal. Any script that already uses transpose() will keep running. For fresh work, prefer list_transpose(), which produces cleaner output with fewer manual steps.
How do I transpose a list of lists in R without purrr?
Base R has no direct equivalent, but you can combine lapply() with setNames(): lapply(names(records[[1]]), function(f) lapply(records, \[[\, f)). This loops over each field name and extracts that field from every record. It works but is verbose, which is exactly the boilerplate transpose() removes.
Can transpose() handle lists of different lengths?
It runs without error, but the result is unreliable. transpose() reads field names from the first element only. Records missing a field get NULL in that position, and records with extra fields have those fields dropped. Make every record consistent before transposing, or you will get silent data loss.
How is transpose() different from t() in R?
They are unrelated despite the similar name. t() is base R and transposes a matrix or data frame, swapping its rows and columns. transpose() is from purrr and reshapes a nested list, regrouping records by field. Use t() for rectangular data and transpose() for lists of lists.
For the official reference, see the purrr transpose() documentation.