lapply, sapply, vapply and Map

R · Practical · 12 min

The apply family is idiomatic R for transforming lists, columns and grouped data.

The apply functions call a function on each element and collect the results:

  • lapply(x, f) always returns a list the same length as x.
  • sapply(x, f) tries to simplify the result to a vector or matrix. Convenient interactively, but its output type depends on the data (an empty input gives list()), so avoid it in functions.
  • vapply(x, f, FUN.VALUE = numeric(1)) declares the type and length of each result and errors otherwise: the safe choice in code.
  • Map(f, x, y) (or mapply) walks several vectors in parallel.
  • apply(m, 1, f) applies over matrix rows (2 for columns); tapply(x, group, f) applies within groups.
  • Filter(pred, x) and Reduce(f, x) complete the functional toolkit.

Because a data frame is a list of columns, lapply(df, class) or vapply(df, is.numeric, logical(1)) operate column by column.

Extra arguments after the function are passed through: lapply(lst, mean, na.rm = TRUE). Anonymous functions are natural here, especially with the \(x) shorthand.

These functions are not dramatically faster than a well-written loop; their benefit is clarity and no manual result bookkeeping. The purrr package offers a consistent typed family (map, map_dbl, map2) used widely in tidyverse code.

Example

scores <- list(ana = c(90, 85, NA), bo = c(70, 75), cy = numeric(0))

print(lapply(scores, length)$bo)                 #> [1] 2
print(sapply(scores, mean, na.rm = TRUE))
#>  ana   bo   cy
#> 87.5 72.5  NaN
print(vapply(scores, \(s) sum(!is.na(s)), numeric(1)))
#> ana  bo  cy
#>   2   2   0
print(class(sapply(list(), length)))             #> [1] "list" (!)

print(unlist(Map(\(n, s) paste(n, max(s)), c("a", "b"), list(1:3, 4:6))))
#>     a     b
#> "a 3" "b 6"
print(Reduce(`+`, 1:5, accumulate = TRUE))       #> [1]  1  3  6 10 15
print(vapply(mtcars[1:3], is.numeric, logical(1)))
#>  mpg  cyl disp
#> TRUE TRUE TRUE

Remember

  • lapply returns a list, always
  • Prefer vapply with a declared result type inside functions
  • Map iterates several inputs in parallel
  • A data frame is a list, so these work column-wise

Watch out for

  • Using sapply in package code, whose return type changes with the input.

Check yourself

  1. Why prefer vapply over sapply in a function you will reuse?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: stat.ethz.ch/R-manual/R-devel/library/base/html/lapply.html