【问题标题】:What does !! operator mean in R什么!! R中的运算符均值
【发布时间】:2021-10-29 05:22:04
【问题描述】:

谁能解释一下,我们需要来自rlang!!!!!{{}} 运算符吗?我试图向more 学习准引用,但没有得到任何东西。

我已经在 Stack 上看到了几篇关于 curly-curly 运算符的帖子,并且了解到当我们将数据帧的变量(或我们对象的其他子对象)传递给函数时,我们使用 {{。但是在阅读了 quote/unquote 之后,我对所有这些运算符及其用法完全感到困惑。

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

如果您以最简单的方式给出答案,我将不胜感激(也许有例子?)。

【问题讨论】:

  • "我们为什么需要它" 你需要它们是因为 tidyverse 大量使用非标准评估。作为一个不使用 tidyverse 的人,我从来没有使用过任何一个。
  • 在基础 R 中,!! 表示 logical 运算符的双重(和 !!! 三重)否定。 rlang 和其他 tidyverse 软件包已采用它用于变量的 NSE 评估。
  • 这可能是重复的,但是!存在 !使!搜索难度增加一倍。
  • 如果你读了准引文一章没看懂,不知道还能说什么。也许 dplyr 编程指南会有所帮助:dplyr.tidyverse.org/articles/programming.html.
  • @MrFlick 很好,谢谢!语言障碍是我有时无法从以非常技术性且仅对程序员友好的语言编写的文档中获取信息的原因。

标签: r tidyverse quote rlang quasiquotes


【解决方案1】:

!!{{ 运算符是用于将变量标记为已被引用的占位符。通常只有在您打算使用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 package (v2.0.1) 于 2021 年 8 月 30 日创建

【讨论】:

  • 贾斯汀,非常感谢您介绍 NSE。我会接受你对这个问题的回答,但这并不意味着它比 Artem 的好。你的答案是相辅相成的,所以他们描绘了一幅关于使用一些运算符的大而简单的画面。
【解决方案2】:

非标准评估 (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’

那么,如何加载动态指定的包?在这里,我们将使用 quasiquotation 进行演示。 (在实际代码中,我建议改用library(a, character.only=TRUE)。)

在基础 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+5a 的值也会在表达式被计算之前发生变化。 Quosures 捕获表达式和定义它们的环境。该环境始终用于评估该表达式。

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

可以使用en- 版本的exprquo 将捕获表达式或quosure 移动到函数内部:

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) 的简写符号。

【讨论】:

  • 库(a,character.only = TRUE)
  • 有些事情已经澄清,感谢您的耐心等待,Артем!
  • Artem,我接受了贾斯汀的回答,但这并不意味着你没有回答这个问题。如果我能接受其中两个,我也会接受你的。谢谢!
  • 完全没问题。我很高兴这很有帮助。我确实想提醒一下,NSE 会使代码难以阅读和维护。我在这里创建了一个简单的示例进行演示,但在实际代码中,我会使用@Roland 在他的评论中写的 library() 调用。
猜你喜欢
  • 2015-12-25
  • 2021-10-28
  • 2021-12-07
  • 2014-12-24
  • 2010-12-17
  • 2013-07-25
相关资源
最近更新 更多