purrr map() Variants: map, map2, imap, pmap — The Complete Tutorial
The purrr package gives you a family of map() functions that replace loops with clear, type-safe iteration. This tutorial covers every variant — map(), map2(), imap(), pmap() — with interactive examples.
Base R's lapply() applies a function to each element of a list. The purrr package takes that idea and builds an entire family of mapping functions, each designed for a different iteration pattern. Once you learn when to use each variant, loops almost disappear from your code.
Introduction
The purrr map family solves one problem: apply a function to every element of a collection and collect the results. The variants differ in two ways — how many inputs they take and what output type they guarantee.
| Function | Inputs | Output | Use when... |
|---|---|---|---|
map(.x, .f) |
1 list/vector | List | Iterating over one thing |
map_dbl(.x, .f) |
1 list/vector | Double vector | You need a numeric result |
map_chr(.x, .f) |
1 list/vector | Character vector | You need a string result |
map_lgl(.x, .f) |
1 list/vector | Logical vector | You need TRUE/FALSE |
map_int(.x, .f) |
1 list/vector | Integer vector | You need whole numbers |
map2(.x, .y, .f) |
2 lists/vectors | List | Iterating over two things in parallel |
imap(.x, .f) |
1 list + its names/indices | List | You need the name or position |
pmap(.l, .f) |
N lists/vectors | List | Iterating over 3+ things in parallel |
walk(.x, .f) |
1 list/vector | Invisible input | Side effects (printing, writing files) |
Prerequisites
map(): One Input, One Output
map() takes a list (or vector) and applies a function to every element. It always returns a list.
The typed variants — map_dbl(), map_chr(), map_lgl(), map_int() — do the same thing but return a vector of the specified type instead of a list. Use them when you know the output type in advance.
Use typed variants (
map_dbl,map_chr, etc.) whenever you can. They're faster thanmap()and catch type errors immediately — if one element returns the wrong type, you get a clear error instead of a silent bug.
The Formula Shorthand (~)
Instead of writing function(x) x + 1, purrr lets you write ~ .x + 1. The tilde ~ creates an anonymous function where .x is the first argument.
Extracting Elements by Name or Position
Pass a string or integer to map() to extract elements from nested lists — no function needed.
map2(): Two Inputs in Parallel
map2() iterates over two vectors simultaneously, passing one element from each to your function.
Use
map2()when you have exactly two parallel inputs. If one is shorter, it recycles (like base R), but purrr warns you if the lengths aren't compatible.
imap(): Iterate with Names or Indices
imap(.x, .f) is shorthand for map2(.x, names(.x), .f) if .x has names, or map2(.x, seq_along(.x), .f) if it doesn't. Use it when you need both the value and its name or position.
pmap(): Three or More Inputs
When you need to iterate over 3+ vectors in parallel, use pmap(). It takes a single list containing all input vectors.
A data frame is a list of columns, so
pmap()over a data frame iterates row by row. Each row's values become the function arguments. This is one of the most powerful patterns in purrr.
walk(): Side Effects Without Output
walk() works like map() but returns the input invisibly. Use it for side effects — printing, writing files, or plotting — where you don't need the return value.
Combining map with Other purrr Tools
map + keep/discard: Filter Results
map + reduce: Collapse a List
Nested map: Lists of Lists
map vs lapply: When to Use Which
| Feature | lapply() |
map() |
|---|---|---|
| Package | Base R | purrr |
| Formula shorthand | No | Yes (~ .x + 1) |
| Typed variants | No | map_dbl(), map_chr(), etc. |
| Element extraction | No | map(x, "name") |
| Multi-input | mapply() |
map2(), pmap() |
| Error handling | tryCatch() |
safely(), possibly() |
| Dependencies | None | Requires purrr |
Use lapply() when you want zero dependencies. Use map() when you want the full toolkit — typed outputs, formula shorthand, and composable helpers like safely() and possibly().
Practice Exercises
Exercise 1: Type-Safe Extraction
Given a list of employees, extract all salaries as a numeric vector and all names as a character vector.
Click to reveal solution
```rExercise 2: map2 Greetings
Generate personalized greetings combining names and cities.
Click to reveal solution
```rExercise 3: pmap with a Parameter Grid
Generate random samples with different parameters using pmap().
Click to reveal solution
```rSummary
| Variant | Inputs | When to use |
|---|---|---|
map() |
1 | One list/vector, return a list |
map_dbl/chr/lgl/int() |
1 | One list/vector, return a typed vector |
map2() |
2 | Two parallel inputs |
imap() |
1 + names/indices | Need the name or position alongside the value |
pmap() |
N | Three or more parallel inputs, or row-wise data frame iteration |
walk() |
1 | Side effects only (printing, saving) |
FAQ
What does the ~ (tilde) mean in purrr functions?
The tilde creates a one-sided formula that purrr converts to an anonymous function. ~ .x + 1 becomes function(.x) .x + 1. In map2(), use .x for the first argument and .y for the second. In pmap(), use ..1, ..2, ..3 or named arguments.
When should I use map() vs lapply()?
Use lapply() in packages or scripts where you want zero dependencies. Use map() when you want typed output (map_dbl), formula shorthand (~), element extraction by name, or error-handling wrappers like safely(). In interactive analysis, purrr is almost always more convenient.
How does pmap() handle data frames?
A data frame is a list of equal-length vectors (columns). When you pass a data frame to pmap(), it iterates row by row, passing each column value as a named argument to your function. Column names must match function parameter names.
What happens if map_dbl() gets a non-numeric result?
It throws an error immediately: "Result 1 must be a single double, not ...". This is the point of typed variants — they fail fast on unexpected types instead of silently producing a mixed-type list.
What's Next?
- Functional Programming in R — the foundation: first-class functions and closures
- R Anonymous Functions — deep dive into
\(x)syntax and when to use lambdas - R Function Factories — functions that create functions