Tidyeval Qua vs Enquo

dea*_*ann 1 r tidyverse tidyeval

我偶然发现了这种行为,并不太了解。有人可以请问一下吗?

我写了下面的函数,它给出以下错误:

> MyFilter <- function(data, filtersVector) {
    filtersVector <- quo(filtersVector)
    result <- data %>% filter(Species %in% !!filtersVector)
    result
  }

> MyFilter(iris, c("setosa", "virginica"))
Error in filter_impl(.data, quo) : 
Evaluation error: 'match' requires vector arguments.
Run Code Online (Sandbox Code Playgroud)

但是,如果我以以下方式对其进行修改,则它将按预期工作:

> MyFilter <- function(data, filtersVector) {
    otherName <- quo(filtersVector)
    result <- data %>% filter(Species %in% !!otherName)
    result
  }

> MyFilter(iris, c("setosa", "virginica"))
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
1            5.1         3.5          1.4         0.2    setosa
2            4.9         3.0          1.4         0.2    setosa
3            4.7         3.2          1.3         0.2    setosa
4            4.6         3.1          1.5         0.2    setosa
5            5.0         3.6          1.4         0.2    setosa
6            5.4         3.9          1.7         0.4    setosa
Run Code Online (Sandbox Code Playgroud)

我也意识到在一个函数中我应该使用enqou它并且它可以正常工作。

> MyFilter <- function(data, filtersVector) {
        filtersVector<- enquo(filtersVector)
        result <- data %>% filter(Species %in% !!filtersVector)
        result
      }

> MyFilter(iris, c("setosa", "virginica"))
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
1            5.1         3.5          1.4         0.2    setosa
2            4.9         3.0          1.4         0.2    setosa
3            4.7         3.2          1.3         0.2    setosa
4            4.6         3.1          1.5         0.2    setosa
5            5.0         3.6          1.4         0.2    setosa
6            5.4         3.9          1.7         0.4    setosa
Run Code Online (Sandbox Code Playgroud)

但是,我仍然对上述行为感到困惑,不胜感激。

Lio*_*nry 7

TLDR:在第一个版本中,您创建了一个自引用(一个指向自身的符号)。其他版本也可以使用,但是实际上您不需要在这里使用quasure和捕获参数,因为您没有在引用数据框列。这也解释了为什么quo()enquo()版本都相同的原因。您可以按常规方式传递参数,而无需使用任何引号,尽管取消引用还是个好主意,!!以避免任何数据掩盖错误。

您可以qq_show()filter()调用周围使用,以发现语法差异:

MyFilter <- function(data, filtersVector) {
  filtersVector <- quo(filtersVector)

  rlang::qq_show(
    result <- data %>% filter(Species %in% !!filtersVector)
  )
}

MyFilter(iris, c("setosa", "virginica"))
#> result <- data %>% filter(Species %in% (^filtersVector))
Run Code Online (Sandbox Code Playgroud)

因此,我们在这里要求filter()查找Species与元素匹配的行filtersVectorfiltersVector数据框中没有任何列,因此它在quosure环境中查找定义。您创建了一个带有quosure quo(),记录你的表达(在这种情况下,一个符号filtersVector)和你的 envionment(你的函数的环境)。因此它查找一个filtersVector对象,其中包含一个指向自身的符号。它仅被评估一次,因此没有无限循环,但是您实际上正在尝试将向量与符号进行比较,这是类型错误:

"setosa" %in% quote(filtersVector)
#> Error in match(x, table, nomatch = 0L) :
#> 'match' requires vector arguments
Run Code Online (Sandbox Code Playgroud)

在第二次尝试中,您给quasure取了另一个名字。它之所以起作用filtersVector,是因为在您的函数环境中,它仍然表示传递给它的参数(向量)。

在第三次尝试中,您将使用enquo()此时间。而不是捕获表达式和环境,而是捕获enquo()函数用户的表达式和环境。让我们qq_show()再次使用以查看区别:

MyFilter <- function(data, filtersVector) {
  filtersVector<- enquo(filtersVector)

  rlang::qq_show(
    data %>% filter(Species %in% !!filtersVector)
  )
}

MyFilter(iris, c("setosa", "virginica"))
#> data %>% filter(Species %in% (^c("setosa", "virginica")))
Run Code Online (Sandbox Code Playgroud)

现在,Quasure包含一个调用,该调用可以在现场创建矢量,这很%in%容易理解。

请注意,尽管如此,您实际上并未真正引用数据框列。您正在传递矢量。这意味着您根本不需要任何保证,也不需要捕获传递给参数的表达式。enquo()仅可用于将评估延迟到最后,因此可以在数据帧内进行评估。如果quo()enquo()版本产生相同的结果,则表明您根本不需要引用。由于不需要它们,让我们通过消除方程的简化来简化函数:

MyFilter <- function(data, filtersVector) {
  data %>% filter(Species %in% filtersVector)
}

MyFilter(iris, c("setosa", "virginica"))
#> # A tibble: 100 x 5
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#>           <dbl>       <dbl>        <dbl>       <dbl> <fct>  
#>  1          5.1         3.5          1.4         0.2 setosa 
#>  2          4.9         3            1.4         0.2 setosa 
#>  3          4.7         3.2          1.3         0.2 setosa 
#>  4          4.6         3.1          1.5         0.2 setosa 
#>  5          5           3.6          1.4         0.2 setosa 
#>  6          5.4         3.9          1.7         0.4 setosa 
#>  7          4.6         3.4          1.4         0.3 setosa 
#>  8          5           3.4          1.5         0.2 setosa 
#>  9          4.4         2.9          1.4         0.2 setosa 
#> 10          4.9         3.1          1.5         0.1 setosa 
#> # ... with 90 more rows
Run Code Online (Sandbox Code Playgroud)

有用!但是,如果数据框包含一filtersVector列怎么办?它比环境中的对象优先:

iris %>%
  mutate(filtersVector = "parasite vector") %>%
  MyFilter(c("setosa", "virginica"))
#> # A tibble: 0 x 6
#> # ... with 6 variables: Sepal.Length <dbl>, Sepal.Width <dbl>,
#> #   Petal.Length <dbl>, Petal.Width <dbl>, Species <fct>, filtersVector <chr>
Run Code Online (Sandbox Code Playgroud)

因此,取消引用仍然是一个好主意,因为这将立即评估向量并将其粘贴在过滤器表达式中。它不能再被列掩盖。内联显示为qq_show()

MyFilter <- function(data, filtersVector) {
  rlang::qq_show(
    data %>% filter(Species %in% !!filtersVector)
  )
}
MyFilter(iris2, c("setosa", "virginica"))
#> data %>% filter(Species %in% <chr: "setosa", "virginica">)
Run Code Online (Sandbox Code Playgroud)