Metaprogramming
Many R functions evaluate arguments in a special context:
33.1 Code as data
e <- quote(x + y * 2) # an unevaluated call
class(e); typeof(e) # "call", "language"
e[[1]]; as.list(e) # `+`, x, y * 2
eval(e, list(x = 1, y = 3)) # 7
expr <- parse(text = "1 + 2")[[1]]; eval(expr)
deparse(e) # "x + y * 2"
call("round", 10.5) # round(10.5)
as.call(list(as.name("sum"), 1, 2)) |> eval()
f <- function(x) substitute(x); f(a + b) # captures the expression: a + b
bquote(.(x) + y, list(x = 5)) # quasiquotation in base R
body(f) <- quote(x * 100) # modify a function's code33.2 Non-standard evaluation (NSE)
Many R functions evaluate arguments in a special context:
subset(mtcars, mpg > 25, select = c(mpg, wt)) # mpg refers to a column
with(mtcars, mean(mpg))
library(ggplot2); ggplot(mtcars, aes(wt, mpg)) # wt, mpg are column names
deparse(substitute(x)) # get the argument's expression as text (e.g. for labels)33.3 Tidy evaluation (rlang)
Programming with dplyr/ggplot2 requires passing column names through functions:
library(dplyr); library(rlang)
# Embracing with {{ }} — most common
group_mean <- function(df, group, var) {
df |> group_by({{ group }}) |> summarise(mean = mean({{ var }}, na.rm = TRUE))
}
group_mean(penguins, species, body_mass_g)
# Column names as strings: .data pronoun
group_mean_str <- function(df, group, var) {
df |> group_by(.data[[group]]) |> summarise(mean = mean(.data[[var]], na.rm = TRUE))
}
group_mean_str(penguins, "species", "body_mass_g")
# Dynamic output names with glue syntax and :=
add_double <- function(df, var) mutate(df, "{{ var }}_x2" := {{ var }} * 2)
# Passing ... through
my_summary <- function(df, ...) df |> group_by(...) |> summarise(n = n())
# Lower-level: enquo / !! / !!! / sym / syms
f <- function(df, var) { v <- enquo(var); df |> summarise(m = mean(!!v)) }
cols <- syms(c("bill_length_mm", "bill_depth_mm")); penguins |> select(!!!cols)
expr(mean(x)); exprs(a + b, c * d); quo(x + 1); eval_tidy(quo(x + 1), list(x = 2))33.4 Building code programmatically
make_filter <- function(var, value) expr(!!sym(var) > !!value)
cond <- make_filter("mpg", 25)
filter(mtcars, !!cond)
f <- new_function(alist(x = , y = 2), quote(x + y)) # rlang::new_function
f(1) # 333.5 Use cases & cautions
Domain-specific languages (formulas, dplyr, data.table), automatic labels, code generation, debugging tools. Avoid eval(parse(text = ...)) on untrusted input; prefer expressions and symbols over string pasting.