Search code examples
rrlangquosure

Creating a new formula of type `~ x + y` using `rlang`


I am trying to write a custom function where I want to use the cor.test function but I am having trouble unquoting the needed arguments to create a working formula.

Here is what I currently have that doesn't work-

library(rlang)

# custom function
tryfn <- function(data, x, y) {
  stats::cor.test(
    formula = rlang::new_formula(NULL, {{ x }} + {{ y }}),
    data = data,
    method = "pearson"
  )
}

# using the function
tryfn(mtcars, wt, mpg)
#> Error in rlang::new_formula(NULL, {: object 'wt' not found

I tried this way because it seems to work if I don't have to unquote the formula in the function environment.

# without unquoting inside another function
print(rlang::new_formula(NULL, quote(x + y)))
#> ~x + y

Any ideas on how to implement this?


Solution

  • It's important to remember that rlang::quo is not the same as base::quote. In practice, the latter ends up being essentially equivalent to rlang::expr. Interpolation with {{ creates quosures with their corresponding environments, so it's a shortcut for a case like the following:

    x <- 0
    
    with_curly <- function(foo) {
      x <- 1
      rlang::eval_tidy({{ foo }})
    }
    
    with_curly(x)
    # 0
    
    with_enquo <- function(foo) {
      x <- 1
      rlang::eval_tidy(rlang::enquo(foo))
    }
    
    with_enquo(x)
    # 0
    

    On the other hand, enexpr acts like quote but for what the user typed:

    with_enexpr <- function(foo) {
      x <- 1
      rlang::eval_tidy(rlang::enexpr(foo))
    }
    
    with_enexpr(x)
    # 1
    

    In my experience, quosures don't play nicely (or at all) with any function that doesn't support them explicitly, and many R functions expect "raw" expressions. Even during printing you can see that they aren't treated the same:

    foo <- function(foo) {
      rlang::qq_show({{ foo }})
      rlang::qq_show(!!rlang::enexpr(foo))
      invisible()
    }
    
    foo(x)
    # ^x
    # x
    

    That means, at least for now, there's no shortcut for creation of simple expressions, and you'll have to do it the long way:

    EDIT: not entirely true. There's no shortcut for simple expressions, but you can still create formulas with quosures. See Moody's answer and the comments below.


    It's also worth taking a step back every now and then and remember that you don't need non-standard evaluation everywhere:

    tryfn <- function(data, x, y) {
      stats::cor.test(
        formula = as.formula(glue::glue("~ {x} + {y}")),
        data = data,
        method = "pearson"
      )
    }
    
    tryfn(mtcars, "wt", "mpg")