The apply Family
You now have the garden's data as a list of pickings per plot, a weekly matrix and a data frame. The questions are all the same shape: "do this to every plot", "total every row", "sum per crop". Writing a loop for each works, but R has a family of functions that each do one of these jobs in a single line.
The rule
Give a function to every piece; the name tells you the shape back.
Each member takes some data, splits it into pieces, runs a function on every piece and combines the results. They differ in what they walk over and what they give back.
weighings <- list(A = c(5, 4, 3.5), B = c(3, 5), C = c(7, 8))
lapply(weighings, sum) # a list: $A 12.5, $B 8, $C 15
sapply(weighings, sum) # a vector: A 12.5, B 8, C 15
vapply(weighings, sum, numeric(1)) # a vector, type guaranteed
vapply(weighings, length, integer(1)) # A 3, B 2, C 2lapply(x, f): list. Always returns a list, one item per piece.sapply(x, f): simplify. Tries to turn that list into a vector or matrix. Handy at the console, but the result's shape depends on the data.vapply(x, f, template): likesapply, but you declare what each result must be, such asnumeric(1)for "one number". If any piece breaks the promise you get an error, not a surprise. Prefer it inside functions.
For grids and groups:
m <- matrix(c(5, 3, 7, 7.5, 5, 8), nrow = 3,
dimnames = list(c("A", "B", "C"), c("wk1", "wk2")))
apply(m, 1, sum) # rows: A 12.5, B 8, C 15
apply(m, 2, sum) # columns: wk1 15, wk2 20.5
harvest <- data.frame(crop = c("tomato", "bean", "tomato", "chilli", "bean"),
kg = c(12.5, 8, 15, 3.5, 6))
tapply(harvest$kg, harvest$crop, sum) # bean 14, chilli 3.5, tomato 27.5apply(m, MARGIN, f): over the rows (1) or columns (2) of a matrix.tapply(values, groups, f): splitsvaluesbygroupsand appliesfto each group, the same kind of "per group" summary as SQL's GROUP BY.
Short functions can be written inline: sapply(weighings, function(w) max(w)), or with R 4.1's shorthand \(w) max(w), gives A 5, B 5, C 8. For walking over two vectors side by side, mapply() and Map() exist too.
These functions are not magic speed-ups over a well-written loop. Their value is clarity: one line that states "sum every piece" leaves less room for bookkeeping mistakes.
The shortcut
Pick by what you are walking over and what you want back:
- A list or vector, want a list:
lapply. - A list or vector, want a plain vector:
vapply(orsapplyat the console). - The rows or columns of a matrix:
applywith 1 or 2. - Values split by a grouping column:
tapply.
Split
Group the weights by crop: bean gets 8 and 6, chilli gets 3.5, tomato gets 12.5 and 15.
Drills
Answer fast — trust the rule.
What is class(lapply(weighings, sum))?
Ignoring names, what does sapply(weighings, length) return?
For the weekly matrix m, what does apply(m, 2, sum) return?
What does tapply(harvest$kg, harvest$crop, max) give for tomato?
Which call guarantees one number per piece, or an error?
Remember
- Give a function to every piece; the name tells you the shape back.
- Shortcut: list back, lapply; vector back, vapply; rows or columns, apply; per group, tapply.
- Trap: trusting sapply's guess about the shape inside code that must not break.
