purrr::map()
map(.x, .f, ...) The map() family of functions from the purrr package provides a consistent, type‑safe way to apply a function to each element of a list or vector. Unlike base R’s lapply() and sapply(), purrr’s map functions enforce predictable output types, offer better error messages, and integrate smoothly with the tidyverse. map() applies a function to a single vector, map2() iterates over two vectors in parallel, and pmap() iterates over an arbitrary number of vectors stored in a list. Together they form the core of purrr’s functional programming toolkit, enabling readable, modular code that avoids explicit loops.
Syntax
map(.x, .f, ...)
map2(.x, .y, .f, ...)
pmap(.l, .f, ...)
The functions also have typed variants that coerce the result to a specific atomic type: map_int(), map_dbl(), map_chr(), map_lgl(), map_dfr(), map_dfc(), etc.
Parameters
map()
| Parameter | Type | Default | Description |
|---|---|---|---|
.x | list, vector, or data frame | Required | The input to iterate over |
.f | function, formula, or atomic vector | Required | The function to apply to each element |
... | any | Optional | Additional arguments passed to .f |
map2()
| Parameter | Type | Default | Description |
|---|---|---|---|
.x | list, vector, or data frame | Required | First input sequence |
.y | list, vector, or data frame | Required | Second input sequence (must be same length as .x) |
.f | function, formula, or atomic vector | Required | Function applied to each pair .x[[i]], .y[[i]] |
... | any | Optional | Additional arguments passed to .f |
pmap()
| Parameter | Type | Default | Description |
|---|---|---|---|
.l | list of lists, vectors, or data frames | Required | A list where each element is a vector of the same length; iteration proceeds over the i‑th elements of each sub‑list |
.f | function, formula, or atomic vector | Required | Function applied to each set of elements across .l |
... | any | Optional | Additional arguments passed to .f |
The .f argument can be:
- A named function (e.g.,
mean) - An anonymous function (e.g.,
function(x) x + 1) - A formula (e.g.,
~ .x + 1), where.x(and.y,.zforpmap) refer to the current element(s) - A character string naming a function (e.g.,
"mean") - An integer, used to extract that element from each input (e.g.,
1extracts the first element)
Examples
Basic map() usage
library(purrr)
# Convert a list of character vectors to their lengths
words <- list("apple", "banana", "cherry")
map(words, nchar)
# [[1]]
# [1] 5
#
# [[2]]
# [1] 6
#
# [[3]]
# [1] 6
# Use a typed variant to get an integer vector directly
map_int(words, nchar)
# [1] 5 6 6
# Apply a custom function with additional arguments
map(words, paste, "-fruit", sep = "")
# [[1]]
# [1] "apple-fruit"
#
# [[2]]
# [1] "banana-fruit"
#
# [[3]]
# [1] "cherry-fruit"
When you need to apply a function to pairs of elements drawn from two vectors of equal length, map2() steps in. Each call receives one element from .x and the corresponding element from .y, making it the natural choice for operations like combining first and last names or multiplying paired measurements.
Parallel iteration with map2()
# Multiply corresponding elements of two vectors
x <- c(1, 2, 3)
y <- c(10, 20, 30)
map2(x, y, ~ .x * .y)
# [[1]]
# [1] 10
#
# [[2]]
# [1] 40
#
# [[3]]
# [1] 90
# Use typed variant to return a numeric vector
map2_dbl(x, y, ~ .x * .y)
# [1] 10 40 90
# Combine strings from two lists
first_names <- c("Alice", "Bob", "Charlie")
last_names <- c("Smith", "Jones", "Brown")
map2_chr(first_names, last_names, paste, sep = " ")
# [1] "Alice Smith" "Bob Jones" "Charlie Brown"
When you have more than two vectors to iterate over simultaneously, pmap() generalises the pattern. Instead of separate .x and .y arguments, it accepts a single list where each element is a vector of the same length. On each iteration it passes the corresponding element from every vector to your function as a separate argument.
Flexible iteration with pmap()
# Create a list of vectors with the same length
args <- list(
a = c(1, 2, 3),
b = c(4, 5, 6),
c = c(7, 8, 9)
)
# Sum across the three vectors element‑wise
pmap(args, sum)
# [[1]]
# [1] 12
#
# [[2]]
# [1] 15
#
# [[3]]
# [1] 18
# Use a data frame as input (each row becomes a set of arguments)
df <- data.frame(
x = 1:3,
y = 4:6,
z = 7:9
)
pmap(df, ~ ..1 + ..2 + ..3) # ..1 refers to the first column, etc.
# [[1]]
# [1] 12
#
# [[2]]
# [1] 15
#
# [[3]]
# [1] 18
# Use a named list with a formula
pmap(args, ~ .a + .b + .c)
# [[1]]
# [1] 12
#
# [[2]]
# [1] 15
#
# [[3]]
# [1] 18
The examples above cover the three core mapping patterns. The sections below show how these functions compose with other tidyverse tools to solve common data-wrangling problems: extracting nested columns, handling errors gracefully, chaining operations in a pipeline, and processing data frames row by row.
Common patterns
Transforming nested data
# Extract a column from each data frame in a list
list_of_dfs <- list(
data.frame(id = 1:2, value = c("a", "b")),
data.frame(id = 3:5, value = c("c", "d", "e"))
)
map(list_of_dfs, "value")
# [[1]]
# [1] a b
# Levels: a b
#
# [[2]]
# [1] c d e
# Levels: c d e
Not every function call succeeds. When map() encounters an error in one iteration, the default behaviour is to stop the entire loop. Wrapping your function with safely() or possibly() catches per-element failures and lets the remaining iterations continue, which is essential when processing real-world data that may contain invalid values.
Error handling with safely() and possibly()
# Wrap a function that may fail
safe_log <- safely(log)
results <- map(c(1, 2, -1, 4), safe_log)
# Extract successful results
transpose(results)$result
# [[1]]
# [1] 0
#
# [[2]]
# [1] 0.6931472
#
# [[3]]
# NULL
#
# [[4]]
# [1] 1.386294
# Use possibly() to provide a default value
possibly_log <- possibly(log, otherwise = NA)
map_dbl(c(1, 2, -1, 4), possibly_log)
# [1] 0.0000000 0.6931472 NA 1.3862944
Once you have a list of results, you often want to chain further operations without assigning intermediate variables. The pipe operator lets you feed the output of one mapping step directly into another, combining map(), keep(), and reduce() into a single readable expression that reads left to right.
Building pipelines with the pipe
library(dplyr)
library(purrr)
# Map over a list, filter results, then combine
list(1:3, 4:6, 7:9) %>%
map(~ .x * 2) %>%
keep(~ mean(.x) > 5) %>%
reduce(c)
# [1] 8 10 12 14 16 18
Data frames are the most common container for tabular data in R, and row-wise operations come up frequently. Instead of writing explicit for loops over row indices, you can pass a data frame directly to pmap() and define a function that receives each column value as a named argument.
Working with data frames row‑wise
# Apply a function to each row of a data frame
df <- data.frame(a = 1:3, b = 4:6, c = 7:9)
pmap(df, function(a, b, c) a + b + c)
# [[1]]
# [1] 12
#
# [[2]]
# [1] 15
#
# [[3]]
# [1] 18
# Use pmap with mutate to create a new column
df %>%
mutate(total = pmap_dbl(., ~ ..1 + ..2 + ..3))
# a b c total
# 1 1 4 7 12
# 2 2 5 8 15
# 3 3 6 9 18
Performance considerations
map()is generally faster thanlapply()for small to medium‑sized data because of its C++ backend.- Typed variants (
map_int,map_dbl, etc.) avoid the overhead of list creation and are recommended when the output type is known. - For very large vectors, consider
future_map()from the furrr package for parallel execution. - When using
pmap()with data frames, be aware that each row is turned into a list; for wide data frames this can incur overhead.
map() always returns a list, regardless of what the function returns. Use the typed variants, map_chr(), map_dbl(), map_int(), map_lgl() — when the return type is uniform and you want to avoid the overhead of list storage. These typed variants also serve as assertions: they raise an error if any iteration returns the wrong type, which catches bugs early. map_vec() is the most flexible typed variant: it uses vctrs::vec_c() to combine results and infers the output type from the first successful return value.
Passing additional arguments to .f is done via ...: map(list_of_df, filter, mpg > 20) calls filter(list_of_df[[i]], mpg > 20) on each element. Named arguments in ... are passed by name. For more complex argument passing, use an anonymous function: map(x, \(el) f(el, extra_arg = compute(el))).