rguides

sapply()

sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)

sapply() is a user-friendly version of lapply() that attempts to simplify the result into a vector or matrix when the output can be reduced to a simpler data structure.

Syntax

sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)

Parameters

ParameterTypeDefaultDescription
Xvector or list,A vector, list, or expression to iterate over
FUNfunction,The function to apply to each element
...arguments,Additional arguments passed to FUN
simplifylogicalTRUEIf TRUE, attempt to simplify the result
USE.NAMESlogicalTRUEIf TRUE and X has names, use them as names in the output

Examples

Basic usage

# Square each element
sapply(1:5, function(x) x^2)
# [1]  1  4  9 16 25

# Using a built-in function
sapply(c("hello", "world"), toupper)
# [1] "HELLO" "WORLD"

With names preserved

When the input vector has names, sapply() carries them through to the output by default. This is controlled by USE.NAMES = TRUE and makes the result self-documenting — each element in the output vector is labeled with the corresponding input name. This is especially useful when transforming named lists or character vectors where the names carry meaning.

names <- c("alice", "bob", "charlie")
sapply(names, function(x) paste0(toupper(substr(x, 1, 1)), substr(x, 2, nchar(x))))
#    alice      bob  charlie 
#  "Alice"    "Bob" "Charlie"

Returning a matrix

When each call to FUN returns a vector of the same length greater than one, sapply() simplifies the result into a matrix. Each element of the input becomes a column, and each position within the return vector becomes a row. This matrix output is convenient for feeding into linear algebra functions or plotting routines that expect columnar data.

# When results have length > 1, sapply creates a matrix
sapply(1:3, function(x) c(x, x^2, x^3))
#      [,1] [,2] [,3]
# [1,]    1    2    3
# [2,]    1    4    9
# [3,]    1    8   27

With additional arguments

The ... argument passes extra parameters directly to FUN on each call. This avoids writing an anonymous wrapper function just to supply a fixed argument — you pass round and digits = -1 instead of function(x) round(x, digits = -1). Any named argument not matching sapply()’s own parameters is forwarded, making the code cleaner and more readable.

# Pass extra arguments to FUN
sapply(c(10, 20, 30), round, digits = -1)
# [1] 10 20 30

Working with lists

sapply() handles lists of data frames naturally. You can apply nrow to count observations in each data frame, or write an anonymous function that extracts summary statistics. The simplification logic converts the scalar results into a named vector, giving you a compact summary of a large collection of tabular objects in a single line.

df_list <- list(
  a = data.frame(x = 1:3, y = 4:6),
  b = data.frame(x = 7:9, y = 10:12)
)

sapply(df_list, nrow)
# a b 
# 3 3

sapply(df_list, function(df) sum(df$x))
#  a  b 
#  6 24

Common patterns

Data transformation pipeline

sapply() works well inside multi-step transformations where you need to apply a sequence of operations to each element. Writing an anonymous function that chains addition, division, and rounding keeps the logic self-contained. The result is a matrix where each column corresponds to one input element, making it straightforward to compare transformed values side by side.

# Apply multiple transformations
data <- list(
  c(1, 2, 3),
  c(4, 5, 6),
  c(7, 8, 9)
)

sapply(data, function(x) {
  x <- x + 1
  x <- x / mean(x)
  round(x, 2)
})
#       [,1]  [,2]  [,3]
# [1,]  0.67  0.67  0.67
# [2,]  1.33  1.33  1.33
# [3,]  2.00  2.00  2.00

Conditional processing

You can embed conditional logic inside the function passed to sapply(). This lets you classify each element based on its properties — checking whether all values in a vector are even, for example — and return a descriptive label. The result is a named character vector mapping input names to classification outcomes, which is hard to achieve as cleanly with lapply() alone.

values <- list(a = 1:5, b = c(2, 4, 6), c = c(1, 3, 5, 7))
sapply(values, function(x) if(all(x %% 2 == 0)) "all even" else "has odd")
#                  a          b          c 
# "has odd" "all even" "has odd"

Handling missing values

sapply() can also clean up vectors with missing data. Applying a function that replaces NA with a default value — zero, in this case — produces a complete vector in one step. While ifelse() inside sapply() works for simple replacements, dedicated functions like tidyr::replace_na() or coalesce() are more expressive for complex missing-data pipelines.

vec <- c(1, 2, NA, 4, NA, 6)
sapply(vec, function(x) ifelse(is.na(x), 0, x))
# [1] 1 2 0 4 0 6

How sapply differs from lapply

Aspectlapplysapply
OutputAlways a listTries to simplify to vector/matrix
Use caseWhen you need list outputWhen you want convenient vector output
SafetyMore predictableCan fail silently if simplification fails

When simplify = FALSE, sapply() behaves exactly like lapply().

sapply() behavior and type safety

sapply() is a user-friendly wrapper for lapply() that tries to simplify the result. When all return values are scalar (length-1 vectors of the same type), sapply() returns a named vector. When all return values are vectors of the same length > 1, it returns a matrix. When types or lengths vary, it falls back to a list, silently, with no warning.

This “helpful” simplification is the root cause of fragile code. A function that works perfectly today with sapply() can break silently when input data changes, because the simplification logic may produce a different output structure. For reliable code, use vapply() which requires you to specify the expected return type and raises an error if the actual output does not match.

Use sapply() interactively when exploring data, where the convenience outweighs the risk. Use vapply() in functions and scripts that will run on production data. The migration is mechanical: replace sapply(x, f) with vapply(x, f, FUN.VALUE = numeric(1)) (or whichever type applies).

sapply() and lapply() take the same arguments, the only difference is the output format. Both vectorize over the first argument X, calling FUN once per element. Both accept ... for passing additional arguments to FUN. Neither modifies the input vector.

sapply() wraps lapply() and attempts to simplify the result: a list of scalars becomes a vector, a list of equal-length vectors becomes a matrix. The simplification happens silently, sapply() falls back to returning a list when simplification is not possible. This unpredictability makes sapply() risky in production code. Prefer vapply() when you know the expected output type, or purrr::map_*() variants for explicit return types.

See also

  • lapply()
  • apply()
  • purrr::map() vapply(x, f, FUN.VALUE = logical(1)) is safer than sapply(x, f) when you expect logical output, it throws if f returns a different type.sapply() with simplify = FALSE is identical to lapply(), it disables simplification and always returns a list. sapply() with USE.NAMES = FALSE suppresses the use of x as names for the result. The simplify = "array" option coerces results to a multi-dimensional array rather than a matrix when appropriate. For package code, never rely on sapply()’s simplification — use vapply() with an explicit return type, or purrr::map_*() for predictable results.