Introduction to the apply Family
The apply family — sapply(), lapply(), apply(), mapply(), and vapply() — provides a functional alternative to explicit for loops by applying a function to every element of a vector, list, or margin of a matrix, and returning the results collected into a single object. Choosing the right member of the family mostly comes down to what shape of input you have (a vector/list versus a matrix/data frame) and what shape of output you want (a list, a simplified vector, or a guaranteed type).
Cricket analogy: A fitness coach running the same beep test protocol on every player in the squad and compiling all 15 results into one summary table mirrors sapply() applying one function across every element and collecting the outputs.
sapply() and lapply()
lapply(X, FUN) always returns a list, with one element per input element, preserving names if X is named, which makes it the predictable, type-safe workhorse of the family; sapply(X, FUN) calls lapply() internally and then attempts to simplify the result into a vector or matrix when every element has the same length, but silently falls back to returning a plain list when the results aren't uniform, which is convenient interactively but risky inside production code because the output type can vary depending on the data. lapply(mylist, function(x) x^2) on a list of numeric vectors returns a list where each element has been squared.
Cricket analogy: lapply() is like a scorer meticulously recording each batsman's individual scorecard in its own labeled folder without trying to merge them, while sapply() tries to staple all the scorecards into one neat summary sheet when the format allows, but keeps them as separate folders if the innings lengths differ.
nums <- list(a = 1:3, b = 4:6, c = 7:9)
lapply(nums, sum)
# $a
# [1] 6
# $b
# [1] 15
# $c
# [1] 24 (a list)
sapply(nums, sum)
# a b c
# 6 15 24 (simplified to a named numeric vector)apply() for Matrices and Data Frames
apply(X, MARGIN, FUN) is specifically for matrices and data frames, applying FUN across rows when MARGIN = 1 or across columns when MARGIN = 2 (and both simultaneously with MARGIN = c(1, 2)), which makes it the natural tool for computing something like a row-wise total or a column-wise mean across a whole table. Because apply() coerces its input to a matrix first, applying it to a data frame with mixed column types (numeric and character together) will silently coerce everything to character, which is a common source of subtle bugs, so apply() is best reserved for genuinely homogeneous, all-numeric matrices.
Cricket analogy: apply(scorecard, 1, sum) totals each batsman's runs across all innings row by row, while apply(scorecard, 2, mean) computes the average score for each innings column across all batsmen — row versus column aggregation.
m <- matrix(1:6, nrow = 2)
apply(m, 1, sum) # row sums: 9 12 15 -> combined per row
apply(m, 2, mean) # column means, one value per columnmapply() and vapply() for Type Safety
vapply(X, FUN, FUN.VALUE) is the type-safe cousin of sapply(): it requires you to specify FUN.VALUE, a template describing the exact type and length FUN should return for each element (like numeric(1) for a single number), and it throws an informative error immediately if any call produces something that doesn't match that template, which makes it much safer to use inside functions or packages where an unexpected input could otherwise cause sapply() to return a differently-shaped result. mapply(FUN, X, Y) is the multivariate version, iterating over two or more vectors in parallel and passing corresponding elements of each to FUN simultaneously, similar to calling FUN(X[1], Y[1]), FUN(X[2], Y[2]), and so on.
Cricket analogy: vapply(scores, calc_average, numeric(1)) enforces that every single player's calculation must return exactly one number, immediately flagging a data-entry error if any player's record produces something else, unlike sapply() which might silently return an inconsistent structure.
mapply(function(x, y) x + y, X = c(1,2,3), Y = c(10,20,30)) applies the function pairwise across both vectors simultaneously, equivalent to c(1+10, 2+20, 3+30); Map() is a thin wrapper around mapply() with SIMPLIFY = FALSE that always returns a list, which is often clearer inside code that will be passed straight into another lapply()-style call.
sapply()'s automatic simplification is convenient in interactive exploration but dangerous inside reusable functions: if the input happens to be empty or produces results of inconsistent length, sapply() can silently return a list instead of the numeric vector your downstream code expects, causing an error far from its actual cause — use vapply() with an explicit FUN.VALUE whenever the function's output feeds into further processing.
- lapply() always returns a list with one element per input, making it predictable and type-safe.
- sapply() calls lapply() internally and tries to simplify the result, but silently falls back to a list when outputs aren't uniform.
- apply(X, MARGIN, FUN) works on matrices/data frames: MARGIN = 1 for rows, MARGIN = 2 for columns.
- apply() coerces its input to a matrix first, so mixed-type data frames get silently coerced to character.
- vapply() requires a FUN.VALUE template and errors immediately if a result doesn't match, unlike sapply().
- mapply() (and its wrapper Map()) iterates over two or more vectors in parallel, applying FUN to corresponding elements.
- Prefer vapply() over sapply() inside functions or packages where predictable output type matters.
Practice what you learned
1. What does lapply(X, FUN) always return, regardless of FUN's output?
2. What happens when apply() is used on a data frame with both numeric and character columns?
3. Why is vapply() considered safer than sapply() inside a function or package?
4. What does mapply(function(x, y) x + y, X = c(1,2), Y = c(10,20)) return?
5. In apply(X, MARGIN, FUN), what does MARGIN = 2 mean?
Was this page helpful?
You May Also Like
Vectorized Operations in R
How R applies operations across entire vectors at once, including the recycling rule, logical indexing, and why vectorized code outperforms loops.
Loops in R
How R's for, while, and repeat loops work, including break/next control flow and the classic vector-preallocation performance fix.
Functions in R
How R functions are defined, scoped, and evaluated — covering default arguments, lexical scoping, return values, and the ... variadic argument.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics