rrlangnon-standard-evaluation

How to functionalize base R function like `with()`?


I am trying to write a function that operates similarly to with() where the function has a data argument, and two other arguments that I would like evaluated in the context of the data frame.

I initially tried something like below where we just wrap with(), but couldn't get it working.

# this works
with(mtcars, mean(am) - mean(vs))
#> [1] -0.03125

# how to extend this to a function
mean_diff <- function(data, x, y) {
  with(data, mean(x = x) - mean(x = y))
}
mean_diff(mtcars, x = am, y = vs)
#> Error: object 'am' not found

Created on 2025-05-08 with reprex v2.1.1

Other things I tried that did not work include

# these variants don't work either
mean_diff <- function(data, x, y) {
  rlang::inject(
    with(data, mean(x = {{ x }}) - mean(x = {{ y }}))
  )
}
mean_diff(mtcars, x = am, y = vs)

mean_diff <- function(data, x, y) {
  rlang::eval_tidy(
    mean(x = x) - mean(x = y),
    data = data
  )
}
mean_diff(mtcars, x = am, y = vs)

mean_diff <- function(data, x, y) {
  rlang::eval_tidy(
    rlang::expr(mean(x = x) - mean(x = y)),
    data = data
  )
}
mean_diff(mtcars, x = am, y = vs)

Any ideas for getting a function like this with this user experience working? Thank you!


Solution

  • You are on the right track with rlang::inject(). But note that the documentation states that

    You can use {{ arg }} with functions documented to support quosures. Otherwise, use !!enexpr(arg).

    So let’s try that:

    mean_diff <- function(data, x, y) {
      rlang::inject(
        with(data, mean(x = !! rlang::enexpr(x)) - mean(x = !! rlang::enexpr(y)))
      )
    }
    
    mean_diff(mtcars, x = am, y = vs)
    
    [1] -0.03125