【问题标题】:using quasiquotation in functions with formula interface在具有公式接口的函数中使用准引用
【发布时间】:2020-05-21 20:12:46
【问题描述】:

我想编写一个自定义函数,可以接受bare"string" 输入,并且可以处理有和没有公式接口的函数。

自定义函数示例

# setup
set.seed(123)
library(tidyverse)

# custom function
foo <- function(data, x, y) {
  # function without formula
  print(table(data %>% dplyr::pull({{ x }}), data %>% dplyr::pull({{ y }})))

  # function with formula
  print(
    broom::tidy(stats::t.test(
      formula = rlang::new_formula({{ rlang::ensym(y) }}, {{ rlang::ensym(x) }}),
      data = data
    ))
  )
}

裸露

适用于有和没有公式接口的函数

foo(mtcars, am, cyl)
#>    
#>      4  6  8
#>   0  3  4 12
#>   1  8  3  2

#> # A tibble: 1 x 10
#>   estimate estimate1 estimate2 statistic p.value parameter conf.low conf.high
#>      <dbl>     <dbl>     <dbl>     <dbl>   <dbl>     <dbl>    <dbl>     <dbl>
#> 1     1.87      6.95      5.08      3.35 0.00246      25.9    0.724      3.02
#> # ... with 2 more variables: method <chr>, alternative <chr>

字符串

适用于有和没有公式接口的函数

foo(mtcars, "am", "cyl")
#>    
#>      4  6  8
#>   0  3  4 12
#>   1  8  3  2

#> # A tibble: 1 x 10
#>   estimate estimate1 estimate2 statistic p.value parameter conf.low conf.high
#>      <dbl>     <dbl>     <dbl>     <dbl>   <dbl>     <dbl>    <dbl>     <dbl>
#> 1     1.87      6.95      5.08      3.35 0.00246      25.9    0.724      3.02
#> # ... with 2 more variables: method <chr>, alternative <chr>

作为列名

仅适用于没有公式接口的函数

foo(mtcars, colnames(mtcars)[9], colnames(mtcars)[2])
#>    
#>      4  6  8
#>   0  3  4 12
#>   1  8  3  2

#> Error: Only strings can be converted to symbols
#> Backtrace:
#>     x
#>  1. \-global::foo(mtcars, colnames(mtcars)[9], colnames(mtcars)[2])
#>  2.   +-base::print(...)
#>  3.   +-broom::tidy(...)
#>  4.   +-stats::t.test(...)
#>  5.   +-rlang::new_formula(...)
#>  6.   \-rlang::ensym(y)

如何修改原始函数,使其适用于上述所有输入输入方式以及两种使用的函数?

【问题讨论】:

  • 你太灵活了:x &lt;- 'am'; y &lt;- 'cyl'; foo(mtcars, x, y)
  • @rawr 甚至更令人困惑:am &lt;- 'disp'; cyl &lt;- 'gear'; foo(mtcars, am, cyl)。那里应该发生什么?参数是否应该被评估为其字符串值?

标签: r tidyverse rlang quasiquotes


【解决方案1】:

rlang 的优点在于,您可以控制何时通过!!{{}} 运算符评估值。您似乎想要创建一个函数,该函数将字符串、符号和(可能已评估的)表达式都包含在同一个参数中。使用 ensym 使用符号或裸字符串实际上很容易,但也希望允许像 colnames(mtcars)[9] 这样的代码在返回字符串之前必须评估是问题所在。这可能会令人困惑。例如,当您运行以下命令时,您期望的行为是什么?

am <- 'disp'
cyl <- 'gear'
foo(mtcars, am, cyl)

如果你想假设所有“调用”都应该被评估,但符号和文字不应该被评估,你可以编写一个辅助函数。这是一个“更清洁”的功能

clean_quo <- function(x) {
  if (rlang::quo_is_call(x)) {
    x <- rlang::eval_tidy(x)
  } else if (!rlang::quo_is_symbolic(x)) {
    x <- rlang::quo_get_expr(x)
  }
  if (is.character(x)) x <- rlang::sym(x)
  if (!rlang::is_quosure(x)) x <- rlang::new_quosure(x)
  x
}

你可以在你的函数中使用它

foo <- function(data, x, y) {
  x <- clean_quo(rlang::enquo(x))
  y <- clean_quo(rlang::enquo(y))

  # function without formula
  print(table(data %>% dplyr::pull(!!x), data %>% dplyr::pull(!!y)))

  # function with formula
  print(
    broom::tidy(stats::t.test(
      formula = rlang::new_formula(rlang::quo_get_expr(y), rlang::quo_get_expr(x)),
      data = data
    ))
  )
}

这样做将允许所有这些返回相同的值

foo(mtcars, am, cyl)
foo(mtcars, "am", "cyl")
foo(mtcars, colnames(mtcars)[9], colnames(mtcars)[2])

但您可能只是在延迟可能出现的其他问题。我不建议使用这种代码过度解释用户意图。这就是为什么最好明确地允许他们脱离自己。也许提供两个不同版本的函数,可以与需要评估的参数和不需要评估的参数一起使用。

【讨论】:

  • 谢谢。我支持输入参数的所有不同方式,因为我的函数的许多用户都希望它们能够工作。这是因为他们之前使用过tidyverse 函数,这些函数确实可以接受符号、字符串和表达式。
【解决方案2】:

在混合标准和非标准评估时,我必须同意@MrFlick 和其他人的固有歧义。 (我也在你的similar question from a while ago 中指出了这一点。)

然而,有人可能会争辩说dplyr::select() 适用于colnames(.)[.] 形式的符号、字符串和表达式。如果您绝对必须拥有相同的界面,那么您可以利用tidyselect 来解析您的输入:

library( rlang )
library( tidyselect )

ttest <- function(data, x, y) {
  ## Identify locations of x and y in data, get column names as symbols
  s <- eval_select( expr(c({{x}},{{y}})), data ) %>% names %>% syms

  ## Use the corresponding symbols to build the formula by hand
  broom::tidy(stats::t.test(
    formula = new_formula( s[[2]], s[[1]] ),
    data = data
  ))
}

## All three now work
ttest( mtcars, am, cyl )
ttest( mtcars, "am", "cyl" )
ttest( mtcars, colnames(mtcars)[9], colnames(mtcars)[2] )

【讨论】:

  • 是的,感谢您注意到这一点。这非常受dplyr::select 的启发!我希望有 tidyvese 经验的用户也希望我的功能能够以同样的方式工作!如果我能支持那就太好了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-10
  • 2011-05-24
  • 1970-01-01
  • 2021-03-07
  • 1970-01-01
  • 2013-12-01
  • 2013-01-22
相关资源
最近更新 更多