17.7 Customising evaluation with data

Rebinding functions is an extremely powerful technique, but it tends to require a lot of investment. A more immediately practical application is modifying evaluation to look for variables in a data frame instead of an environment. This idea powers the base subset() and transform() functions, as well as many tidyverse functions like ggplot2::aes() and dplyr::mutate(). It’s possible to use eval() for this, but there are a few potential pitfalls (Section 20.6), so we’ll switch to rlang::eval_tidy() instead.

As well as expression and environment, eval_tidy() also takes a data mask, which is typically a data frame:

df <- data.frame(x = 1:5, y = sample(5))
eval_tidy(expr(x + y), df)
#> [1] 6 5 7 5 7

Evaluating with a data mask is a useful technique for interactive analysis because it allows you to write x + y rather than df$x + df$y. However, that convenience comes at a cost: ambiguity. In Section 20.4 you’ll learn how to deal with ambiguity using special .data and .env pronouns.

We can wrap this pattern up into a function by using enexpr(). This gives us a function very similar to base::with():

with2 <- function(df, expr) {
  eval_tidy(enexpr(expr), df)
}

with2(df, x + y)
#> [1] 6 5 7 5 7

Unfortunately, this function has a subtle bug and we need a new data structure to help deal with it.