rguides

lapply()

lapply(X, FUN, ...)

lapply() applies a function to every element of a list or vector and returns the results as a list. It is one of the most fundamental functions in R for iteration, forming the foundation for many other apply functions. Unlike sapply() which attempts to simplify the output, lapply() always returns a list, making it predictable and reliable for downstream operations.

Syntax

lapply(X, FUN, ...)

Parameters

ParameterTypeDefaultDescription
Xlist or vectorAn atomic object or list to iterate over
FUNfunctionThe function to apply to each element
...anyAdditional arguments passed to FUN

Examples

Basic usage with a list

x <- list(a = 1:3, b = 4:6, c = 7:9)
result <- lapply(x, sum)
print(result)
# $a
# [1] 6
# $b
# [1] 15
# $c
# [1] 24

Using additional arguments

The ... argument in lapply() passes extra fixed parameters to FUN for every element without requiring an anonymous function wrapper. This is essential when the target function needs more than one argument, such as a multiplier, a column name, or a configuration value that stays constant across all iterations. Without ..., you would need to wrap each call in function(elem) my_fun(elem, extra_arg), which is more verbose and harder to scan.

x <- list(1, 2, 3, 4, 5)
result <- lapply(x, function(elem, mult) elem * mult, mult = 10)
print(result)
# [[1]]
# [1] 10
# [[2]]
# [1] 20
# [[3]]
# [1] 30
# [[4]]
# [1] 40
# [[5]]
# [1] 50

Working with data frame columns

Because data frames are technically lists of columns underneath, lapply(df, fun) applies the function column by column. This is the idiomatic base R way to compute column-wise summaries like means, standard deviations, or class checks without writing an explicit loop. Each column receives the function independently, and the result is a named list with one entry per original column, preserving the column names for convenient downstream access.

df <- data.frame(
  a = c(1, 2, 3),
  b = c(4, 5, 6),
  c = c(7, 8, 9)
)
result <- lapply(df, mean)
print(result)
# $a
# [1] 2
# $b
# [1] 5
# $c
# [1] 8

Common patterns

Combining with unlist for vector output: When you need a vector instead of a list, the standard pattern is to chain unlist() after lapply(): first compute the list of results, then flatten to an atomic vector. This two-step approach preserves lapply()’s predictable list output during computation while delivering the vector format needed for plotting, tabulation, or further numerical operations. As a shorthand, sapply() combines both steps, but the separated pattern is safer in scripts where output structure must be guaranteed.

x <- list(1, 2, 3)
# Get numeric vector instead of list
vec <- unlist(lapply(x, function(i) i^2))
print(vec)
# [1] 1 4 9

Using lapply with anonymous functions: When the operation is simple and used only once in your script, defining an anonymous function directly inside lapply() avoids cluttering the global environment with single-use function definitions. The syntax function(x) expression captures each element as x and computes the result inline. For functions that already accept the element as their first argument, like toupper(), you can pass the function name directly without wrapping it in an anonymous function at all.

strings <- list("hello", "world", "r")
upper <- lapply(strings, toupper)
print(upper)
# [[1]]
# [1] "HELLO"
# [[2]]
# [1] "WORLD"
# [[3]]
# [1] "R"

lapply() in practice

lapply() always returns a list of the same length as the input. This guaranteed return type is its key advantage over sapply(): the output structure does not depend on the type or length of the function’s return values. Use lapply() in functions that must return consistent types regardless of input data.

A common pattern: lapply(files, read.csv) reads a list of CSV files into a list of data frames, then do.call(rbind, lapply(files, read.csv)) or dplyr::bind_rows(lapply(files, read.csv)) combines them into one. For most data-loading operations, this pattern is the idiomatic alternative to a for loop.

lapply() passes the first argument to FUN as the first positional argument. Additional fixed arguments go in ...: lapply(list_of_df, filter, mpg > 20) calls filter(df, mpg > 20) for each data frame. When the extra arguments themselves vary per element, use Map() or mapply() instead.

The tidyverse equivalent is purrr::map(), which provides type-safe variants (map_dbl(), map_chr()) and more consistent error messages. lapply() is appropriate when you want no dependencies and the output is always a list. For interactive exploration where you want automatic simplification, sapply() is a shorthand, but lapply() is the safer choice in scripts and package code.

lapply() always returns a list, regardless of what FUN returns. This makes it predictable but means you often follow it with unlist() or do.call(rbind, ...) to flatten results. When the function returns a scalar, sapply() simplifies to a vector. For type-safe simplification, use vapply() which lets you specify the expected return type and length. In the tidyverse, purrr::map() mirrors lapply() with a more consistent interface.

See also