purrr::map
Overview
map() applies a function to each element of a vector or list and returns the results in a list. It’s the tidyverse alternative to base R’s lapply() with a more consistent interface and shorter syntax.
The purrr package also provides type-specific variants that return atomic vectors directly, eliminating the unlist() or simplify() steps. There are also map2() and pmap() for iterating over multiple inputs in parallel, and imap() for iterating with the index.
Installation
# purrr is part of the tidyverse, install it with:
install.packages("tidyverse")
# or just purrr:
install.packages("purrr")
library(purrr)
Signature
map(.x, .f, ...)
map_lgl(.x, .f, ...)
map_int(.x, .f, ...)
map_dbl(.x, .f, ...)
map_chr(.x, .f, ...)
map_df(.x, .f, ...)
Parameters
| Parameter | Description |
|---|---|
.x | A vector or list to iterate over. |
.f | A function, formula, or atom. |
... | Additional arguments passed to .f for each call. |
Return Value
map() returns a list the same length as .x. Type-specific variants (map_lgl, map_int, map_dbl, map_chr) return atomic vectors of that type, or throw an error if coercion fails. map_df() returns a data frame by row-binding the results.
Basic Usage
Simple iteration
numbers <- list(1, 2, 3, 4, 5)
map(numbers, ~ .x * 2)
# [[1]]
# [1] 2
# [[2]]
# [1] 4
# ...
Type-specific variants
scores <- list(c(85, 92, 78), c(90, 88, 95), c(72, 81, 88))
map_dbl(scores, mean)
# [1] 85.0 91.0 80.3
map_chr(scores, ~ paste(.x, collapse = "-"))
# [1] "85-92-78" "90-88-95" "72-81-88"
Passing additional arguments
data <- list(c(3, 7, 2), c(10, 1, 5), c(8, 4, 6))
map(data, round, digits = 1)
# [[1]]
# [1] 3 7 2
# [[2]]
# [1] 10 1 5
# [[3]]
# [1] 8 4 6
map(data, seq, by = 2)
# [[1]]
# [1] 3 5 7
# [[2]]
# [1] 10 8 6 4 2
# [[3]]
# [1] 8 6 4
Formula shorthand
words <- list("hello", "world", "purrr")
map_chr(words, ~ paste0(str_to_upper(.), "!"))
# [1] "HELLO!" "WORLD!" "PURR!"
# Equivalent to:
map_chr(words, str_to_upper)
# [1] "HELLO" "WORLD" "PURR"
Anonymous function shorthand
nested <- list(
list(a = 1, b = 2),
list(a = 3, b = 4),
list(a = 5, b = 6)
)
map_dbl(nested, "a") # extract element named "a"
# [1] 1 3 5
map_dbl(nested, 1) # extract first element
# [1] 1 3 5
Common Use Cases
Column-wise operations in data frames
library(dplyr)
df <- tibble(
a = c(1, 2, 3),
b = c(4, 5, 6),
c = c(7, 8, 9)
)
df %>% map_dbl(mean)
# a b c
# 2 5 8
Nested data frames and list columns
library(tidyr)
recipes <- tibble(
name = c("pasta", "risotto", "soup"),
ingredients = list(
c("flour", "eggs", "salt"),
c("rice", "stock", "parmesan", "white wine"),
c("onion", "carrot", "celery", "stock")
)
)
recipes %>%
mutate(count = map_int(ingredients, length))
# # A tibble: 3 × 3
# name ingredients count
# 1 pasta <chr [3]> 3
# 2 risotto <chr [4]> 4
# 3 soup <chr [4]> 4
Reading multiple files
library(purrr)
files <- c("sales_jan.csv", "sales_feb.csv", "sales_mar.csv")
all_data <- map(files, read_csv) %>%
list_rbind()
Model fitting across groups
mtcars %>%
split(.$cyl) %>%
map(~ lm(mpg ~ wt, data = .x)) %>%
map(summary) %>%
map_dbl("r.squared")
# 4 6 8
# 0.8552764 0.4650944 0.4230155
Variants and Related Functions
map2 and pmap for multiple inputs
# map2 iterates over two vectors in parallel
ages <- c(25, 30, 35)
names <- c("Alice", "Bob", "Carol")
map2_chr(names, ages, ~ paste0(.x, " (", .y, ")"))
# [1] "Alice (25)" "Bob (30)" "Carol (35)"
# pmap for any number of inputs
params <- tibble(
x = c(1, 2, 3),
n = c(10, 20, 30)
)
pmap_chr(params, rep)
# [1] "1 1 1 1 1 1 1 1 1 1" ...
imap for iteration with index
colors <- c("red", "green", "blue")
imap_chr(colors, ~ paste0(.y, ": ", .x))
# [1] "1: red" "2: green" "3: blue"
Type-specific shortcuts
| Function | Returns | Error if coercion fails |
|---|---|---|
map() | list | never |
map_lgl() | logical vector | yes |
map_int() | integer vector | yes |
map_dbl() | double vector | yes |
map_chr() | character vector | yes |
map_df() | data frame (row bind) | no |
map_dfr() | data frame (row bind) | no |
map_dfc() | data frame (col bind) | no |
Safely handling errors
map() stops if any call throws an error. Use safely() or possibly() to handle this:
numbers <- list(1, 2, "three", 4, 5)
# safely returns a list with result and error components
safe_log <- safely(log)
map(safe_log, numbers)
# [[1]]$result [1] 0
# [[2]]$result [1] 0.6931472
# [[3]]$result NULL
# [[3]]$error <simpleError in log("three")...
Or use possibly() to provide a default value:
map_dbl(numbers, possibly(log, NA_real_))
# [1] 0.0000000 0.6931472 NA 1.3862944 1.6094379
Comparison with Base R
map vs lapply
# Base R
lapply(list(1, 2, 3), function(x) x * 2)
# [[1]] [1] 2
# [[2]] [1] 4
# [[3]] [1] 6
# purrr — same result, cleaner syntax
map(list(1, 2, 3), ~ .x * 2)
map vs sapply
# sapply simplifies to vector if possible (but gives cryptic warning otherwise)
sapply(list(1, 2, 3), `*`, 2)
# [1] 2 4 6
# map_dbl is explicit about the return type
map_dbl(list(1, 2, 3), ~ .x * 2)
# [1] 2 4 6
sapply() tries to simplify the output, which can silently return a list, vector, or matrix depending on the results. map_*() variants make the expected output explicit, catching type mismatches early.
Gotchas
Type coercion fails with an error. map_dbl() throws if any element cannot be coerced to double:
map_dbl(list(1, "two", 3), ~ .x)
# Error: Can't coerce element 2 to a double
Use map() if the return type varies, or use modify() to preserve the input type.
map does not name output elements. set_names() or set_names(map()) adds names:
x <- list(a = 1, b = 2, c = 3)
map(x, ~ .x * 2) # list with names preserved
map_dbl(x, ~ .x * 2) # unnamed double vector
set_names(map_dbl(x, ~ .x * 2), names(x)) # named double vector
Empty inputs return empty lists/vectors. map() on an empty list returns an empty list, not list(NULL):
map(list(), identity)
# list()
This is consistent with purrr design — check length(x) > 0 before mapping if you need different behavior.
See Also
- /reference/tidyverse/purrr-keep/ — keep elements that pass a condition
- /reference/tidyverse/purrr-reduce/ — reduce a list to a single value by repeatedly applying a function
- /reference/tidyverse/purrr-walk/ — call a function for its side effects, returning the input unchanged
- /guides/purrr-functional-programming/ — full guide to functional programming in R with purrr