Working with lists in purrr

R
intermediate
Published

August 4, 2025

Introduction

purrr is mainly used for functional programme, especially making it easier and more consistent to apply a function over a group of items and collect the output:

more_less <- function(col) {
  if(sum(col) > 200) "Loads"
  else "Not much"
}

mtcars |>
  map(more_less)
$mpg
[1] "Loads"

$cyl
[1] "Not much"

$disp
[1] "Loads"

$hp
[1] "Loads"

$drat
[1] "Not much"

$wt
[1] "Not much"

$qsec
[1] "Loads"

$vs
[1] "Not much"

$am
[1] "Not much"

$gear
[1] "Not much"

$carb
[1] "Not much"
mtcars |>
  map_vec(more_less)
       mpg        cyl       disp         hp       drat         wt       qsec 
   "Loads" "Not much"    "Loads"    "Loads" "Not much" "Not much"    "Loads" 
        vs         am       gear       carb 
"Not much" "Not much" "Not much" "Not much" 

But purrr also has a set of tools for working with lists. This session is an introduction to those functions.

A reminder about lists

Lists are a data structure in R with two very useful properties. Unlike vectors, they can contain data of different classes. Unlike data frames/tibbles, they can be ragged, containing items of different lengths:

test_list <- list(chars = letters[1:5],
     nums = 1:3)
test_list
$chars
[1] "a" "b" "c" "d" "e"

$nums
[1] 1 2 3
  • subset a list by item/index with []:
test_list["nums"]
$nums
[1] 1 2 3
test_list[1]
$chars
[1] "a" "b" "c" "d" "e"

** retrieve the contents of a list item with $ or [[]]:

test_list[["nums"]]
[1] 1 2 3
test_list$chars
[1] "a" "b" "c" "d" "e"

** subset those list contents with chained square brackets:

test_list[["nums"]][1]
[1] 1
test_list$chars[3]
[1] "c"

** nesting lists within lists

deep_list <- list(outer_one = list(
  inner_one = 10:5,
  inner_two = 6:2),
outer_two = 20:15)

modify()

Effectively map for list items. modify() applies a function to each list item and returns a structurally-identical modified list:

test_list |>
  modify(toupper)
$chars
[1] "A" "B" "C" "D" "E"

$nums
[1] "1" "2" "3"

Select list items to modify with modify_at()/modify_if() / modify_depth():

test_list |>
  modify_at("chars", toupper) # select list item by quoted name
$chars
[1] "A" "B" "C" "D" "E"

$nums
[1] 1 2 3
test_list |>
  modify_if(is.numeric, \(x) x + 2) # select list item by function
$chars
[1] "a" "b" "c" "d" "e"

$nums
[1] 3 4 5
list(list(list(deepo = c(3, 4, 5)))) |> # likely to be more trouble than it's worth, especially because many functions will do odd things to list itels
  modify_depth(3, \(x) x * 10)
[[1]]
[[1]][[1]]
[[1]][[1]]$deepo
[1] 30 40 50

reduce()

1:10 |>
  reduce(sum)
[1] 55
1:10 |>
  accumulate(sum)
 [1]  1  3  6 10 15 21 28 36 45 55
test_list |>
  reduce(paste) # odd results over vectors, and beware of vector recycling
[1] "a 1" "b 2" "c 3" "d 1" "e 2"