Programming with dplyr: Tidy Evaluation
Prerequisites: Environments, scoping, and closures, dplyr: mutate, summarise, and group_by
dplyr lets you write bare column names — filter(df, age > 30) — instead
of df$age. That convenience, called data masking, needs special
handling when you write your own functions around dplyr.
The problem
library(dplyr, warn.conflicts = FALSE)
summarise_by <- function(data, group, value) {
data |>
summarise(mean = mean(value), .by = group)
}
Calling summarise_by(mtcars, cyl, mpg) fails: dplyr looks for columns
literally called group and value.
The fix: embrace with {{ }}
Wrapping an argument in double braces tells dplyr to use the column the caller named:
library(dplyr, warn.conflicts = FALSE)
summarise_by <- function(data, group, value) {
data |>
summarise(mean = mean({{ value }}), n = n(), .by = {{ group }}) |>
arrange({{ group }})
}
summarise_by(mtcars, cyl, mpg)
summarise_by(mtcars, gear, hp)
cyl mean n
1 4 26.66364 11
2 6 19.74286 7
3 8 15.10000 14
gear mean n
1 3 176.1333 15
2 4 89.5000 12
3 5 195.6000 5
(mtcars is a plain data frame, so results print without tibble
formatting.)
Column names as text
When column names arrive as strings — from a Shiny input or a
configuration file — use the .data pronoun:
library(dplyr, warn.conflicts = FALSE)
top_by <- function(data, column, n = 3) {
data |>
arrange(desc(.data[[column]])) |>
head(n) |>
select(all_of(column))
}
top_by(mtcars, "hp", 2)
hp
Maserati Bora 335
Ford Pantera L 264
.data[[column]]looks up a column by name inside data-masking verbs.all_of(column)does the same for column-selecting verbs likeselect.
Naming new columns dynamically
library(dplyr, warn.conflicts = FALSE)
add_double <- function(data, column) {
data |> mutate("{{ column }}_x2" := {{ column }} * 2)
}
tibble(price = c(10, 20)) |> add_double(price)
# A tibble: 2 × 2
price price_x2
<dbl> <dbl>
1 10 20
2 20 40
The := operator allows a computed name on the left-hand side.
Why bother?
Once repeated analysis steps live in functions, you can apply them to new datasets and columns without copying code — and test them.