Skip to content
AgileLogics
6 min read

Functional Programming with purrr

Prerequisites: The apply family

purrr is the tidyverse's toolkit for applying functions to lists and vectors. Its functions are consistent and predictable about what they return — which the apply family isn't always.

map and its typed variants

library(purrr)

words <- c("apple", "banana", "kiwi")

map(words, nchar)
map_int(words, nchar)
map_chr(words, toupper)
map_dbl(1:3, \(x) x / 2)
[[1]]
[1] 5

[[2]]
[1] 6

[[3]]
[1] 4

[1] 5 6 4
[1] "APPLE"  "BANANA" "KIWI"
[1] 0.5 1.0 1.5
  • map() always returns a list.
  • map_int(), map_dbl(), map_chr(), and map_lgl() return a vector of that type — or give a clear error if a result doesn't fit.

Iterating over two inputs, and filtering

library(purrr)

map2_dbl(c(10, 20, 30), c(0.1, 0.2, 0.3), \(price, tax) price * (1 + tax))

numbers <- list(3, "x", 7, NULL, 12)
keep(numbers, is.numeric) |> unlist()
[1] 11 24 39
[1]  3  7 12

keep() and discard() filter a list by a condition; reduce() combines elements into one value.

A real use: many files, one table

A very common task — read every CSV file in a folder and stack them:

files <- list.files("data/monthly", pattern = "\\.csv$", full.names = TRUE)
all_sales <- files |>
  purrr::map(readr::read_csv) |>
  purrr::list_rbind()

Handling failures

safely() wraps a function so errors are captured instead of stopping the whole loop:

library(purrr)

safe_log <- safely(log)
results <- map(list(10, "a", 100), safe_log)
map(results, "result") |> map_dbl(\(x) if (is.null(x)) NA else x) |> round(2)
[1] 2.30   NA 4.61

Each result holds either a result or an error, so one bad input doesn't lose all the others.

Common mistakes

  • Using map() when you wanted a vector, then struggling with a list. Use the typed map_*() variant.