purrr::map

Updated April 20, 2026 · Tidyverse
purrr tidyverse iteration functional programming list-columns

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

ParameterDescription
.xA vector or list to iterate over.
.fA 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

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

FunctionReturnsError if coercion fails
map()listnever
map_lgl()logical vectoryes
map_int()integer vectoryes
map_dbl()double vectoryes
map_chr()character vectoryes
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