3

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?

NelsonGon
  • 13,015
  • 7
  • 27
  • 57
Indrajeet Patil
  • 4,673
  • 2
  • 20
  • 51
  • Can you try with `tryfn(mtcars, quote(mpg + wt))` and inside the `formula = rlang::new_formula(NULL, expr)` and `function(data, expr)` – akrun Jul 25 '19 at 20:02
  • That works, but I must have a function with arguments `data`, `x`, and `y`, so this still won't solve my problem. – Indrajeet Patil Jul 25 '19 at 20:06

2 Answers2

5

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")
Alexis
  • 4,950
  • 1
  • 18
  • 37
  • I disagree with this, the difference between quosures and quoted expressions is irrelevant here, as OP didn't try to feed a quosure where a quoted expression was expected. `rlang::new_formula(NULL, {{ x }} + {{ y }})` would work fine if `new_formula()` supported quasi quotation and used NSE instead of expecting calls or names. See my take on this in my own answer. – moodymudskipper Aug 08 '19 at 16:44
  • @Moody_Mudskipper Well, sort of. Notation with `{{` does create quosures. If you print the formula created in your solution, you'll see extra `~` symbols added due to the arguments being quosures, but that doesn't seem to affect the semantics of the formula, so OP could indeed use it anyway. – Alexis Aug 08 '19 at 18:12
  • Indeed you're right, I'll edit my answer when I get the chance. – moodymudskipper Aug 08 '19 at 18:19
2

Your issues come from the fact that :

  • new_formula() requires call or name objects as inputs, and you're expecting it to use NSE :
rlang::new_formula(NULL, wt + mpg)
#> Error in rlang::new_formula(NULL, wt + mpg): objet 'wt' introuvable
rlang::new_formula(NULL, quote(wt + mpg))
#> ~wt + mpg
  • new_formula() doesn't support quasi-quotation :
quoted_expr <- quote(wt + mpg)
rlang::new_formula(NULL, !!quote(quoted_expr))
#> Error in !quote(quoted_expr): type de l'argument incorrect

(see how !! is not recognized)

A way around both of these issues is to create a quoted expression from a function that supports quasiquotation. This function is expr() :


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

tryfn(mtcars, wt, mpg)
#> 
#>  Pearson's product-moment correlation
#> 
#> data:  wt and mpg
#> t = -9.559, df = 30, p-value = 1.294e-10
#> alternative hypothesis: true correlation is not equal to 0
#> 95 percent confidence interval:
#>  -0.9338264 -0.7440872
#> sample estimates:
#>        cor 
#> -0.8676594
NelsonGon
  • 13,015
  • 7
  • 27
  • 57
moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
  • see comments below Alexis's answer too – moodymudskipper Aug 08 '19 at 18:20
  • I think a note should be made about the use of `{{}}` and how it only appears after a specific version of `rlang`. Curious why the `!!` fails but `{{}}` works. What are the fundamental differences? Also curious what the advantage would be of using `rlang` over `reformulate` and the like. – NelsonGon Aug 19 '19 at 18:08
  • @Moody_Mudskipper How will the call to `rlang::new_formula` will change if the formula was not `~x + y`, but rather `y ~ x`? I tried `rlang::new_formula(rlang::expr({{x}}), rlang::expr({{y}}))`, but that doesn't work. – Indrajeet Patil Jun 09 '20 at 21:07
  • I don't fuly understand what's going on but I'd go with base R, my answer could use `formula = rlang::new_formula(NULL, substitute(x + y))` or even `formula = eval(substitute(~x + y))`, so for your variant you could use `formula = rlang::new_formula(substitute(x), substitute(y))` or `formula = eval(substitute(x ~ y))` – moodymudskipper Jun 10 '20 at 01:15