In R, what do lapply(), sapply() and vapply() each return, and when is vapply() the safer choice?
answer
- always a list
- simplify when it can
- declare the result shape
- zero-length input
- apply() wants a matrix
basics
~20 sIn R, lapply() always returns a list; sapply() simplifies to a vector or matrix when results line up, else a list; vapply() requires a declared type and length and errors otherwise, making it predictable in functions.
solid answer
~50 sAll three call a function on each element of a list or vector. **`lapply()`** always returns a **list** of the same length. **`sapply()`** tries to **simplify**: to a vector if every result has length 1, to a **matrix** (one column per element) if all results share a length above 1, and otherwise it gives up and returns a list. The same code can therefore return different types depending on the data, and on empty input `sapply()` returns `list()`. **`vapply()`** takes a template, `FUN.VALUE`, such as `numeric(1)`. Every result must match it in type and length or it **errors**, and empty input gives a typed empty vector. Use `vapply()` inside functions and packages where the result type must be guaranteed. `apply()` is different: it works over matrix margins and converts a data frame with mixed column types to a character matrix first.
code
r · 24 linesscores <- list(north = c(3, 5, 4), south = c(2, 4), west = c(5, 5, 4, 3))
sapply(scores, mean)
#> north south west
#> 4.00 3.00 4.25
sapply(scores, range)
#> north south west
#> [1,] 3 2 3
#> [2,] 5 4 5
class(sapply(scores, function(s) s[s > 3]))
#> [1] "list"
vapply(scores, mean, numeric(1))
#> north south west
#> 4.00 3.00 4.25
try(vapply(scores, function(s) s[s > 3], numeric(1)))
#> Error in vapply(scores, function(s) s[s > 3], numeric(1)) :
#> values must be length 1,
#> but FUN(X[[1]]) result is length 2
class(sapply(list(), mean))
#> [1] "list"
vapply(list(), mean, numeric(1))
#> numeric(0)go deeper
Recall the return shapes: lapply gives a list, sapply simplifies when it can, and vapply needs a declared result type.
Explain sapply's three outcomes (vector, matrix, list), the empty-input case, and why apply() on a mixed data frame coerces everything to character.
Show how a report broke when one group returned an empty result through sapply(), and replace it with vapply() or a vectorised expression.
Decide conventions for iteration in shared code: when vapply() is required, when a vectorised rewrite pays off, and when a list-returning Map() is the honest shape.
## The shared idea The **apply family** runs a function over pieces of a data structure and collects the results. The members differ mainly in **what they give back**. That is what interviewers probe, because the return shape decides whether the next line of code works. ```r scores <- list(north = c(3, 5, 4), south = c(2, 4), west = c(5, 5, 4, 3)) ``` ## lapply: always a list `lapply(X, FUN)` returns a **list** with one element per element of `X`, keeping names: ```r lapply(scores, mean) # $north 4 $south 3 $west 4.25 ``` It is predictable, but you often want a vector at the end. ## sapply: simplify if you can `sapply()` calls `lapply()` and then tries to simplify the result: | Every result is… | `sapply()` returns | |---|---| | length 1 | a vector, e.g. `sapply(scores, mean)` gives `north 4, south 3, west 4.25` | | the same length k > 1 | a **k x n matrix**, one column per element: `sapply(scores, range)` is 2 x 3 | | of differing lengths | a **list**: `sapply(scores, function(s) s[s > 3])` | | (input has length 0) | `list()` | The data decides the type. A function written against the length-1 case, `result[["north"]] > 3` or `sum(result)`, can break when one group suddenly returns two values or none. That is the classic `sapply()` bug: fine in testing, a list or matrix in production. ## vapply: declare the shape `vapply(X, FUN, FUN.VALUE)` makes you declare what each result looks like: ```r vapply(scores, mean, numeric(1)) # named numeric vector vapply(scores, range, numeric(2)) # 2 x 3 matrix vapply(scores, function(s) s[s > 3], numeric(1)) # Error: values must be length 1, but FUN(X[[1]]) result is length 2 ``` What you gain: 1. **Type safety.** A result of the wrong length or an incompatible type is an **error at the point of the mistake**, not a strange object three lines later. 2. **Stable empty case.** `vapply(list(), mean, numeric(1))` returns `numeric(0)`, not `list()`. 3. **Self-documenting.** The template says what the function is expected to produce. The trade-off is a little more typing. At the console `sapply()` is fine. In a function, a package or a scheduled report, prefer `vapply()`. ## The rest of the family | Function | Iterates over | Returns | |---|---|---| | `apply(X, MARGIN, FUN)` | rows (`1`) or columns (`2`) of a **matrix** or array | vector, matrix or list, simplified like `sapply()` | | `mapply(FUN, ...)` | several vectors **in parallel** | simplified like `sapply()`; `Map()` is the list-returning form | | `tapply(X, INDEX, FUN)` | groups of `X` defined by a factor | an array indexed by group | `apply()` on a **data frame** is the most common trap. It first converts the data frame with `as.matrix()`. A matrix holds one type, so a character column turns **every value into character**: ```r sales <- data.frame(region = c("North", "South"), revenue = c(120, 80)) apply(sales, 1, function(r) r["revenue"] * 2) # Error: non-numeric argument to binary operator ``` For row-wise work on a data frame, vectorise instead (`sales$revenue * 2`). If you really need to iterate, use `mapply()`/`Map()` over the columns you need. ## None of these is vectorisation Every member calls your R function once per element. They replace the bookkeeping of a `for` loop, not its per-element cost. The speed win comes from truly vectorised functions such as `rowSums()` or arithmetic on whole columns.
- In R, why does apply(df, 1, f) on a data frame with a text column break arithmetic in f?`apply()` converts the data frame to a matrix with `as.matrix()`, and a matrix holds a single type. With one character column, every value becomes character, so `r["revenue"] * 2` fails with *non-numeric argument to binary operator*. Vectorise on the column instead, or iterate over just the numeric columns with `mapply()`.
- In R, how does mapply() differ from sapply()?`sapply()` iterates over one vector or list. `mapply()` walks several vectors in parallel, passing their i-th elements together to the function: `mapply(function(p, q) p * q, price, qty)`. It simplifies its result like `sapply()`. `Map()` does the same but always returns a list.
saying these in an interview costs you the question
- sapply() always returns a vector.
- The apply family is vectorised and much faster than a for loop.
- apply() works column by column on a data frame without changing types.
- vapply() is just sapply() with a different argument order.
- lapply() simplifies its result when every element has length one.