purrr::pmap
Overview
pmap() is the multi-input variant of the purrr mapping family. Instead of iterating over a single vector like map(), it takes a named list (or data frame) where each element is a vector. On each iteration, pmap() pulls one element from each vector and passes them as arguments to your function.
The “p” stands for “parallel” — though it’s not true parallelism, it means all inputs advance together, one step per iteration.
Installation
library(purrr)
Signature
pmap(.l, .f, ...)
pmap_lgl(.l, .f, ...)
pmap_int(.l, .f, ...)
pmap_dbl(.l, .f, ...)
pmap_chr(.l, .f, ...)
pmap_dfr(.l, .f, ...)
pmap_df(.l, .f, ...)
Parameters
| Parameter | Description |
|---|---|
.l | A list or data frame. Each element should be a vector of the same length. |
.f | A function, formula, or atom. |
... | Additional arguments passed to .f for each call. |
Return Value
pmap() returns a list. Type-specific variants return atomic vectors. A data frame variant (pmap_dfr()) row-binds results into a tibble.
Basic Usage
Row-wise iteration with a data frame
The most common use case: treating each row of a data frame as a set of arguments:
params <- tibble(
x = c(1, 2, 3),
y = c(10, 20, 30)
)
pmap_chr(params, ~ paste0(..1, "x", ..2))
# [1] "1x10" "2x20" "3x30"
Named arguments are cleaner than positional placeholders:
pmap_chr(params, function(x, y) paste0(x, "x", y))
# [1] "1x10" "2x20" "3x30"
Using a named list
configs <- list(
host = c("db1.example.com", "db2.example.com"),
port = c(5432, 5433),
db = c("app_prod", "app_staging")
)
pmap(configs, function(host, port, db) {
list(host = host, port = port, db = db)
})
# [[1]]
# [[1]]$host
# [1] "db1.example.com"
# ...
Multiple inputs with additional arguments
urls <- c("https://api.example.com", "https://api.test.com")
keys <- c("abc123", "def456")
endpoints <- c("/users", "/products")
pmap_chr(list(url = urls, key = keys), ~ paste0(..1, ..2, sep = "/"))
# [1] "https://api.example.com/abc123" "https://api.test.com/def456"
Common Use Cases
Building URLs from parameters
base_url <- "https://api.example.com"
requests <- tibble(
endpoint = c("/users", "/products", "/orders"),
id = c(42, 17, 99),
key = c("key_a", "key_b", "key_c")
)
pmap_chr(requests, function(endpoint, id, key) {
paste0(base_url, endpoint, "/", id, "?key=", key)
})
# [1] "https://api.example.com/users/42?key=key_a"
# [2] "https://api.example.com/products/17?key=key_b"
# [3] "https://api.example.com/orders/99?key=key_c"
Row-wise model fitting
models <- tibble(
formula = c("mpg ~ wt", "mpg ~ wt + cyl", "mpg ~ wt * cyl"),
data = list(mtcars, mtcars, mtcars)
)
pmap(models, function(formula, data) {
lm(as.formula(formula), data = data)
})
# [[1]]
# Call:
# lm(formula = as.formula(formula), data = data)
# ...
Applying multiple transformations
library(dplyr)
operations <- tibble(
col = c("sepal_length", "petal_length", "sepal_width"),
op = c("log", "exp", "sqrt"),
input = list(mtcars$disp, mtcars$disp, mtcars$disp)
)
pmap(operations, function(col, op, input) {
switch(op,
log = log(input),
exp = exp(input),
sqrt = sqrt(input)
)
})
pmap vs map2
map2() handles exactly two inputs. pmap() handles any number:
# map2 — two inputs
map2_chr(first_names, last_names, ~ paste(.x, .y))
# pmap — one, two, or any number
pmap_chr(list(first = first_names, last = last_names), ~ paste(.x, .y))
pmap_chr(list(a = x, b = y, c = z), ~ a + b + c)
When you find yourself nesting map() calls or using map2() with flatten(), pmap() is usually the cleaner answer.
pmap_dfr — Row-Binding Results
When your function returns a tibble or named list, use pmap_dfr() to row-bind results automatically:
results <- tibble(
name = c("Alice", "Bob"),
score = c(85, 92)
)
pmap_dfr(results, function(name, score) {
tibble(
name = name,
score = score,
grade = ifelse(score >= 90, "A", "B"),
passed = score >= 70
)
})
# # A tibble: 2 x 4
# name score grade passed
# 1 Alice 85 B TRUE
# 2 Bob 92 A TRUE
Gotchas
List elements must have the same length. pmap() recycles inputs of different lengths — but only if the shorter one has length 1:
pmap(list(x = c(1, 2), y = c(3, 4)), ~ ..1 + ..2) # OK
pmap(list(x = c(1, 2), y = 10), ~ ..1 + ..2) # OK (y recycled)
pmap(list(x = c(1, 2), y = c(3, 4, 5)), ~ ..1 + ..2)
# Error: Elements of `.l` must have the same size
Positional placeholders are fragile. If the argument order in your list changes, ..1, ..2 break. Named function arguments are more reliable.
.l can be a data frame. Column names become argument names when passed to a function:
params <- tibble(x = 1:3, y = 10:12)
pmap_int(params, function(x, y) x + y)
# [1] 11 13 15
See Also
- /reference/tidyverse/purrr-map/ — iterate over a single vector with
map() - /reference/tidyverse/purrr_map2/ — iterate over two vectors in parallel with
map2() - /guides/purrr-functional-programming/ — full guide to functional programming patterns with purrr