What does !! operator mean in R



谁能解释一下,我们为什么需要rlang中的!!!!!{{}}算子?我想了解更多关于准报价的知识,但是一无所获。

我已经读了几个关于Stack上的花括号操作符的帖子,并且理解了当我们将dataframe的变量(或对象的其他子对象)传递给函数时,我们使用{{。但是在阅读了quote/unquote之后,我对所有这些操作符及其用法完全感到困惑。

为什么我们需要它,为什么有些函数在没有它的情况下不读取参数,最后,它们实际上是如何工作的?

如果你把答案用最简单的方式,甚至我都能理解(也许有例子?),我会很感激。

!!{{操作符是占位符,用于标记变量已被引用。通常只有当你打算用tidyverse编程时才需要它们。tidyverse喜欢利用NSE(非标准评估)来减少重复的数量。最常见的应用是"data.frame"类,其中表达式/符号在搜索其他作用域之前在data.frame的上下文中求值。为了使其工作,一些特殊的函数(如包dplyr)的参数被引用。引用表达式是保存组成表达式的符号并防止求值(在tidyverse上下文中,他们使用"quosures",它与引用表达式类似,只是它包含对生成表达式的环境的引用)。虽然NSE非常适合交互式使用,但它的编程难度明显更大。让我们考虑dplyr::select

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union

iris <- as_tibble(iris)

my_select <- function(.data, col) {
select(.data, col) 
}

select(iris, Species)
#> # A tibble: 150 × 1
#>    Species
#>    <fct>  
#>  1 setosa 
#>  2 setosa 
#>  3 setosa 
#>  4 setosa 
#>  5 setosa 
#>  6 setosa 
#>  7 setosa 
#>  8 setosa 
#>  9 setosa 
#> 10 setosa 
#> # … with 140 more rows
my_select(iris, Species)
#> Error: object 'Species' not found

我们遇到一个错误,因为在my_select的范围内col参数使用标准求值和求值无法找到名为Species的变量。

如果我们尝试在全局环境中创建一个变量,我们会看到函数工作-但它不符合tidyverse的启发式。事实上,他们会提示你这是一个模棱两可的用法。

Species <- "Sepal.Width"
my_select(iris, Species)
#> Note: Using an external vector in selections is ambiguous.
#> ℹ Use `all_of(col)` instead of `col` to silence this message.
#> ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
#> This message is displayed once per session.
#> # A tibble: 150 × 1
#>    Sepal.Width
#>          <dbl>
#>  1         3.5
#>  2         3  
#>  3         3.2
#>  4         3.1
#>  5         3.6
#>  6         3.9
#>  7         3.4
#>  8         3.4
#>  9         2.9
#> 10         3.1
#> # … with 140 more rows

为了解决这个问题,我们需要防止用enquo()求值,用!!反引号,或者直接用{{

my_select2 <- function(.data, col) {
col_quo <- enquo(col)
select(.data, !!col_quo) #attempting to find whatever symbols were passed to `col` arugment
}
#' `{{` enables the user to skip using the `enquo()` step.
my_select3 <- function(.data, col) {
select(.data, {{col}}) 
}

my_select2(iris, Species)
#> # A tibble: 150 × 1
#>    Species
#>    <fct>  
#>  1 setosa 
#>  2 setosa 
#>  3 setosa 
#>  4 setosa 
#>  5 setosa 
#>  6 setosa 
#>  7 setosa 
#>  8 setosa 
#>  9 setosa 
#> 10 setosa 
#> # … with 140 more rows
my_select3(iris, Species)
#> # A tibble: 150 × 1
#>    Species
#>    <fct>  
#>  1 setosa 
#>  2 setosa 
#>  3 setosa 
#>  4 setosa 
#>  5 setosa 
#>  6 setosa 
#>  7 setosa 
#>  8 setosa 
#>  9 setosa 
#> 10 setosa 
#> # … with 140 more rows

总之,如果您试图以编程方式应用NSE,您实际上只需要!!{{或者用这门语言编程。

!!!用于将某种类型的列表/向量拼接到某个引用表达式的参数中。

library(rlang)
quo_let <- quo(paste(!!!LETTERS))
quo_let
#> <quosure>
#> expr: ^paste("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L",
#>           "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y",
#>           "Z")
#> env:  global
eval_tidy(quo_let)
#> [1] "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"

由reprex包(v2.0.1)在2021-08-30创建

非标准求值(NSE)通常与tidyverse/dplyr一起使用,但是大多数人在每天加载包时都会遇到它。

a <- "rlang"
print(a)               # Standard evaluation: the expression a is replace by its value
# [1] "rlang"
library(a)             # Non-standard evaluation: the expression a is used as-is
# Error in library(a) : there is no package called ‘a’
那么,如何加载一个动态指定的包呢?在这里,我们将使用准报价进行演示。(在实际代码中,我建议使用library(a, character.only=TRUE)。)

在base R中,您可以使用bquote()动态构造表达式,然后对其求值。

myexpr <- bquote(library(.(a)))      # myexpr will now be library("rlang")
eval(myexpr)                         # rlang is now loaded

rlang提供了额外的工具来操作表达式。一般来说,它们让你比基本的R工具更具表现力。!!的行为类似于上面的:

myexpr <- rlang::expr(library(!!a))  # Same as above, myexpr is now library("rlang")

您可以使用rlang::expr!!来构造任何表达式,以便将来求值。

x <- rlang::expr(mtcars)
y <- rlang::expr(mpg > 30)
z <- rlang::expr(disp)
rlang::expr(subset(!!x, !!y, !!z))   # Constructs subset(mtcars, mpg > 30, disp)

当您有很多参数时,您可以将它们放在一个列表中并使用!!!快捷方式。上面的表达式可以用

复制
l <- rlang::exprs(mtcars, mpg > 30, disp)   # Note the s on exprs
rlang::expr(subset(!!!l))                   # Also builds subset(mtcars, mpg > 30, disp)

{{运算符是最难解释的,需要引证。

R中的表达式是一等对象,这意味着它们可以被传递给函数,由函数返回等等。但是,用rlang::expr创建的表达式总是在其直接上下文中求值。考虑,

a <- 10
x <- rlang::expr(a+5)
f <- function(y) {
a <- 5
eval(y)
}
f(x)     # What does this return?

即使表达式x捕获了a+5,a的值在表达式求值之前发生了变化。引用捕获表达式和定义表达式的环境。该环境总是用于计算该表达式。

a <- 10
x <- rlang::quo(a+5)    # Quosure = expression + environment where a == 10
f <- function(y) {
a <- 5
eval_tidy(y)          # Instead of simple eval()
}
f(x)                    # 15 = 10 + 5

捕获表达式或引用可以通过使用exprquoen-版本移动到函数内部:

f <- function(y) {
a <- 5
eval(rlang::enexpr(y))
}
g <- function(y) {
a <- 5
eval_tidy(rlang::enquo(y))
}

允许用户将表达式直接传递给函数

a <- 10
f(a*4)    # 20 = 5*4,  because f captures expressions, and a is overwritten
g(a*4)    # 40 = 10*4, because g captures quosures

综上所述,{{x}}只是!!enquo(x)的简写符号。

相关内容

  • 没有找到相关文章

最新更新