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
| Parameter | Type | Default | Description |
|---|---|---|---|
X | vector or list | , | A vector, list, or expression to iterate over |
FUN | function | , | The function to apply to each element |
... | arguments | , | Additional arguments passed to FUN |
simplify | logical | TRUE | If TRUE, attempt to simplify the result |
USE.NAMES | logical | TRUE | If 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
| Aspect | lapply | sapply |
|---|---|---|
| Output | Always a list | Tries to simplify to vector/matrix |
| Use case | When you need list output | When you want convenient vector output |
| Safety | More predictable | Can 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 thansapply(x, f)when you expect logical output, it throws iffreturns a different type.sapply()withsimplify = FALSEis identical tolapply(), it disables simplification and always returns a list.sapply()withUSE.NAMES = FALSEsuppresses the use ofxas names for the result. Thesimplify = "array"option coerces results to a multi-dimensional array rather than a matrix when appropriate. For package code, never rely onsapply()’s simplification — usevapply()with an explicit return type, orpurrr::map_*()for predictable results.