You should {purrr}, really.

r
data science
tidyverse
a quick exercise on clearer and safer iteration with R
Author

Ana Luisa Bodevan

Published

January 20, 2026

Iteration is a fundamental part of data analysis. Any time you repeat the same operation across columns, rows, or objects, you are iterating. Base R gives us for loops, and they are perfectly valid. However, as analyses grow in complexity, loops tend to become verbose, fragile, and harder to reason about.

The purrr package offers clearer, safer and more expressive ways to iterate in R, focusing on what you want to do with the loop rather than how to control it.

A simple example with the iris dataset

data(iris)
str(iris)
'data.frame':   150 obs. of  5 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

Suppose we want to compare the mean of each numeric column. With loop, a simple solution would be:

means_loop <- numeric(4)

for (i in 1:4) {
  means_loop[i] <- mean(iris[[i]])
}

means_loop
[1] 5.843333 3.057333 3.758000 1.199333

It works perfectly! However, if the code grows in complexity so does the loop, given that:

  • You must manually pre-allocate the output object.
  • You must keep track of indices (i, 1:4).
  • If the structure of the data changes, the loop may quietly break.
  • The intent of the code (“apply mean to each column”) is not immediately obvious.

Now, let’s use purrr:

# install.packages("purrr")

library(purrr)

means_purrr <- map_dbl(iris[1:4], mean)
means_purrr
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333 

You can almost read it “map mean over columns 1:4 and return a numeric vector”. Much simpler, right? In the example above, we see that:

  • purrr has clearer intent. Functions like map(), map_dbl(), and map_chr() make it explicit that iteration is happening.
  • Type enforcement (map_dbl guarantees numeric result, for example) gives less room for errors.
  • Requires way less writing

Also, purrr integrates perfectly with tidy tools like pipes:

library(dplyr)

iris |>
  select(where(is.numeric)) |>
  map_dbl(mean)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
    5.843333     3.057333     3.758000     1.199333