在具有公式接口的函数中使用半引号

问题描述 投票:0回答:2

我想编写一个可以接受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)

我如何修改原始功能,使其能够与上述所有输入输入方式以及所使用的两种功能一起使用?

r tidyverse rlang quasiquotes
2个回答
2
投票

rlang的基本原理是,您可以控制何时要通过!!{{}}运算符评估值。您似乎想要创建一个函数,该函数在同一参数中全部包含字符串,符号和(可能是经过评估的)表达式。对于ensym,使用符号或裸露的字符串实际上很容易,但是想要返回colnames(mtcars)[9]之类的代码,必须在返回字符串之前先进行[[evaulated)。这可能会造成混乱。例如,运行以下命令时,您期望的行为是什么?

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])

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

0
投票
在混合标准和非标准评估时,我必须同意@MrFlick和其他人关于固有歧义的观点。 (我也在您的similar question from a while ago中指出了这一点。)
© www.soinside.com 2019 - 2024. All rights reserved.