How to purrr

functional programming in R with the purrr package
(Work in progress)

Danielle Ferraro

October 2025

About

In this presentation, we will:

  1. briefly introduce functional programming
  2. get acquainted with the purrr R package
  3. take a look at some examples of purrr in the wild

What is functional programming?

In simple terms, functional programming is a style of programming where you build your code by combining small, self-contained functions instead of writing long, step-by-step procedures.

Basically, this means we break our code into small, reusable functions that can be combined to perform complex tasks.

What is functional programming?

This approach has several advantages:

  • Reusability:
    • Functions can be reused across different parts of a program or in different projects.
    • “Write once, use everywhere” mindset
  • Scales well:
    • Works naturally with lists, nested data, and pipelines (|> or %>%).
    • Easier to extend to larger datasets or more complex workflows.
  • Readability & debugging:
    • Smaller functions are easier to read, test, and debug
  • Computing speed:
    • Replace loops with function calls, which are faster in R
    • Easy to run in parallel

Functional programming in R

Though R supports different programming styles, it is very good at functional programming.

R, at its heart, is a functional programming (FP) language. This means that it provides many tools for the creation and manipulation of functions. In particular, R has what’s known as first class functions. You can do anything with functions that you can do with vectors: you can assign them to variables, store them in lists, pass them as arguments to other functions, create them inside functions, and even return them as the result of a function.

— Hadley Wickham, Advanced R

Functional programming in R

In fact, you’re already using functional programming in R even if you don’t realize it.

R excels at vector operations, which are a form of functional programming:

x <- 1:10
y <- x * 2 # This is secretly functional programming! No iteration needed!

The tidyverse packages are built on functional programming principles:

library(tidyverse)

# For each group, apply the mean function
mtcars |> 
  group_by(cyl) |> 
  summarize(mpg = mean(mpg))

The purrr package

The purrr package is a powerful tool in R that helps us apply functional programming principles to our data analysis tasks.

purrr is part of the tidyverse, so you can install it with:

install.packages("tidyverse")

or install it on its own.

install.packages("purrr")

The purrr workhorse: map()

map() takes a vector and a function, calls the function once for each element of the vector, and returns the results in a list.

The following two statements are equivalent:

map(c(a,b,c), function)
list(function(a),
     function(b),
     function(c))

Example: map()

map() has two arguments: .x (the input) and .f (the function).

A simple example to illustrate how map() works:

library(purrr)
map(.x = 1:5, .f = sqrt) # Notice the output is a list - more on that later!
[[1]]
[1] 1

[[2]]
[1] 1.414214

[[3]]
[1] 1.732051

[[4]]
[1] 2

[[5]]
[1] 2.236068

Example: map()

The function can be specified in several ways:

# Named function
map(.x = 1:5, .f = sqrt)
# Anonymous function
map(.x = 1:5, .f = function(x) x + 10) # old notation
map(.x = 1:5, .f = \(x) x + 10) # new notation
# A "formula"
map(.x = 1:5, .f = ~ .x + 10)

Output classes

The output of map() is always a list. Lists are a very flexible class, but sometimes we want a more specific type of output.

Luckily, there are several map() variants:

Function Output class
map() list
map_lgl() logical vector
map_int() integer vector
map_dbl() double vector
map_chr() character vector

Note: There used to be a map_df() function that returned a data frame, but it has been superseded in favor of using list_rbind() with map().

Example: map_dbl()

In our previous example, it would make more sense to return the results as a numeric vector.

library(purrr)
map_dbl(.x = 1:5, .f = sqrt)
[1] 1.000000 1.414214 1.732051 2.000000 2.236068

Using purrr with more than one input

Often, we want to apply a function over multiple inputs. purrr has you covered with map2() and pmap().

  • map(.x, .f) works with one input vector, .x
  • map2(.x, .y, .f) works with two input vectors, .x and .y
  • pmap(.l, .f) works with more than two input vectors (.l is a list of vectors)

Example: map2()

library(purrr)
map2(.x = 1:5, .y = 6:10, .f = ~ .x + .y)
[[1]]
[1] 7

[[2]]
[1] 9

[[3]]
[1] 11

[[4]]
[1] 13

[[5]]
[1] 15

Example: map2()

Examples from recent projects:

Isn’t this the same thing as the apply() functions in base R? Can’t I just use that?

Yes, you can! In some cases, a base R function can do the same thing as a given purrr function.

Input Output purrr Base R
1 vector list map() lapply()
2 vectors list map2() mapply()
>2 vectors list pmap() mapply()
1 vector vector of specific type map_lgl() (logical), map_int() (integer), map_dbl() (double), map_chr() (character) vapply()

Isn’t this the same thing as the apply() functions in base R? Can’t I just use that?

However, purrr offers a more consistent and user-friendly interface, along with additional features that make it easier to work with complex data structures.

  • Consistency: purrr functions have a consistent naming scheme, making them easier to learn and remember.
  • Type stability: the map() functions in purrr return outputs of consistent types, reducing unexpected errors.
  • Nice and “tidy”: the first argument of map() functions are the data, so they seamlessly integrate with the pipe and generally integrate smoothly with other tidyverse packages.

Other advantages

  • All map() functions have a .progress argument to track the progress of longer jobs.
  • !NEW! All map() functions can be run in parallel using in_parallel() to easily spread computation across multiple cores on your computer, or multiple machines over the network. Formerly, this was only possible with the future_map() functions in the furrr package.
  • Built in error handling with safely() and possibly()

Resources

  1. purrr documentation
  2. Get started with purrr, R-Universe tutorial
  3. Functional programming chapter in Advanced R