vapply()
vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE) vapply() is the type-safe cousin of sapply(). It applies a function to each element of a vector or list, but unlike sapply() which silently coerces output, vapply() requires you to declare the expected return shape via the FUN.VALUE argument. If any call to FUN produces a value that does not match that declared shape, R stops immediately with an error.
This makes vapply() the go-to choice for production code where silent type coercion would cause subtle bugs.
Syntax
vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
X | vector or list | , | An atomic vector or list to iterate over |
FUN | function | — | The function to apply to each element |
FUN.VALUE | template | — | A prototype for the expected output: type and length must match exactly |
... | any | — | Additional arguments passed to FUN on each call |
USE.NAMES | logical | TRUE | If TRUE and X has names, those names are used for the result |
Examples
Basic usage with character vectors
vapply() needs to know the return type upfront — that is the whole point. Passing FUN.VALUE = integer(1) declares “each call to nchar() returns one integer,” and vapply() enforces that contract. If nchar() ever returned something else, the call would stop with an error rather than silently reshaping the output.
cities <- c("New York", "London", "Tokyo", "Paris")
result <- vapply(cities, nchar, FUN.VALUE = integer(1))
result
# [1] 8 6 5 5
nchar() returns a single integer per string. FUN.VALUE = integer(1) declares this contract explicitly. The result is a plain integer vector.
Returning a named vector per element
When FUN.VALUE has length greater than 1 — like character(2) — vapply() returns a matrix instead of a flat vector. Each call to the function produces a vector of that length, and those vectors become the columns of the result matrix. Giving names to the FUN.VALUE template, as in c(first = "", last = ""), propagates those names to the matrix rows, which makes the output self-documenting.
first_and_last <- function(x) {
chars <- strsplit(x, "")[[1]]
c(first = chars[1], last = chars[length(chars)])
}
letters <- c("apple", "banana", "cherry")
result <- vapply(letters, first_and_last, FUN.VALUE = character(2))
result
# [,1] [,2] [,3]
# first "a" "b" "c"
# last "e" "a" "y"
FUN.VALUE = character(2) tells R the function returns a character vector of length 2. Since FUN.VALUE has length greater than 1, the result is a matrix with rows matching the FUN.VALUE length.
Passing extra arguments
Extra arguments passed through ... are forwarded to FUN on every call. This is how you supply parameters that stay constant across iterations — like a multiplier or a threshold — without wrapping the function in an anonymous closure. The extra arguments appear after FUN.VALUE in the call signature, a position that is easy to overlook but important for keeping your code compact.
compute_range <- function(x, mult = 1) {
r <- range(x)
c(min = r[1] * mult, max = r[2] * mult)
}
numbers <- list(c(1, 5, 3), c(10, 2, 8), c(6, 6, 6))
result <- vapply(numbers, compute_range, FUN.VALUE = numeric(2), mult = 2)
result
# [,1] [,2] [,3]
# min 2 10 12
# max 10 16 12
Extra arguments go after FUN.VALUE in the ... slot.
Type mismatch causes an error
The type check is the feature that distinguishes vapply() from sapply(). Declaring FUN.VALUE = character(1) when the function actually returns integers causes an immediate error with a message that names both the expected and received types. This catches mistakes that sapply() would silently absorb by returning a list, which might only surface as a bug several steps downstream.
# This throws an error: nchar returns integer, not character
result <- vapply(cities, nchar, FUN.VALUE = character(1))
# Error in vapply(): values must be character, not integer
This strictness is the feature, not a bug. It catches type errors early.
Common patterns
Ensuring consistent output from a custom function
When a custom function returns a named numeric vector of fixed length — say, c(mean = ..., sd = ...) — vapply() with FUN.VALUE = numeric(2) assembles a matrix where each column is one call’s result and each row is one named statistic. This is a compact way to compute the same set of summary measures across multiple data subsets and get the results back in a tidy rectangular shape.
get_stats <- function(x) {
c(mean = mean(x), sd = sd(x))
}
datasets <- list(
control = rnorm(50, mean = 0, sd = 1),
treatment = rnorm(50, mean = 2, sd = 1)
)
vapply(datasets, get_stats, FUN.VALUE = numeric(2))
# [,1] [,2]
# mean 0.1373522 2.0939492
# sd 0.9408999 0.9165887
Safe extraction from a list of data frames
Extracting the same property from each element of a list is one of the most natural uses of vapply(). If you have a list of data frames and need the row count of each, vapply(dfs, nrow, FUN.VALUE = integer(1)) returns a clean integer vector — no unlist(), no type guessing, and no risk of getting back a list because one element happened to be NULL.
extract_nrow <- function(df) nrow(df)
data_frames <- list(a = data.frame(x = 1:5), b = data.frame(y = 1:3))
vapply(data_frames, extract_nrow, FUN.VALUE = integer(1))
# a b
# 5 3
How vapply compares to related functions
| Function | Output | Simplification | Safety |
|---|---|---|---|
lapply() | Always list | None | Safe |
sapply() | Vector, matrix, or list | Automatic | Unsafe (silent coercion) |
vapply() | Vector, matrix, or array | None (you declare it) | Safe (enforced) |
apply() | Vector, matrix, or array | None | Safe |
The key distinction from sapply() is that vapply() never attempts simplification. The output shape is entirely determined by FUN.VALUE. If FUN.VALUE is a scalar (e.g., integer(1)), you get a vector. If FUN.VALUE has length 2, you get a 2-row matrix.
vapply() in practice
vapply() requires specifying the expected return type via FUN.VALUE, which doubles as documentation of the function’s contract. When the actual return type does not match FUN.VALUE, vapply() raises an error immediately. This makes vapply() safer than sapply() in production code: sapply() silently changes its return type based on the output, making it unreliable in functions that depend on consistent output structure.
The FUN.VALUE argument is a template, not just a type — it specifies both type and length. FUN.VALUE = numeric(1) means each call must return a single double; FUN.VALUE = character(3) means each call must return a character vector of length 3. The names in FUN.VALUE become the column names of the resulting matrix when FUN.VALUE has length > 1.
A common migration pattern is to replace sapply(x, f) with vapply(x, f, numeric(1)) (or whichever type is appropriate) to catch type errors that were previously silent. This is particularly valuable in functions that will be called with untrusted input — a NULL return from f will crash vapply() with a clear error rather than silently producing a list.
For functions that return a vector of fixed length, vapply() allocates the result matrix upfront and fills it by position, making it faster than sapply() for large inputs. The performance difference rarely matters at the interactive scale but becomes significant in tight loops over thousands of elements.