Functions are how you organise and reuse every analysis in R.
Create a function with function(args) body and assign it to a name: area <- function(w, h = w) w * h. The value of the last expression is returned; return() is only needed for an early exit.
Arguments can be matched by position or by name, and names may be partially matched (avoid relying on that). Defaults can refer to other arguments, as h = w does, because they are evaluated lazily when first used. missing(h) tells you whether a caller supplied an argument.
... collects extra arguments and passes them on, which is how wrappers forward options: my_plot <- function(x, ...) plot(x, col = "red", ...).
Since R 4.1 there's a short lambda syntax: \(x) x + 1 is the same as function(x) x + 1, handy with sapply and pipes.
Functions don't modify their arguments: R passes values with copy-on-modify semantics, so changing x inside a function changes a local copy. To return several results, return a list.
Good habits: keep functions small, give arguments sensible defaults, validate inputs early with stopifnot() and put the data argument first so the function works well with the pipe.
Example
area <- function(w, h = w) {
stopifnot(is.numeric(w), w >= 0)
w * h # last value is returned
}
print(area(3)) #> [1] 9
print(area(h = 2, w = 5)) #> [1] 10
summarise_vec <- function(x, ...) {
list(mean = mean(x, ...), n = length(x))
}
s <- summarise_vec(c(1, 5, NA), na.rm = TRUE)
print(s$mean) #> [1] 3
double_it <- function(v) { v[1] <- 99; v * 2 }
v <- c(1, 2)
print(double_it(v)) #> [1] 198 4
print(v) #> [1] 1 2 (unchanged)
sq <- \(x) x^2
print(sq(1:4)) #> [1] 1 4 9 16Remember
- The last expression is the return value
- Defaults are lazy and can use other arguments
...forwards extra arguments;\(x)is the short lambda- Arguments are never modified in the caller
Watch out for
- Expecting a function to change a vector or data frame passed in; reassign its result instead.