Menu

apply, lapply, sapply in R: The apply Family Explained

The apply family - apply, lapply, sapply, vapply, mapply, and tapply - runs a function over every element of your data. Here's what each one does and when to reach for it.

This page includes runnable editors - edit, run, and see output instantly.

The apply Family in One Sentence

Every member of the apply family does the same job: take a function and run it over every element of some structure, collecting the results. What varies is the input shape and the output shape. The map:

  • apply(m, MARGIN, FUN) - rows or columns of a matrix.
  • lapply(x, FUN) - each element of a list or vector; always returns a list.
  • sapply(x, FUN) - same as lapply, then simplifies to a vector or matrix when it can.
  • vapply(x, FUN, FUN.VALUE) - sapply with a declared, enforced return type.
  • mapply(FUN, x, y, ...) - walks several inputs in parallel, element by element.
  • tapply(values, groups, FUN) - applies FUN within each group: one result per group.

They all accept the functions you write yourself, including anonymous ones, which is where the family gets its power.

apply(): Rows and Columns of a Matrix

apply takes a matrix, a MARGIN (1 for rows, 2 for columns), and a function:

The matrix fills column by column, so the rows are 1 3 5 and 2 4 6: the row sums print as 9 12 and the column sums as 3 7 11. For the two most common cases base R ships dedicated, faster helpers - rowSums, colSums, rowMeans, colMeans - so reserve apply for functions that don't have one, like apply(m, 2, max).

One trap: apply on a data frame silently converts it to a matrix first, which coerces every column to one shared type. For data frames, treat the columns as a list and use lapply/sapply instead.

lapply() Always Gives a List, sapply() Simplifies

lapply maps a function over each element and returns a list of the same length, no matter what:

Same computation, two shapes: lapply hands back a list holding 85 and 80; sapply notices every result has length 1 and simplifies to a named numeric vector - much nicer to read and to feed into further math.

The convenience has a sharp edge, though: sapply's return type depends on the data. If even one element produces a different length, simplification fails and you silently get a list again:

Interactively that's a shrug; inside a script it means code that worked all year breaks the day the data changes shape. That's the whole reason vapply exists.

vapply(): The Type-Safe sapply

vapply adds a third argument, FUN.VALUE: a template declaring what one result must look like. integer(1) means "each call returns exactly one integer":

You get a named integer vector - 5 6 6 - and, more importantly, a guarantee: if nchar ever returned two values or a character, vapply would stop with an error at the call site instead of letting a mis-shaped result float downstream. The declaration doubles as documentation:

vapply(words, nchar, character(1))
# Error in vapply(words, nchar, character(1)) : values must be type 'character'

Rule of thumb: sapply at the console, vapply in functions and scripts that have to run unattended.

mapply() and tapply(): Parallel Inputs and Groups

lapply walks one structure. When each call needs an element from several structures at matching positions, use mapply - note the function comes first:

Each output element pairs the first values, then the second values, and so on: 10 200 3000 40000.

tapply is the group-wise member: it splits values by groups and applies the function within each group, returning one result per group:

Group a averages 30, group b averages 40. This is base R's answer to "average per category" - the same shape of question group_by + summarize answers for data frames, so if you're already in dplyr territory use that; tapply shines when you have two bare vectors and want one line.

Two conveniences work across the whole family. Extra arguments after the function are forwarded to it on every call:

And anonymous functions slot in whenever no ready-made function fits - sapply(x, \(v) max(v) - min(v)) computes a range per element without naming a helper first.

apply Family vs for Loops

You'll hear that for loops are slow in R and the apply family is fast. That's mostly myth. What's genuinely slow is growing a result inside a loop - result <- c(result, new_value) copies the whole vector on every pass. A loop that pre-allocates its output performs fine:

So choose on readability, not performance. The apply version says what in one line - "square each element" - and handles allocation for you, which is why it's the idiom for straightforward element-wise work. A for loop earns its keep when iterations depend on previous results, when you need early exits with break, or when the body is long enough that a lambda would hurt more than help. Both are legitimate R.

What You Take Away

  • The whole family is one idea: run a function over every element, collect the results.
  • apply is for matrix rows (MARGIN = 1) and columns (MARGIN = 2); prefer rowSums/colMeans when they exist.
  • lapply always returns a list; sapply simplifies when it can - which means its return type can change with the data.
  • vapply locks the return type down; use it in code that must not surprise you.
  • mapply zips several inputs together; tapply aggregates values within groups.
  • for loops aren't inherently slow - growing vectors is. Pick the spelling that reads best.

Next up: pipes - the operator that chains these calls into readable, step-by-step pipelines.

Frequently Asked Questions

What is the difference between lapply and sapply in R?

lapply always returns a list, no exceptions. sapply runs the same computation and then tries to simplify the result - to a vector if every element has length 1, to a matrix if they share a length, and back to a list if it can't. sapply is nicer interactively; lapply (or vapply) is safer in scripts because its return type never changes.

What does apply() do in R?

apply(m, MARGIN, FUN) runs FUN over a matrix: MARGIN = 1 applies it to each row, MARGIN = 2 to each column. apply(m, 1, sum) gives row sums, apply(m, 2, mean) gives column means. It's for matrices and arrays - for lists and vectors use lapply/sapply instead.

Is the apply family faster than a for loop in R?

Usually not by much - that's a myth. A well-written for loop with a pre-allocated result vector performs comparably. The loops that gave for a bad name grow their result one element at a time, copying everything on each pass. Choose apply-family functions for concision and intent, not speed.

What is vapply used for in R?

vapply is sapply with a contract: you declare the type and length of each result, e.g. vapply(x, nchar, integer(1)). If the function returns anything else, R raises an error immediately instead of silently handing you an unexpected structure. Prefer it in code that has to keep working unattended.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED