purrr::pmap

Updated April 21, 2026 · Tidyverse
purrr tidyverse iteration functional programming multiple-inputs

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

ParameterDescription
.lA list or data frame. Each element should be a vector of the same length.
.fA 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