使用 rlang 创建类型为 ~ x + y 的新公式

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

我正在尝试编写一个自定义函数,我想在其中使用 cor.test 函数,但我无法取消引用所需的参数来创建一个有效的公式。

这是我目前无法使用的方法-

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

我试过这种方法,因为如果我不必在函数环境中取消对公式的引用,它似乎可以工作。

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

关于如何实现这个的任何想法?

请务必记住 rlang::quobase::quote 不同。 实际上,后者最终基本上等同于 rlang::expr。 使用 {{ 进行插值会创建具有相应环境的 quosures, 所以它是以下情况的快捷方式:

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

另一方面,enexpr 的行为类似于 quote,但对于用户键入的内容:

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

with_enexpr(x)
# 1

根据我的经验,quosures 不能很好地(或根本)不支持任何不明确支持它们的函数, 许多 R 函数需要 "raw" 表达式。 即使在打印过程中,您也可以看到它们的处理方式不同:

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

foo(x)
# ^x
# x

这意味着,至少目前,创建简单表达式没有捷径可走, 而且你必须做很长的路要走:

编辑:不完全正确。 简单的表达没有捷径, 但您仍然可以创建带有等式的公式。 请参阅穆迪的回答和下面的评论。


时不时地退后一步并记住,您不需要到处都进行非标准评估也是值得的:

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

tryfn(mtcars, "wt", "mpg")

您的问题来自以下事实:

  • new_formula() 需要调用或命名对象作为输入,并且您希望它使用 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()不支持准引用:
quoted_expr <- quote(wt + mpg)
rlang::new_formula(NULL, !!quote(quoted_expr))
#> Error in !quote(quoted_expr): type de l'argument incorrect

(看看!!是如何不被识别的)

解决这两个问题的一种方法是从支持准引用的函数创建引用表达式。这个函数是 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