【问题标题】:Tidyeval quo vs enquoTidyeval quo vs enquo
【发布时间】:2019-04-03 16:16:24
【问题描述】:

我偶然发现了这种行为,但不太理解。有人能解释一下吗?

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

> 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.

但是,如果我按以下方式修改它,它会按预期工作:

> 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

我也意识到在一个函数中我应该使用 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

但是,我仍然对上述行为感到困惑,任何解释都将不胜感激。

【问题讨论】:

    标签: r tidyverse tidyeval


    【解决方案1】:

    TLDR:在第一个版本中,您创建了一个自引用(一个指向自身的符号)。其他版本有效,但您实际上不需要 quosures 或在此处捕获参数,因为您没有引用数据框列。这也解释了为什么quo()enquo() 版本的工作方式相同。您可以直接以正常方式传递参数,无需任何引用,但最好使用 !! 取消引用以避免任何数据屏蔽错误。

    您可以在filter() 调用周围使用qq_show() 来发现语法上的差异:

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

    所以在这里我们要求filter() 查找SpeciesfiltersVector 的元素匹配的行。数据框中没有 filtersVector 列,因此它会在 quosure 环境中查找定义。你已经用quo() 创建了一个quosure,它记录了你的 表达式(在本例中是一个符号filtersVector)和你的 环境(你的函数的环境)。所以它会查找一个filtersVector 对象,其中包含一个引用自身的符号。它只评估一次,因此没有无限循环,但您实际上是在尝试将向量与符号进行比较,这是一个类型错误:

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

    在您的第二次尝试中,您为 quosure 赋予了另一个名称。它现在可以工作了,因为在您的函数环境中,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")))
    

    现在,quosure 包含一个在现场创建向量的调用,%in% 完全理解。

    请注意,您实际上并没有引用数据框列。你正在传递向量。这意味着您根本不需要任何 quosure,也不需要捕获传递给参数的表达式。 enquo() 仅用于延迟评估直到最后,因此可以在数据帧内评估。如果quo()enquo() 版本产生相同的结果,这很好地表明您根本不需要任何引用。由于不需要它们,让我们通过删除方程的quosures来简化函数:

    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
    

    有效!但是如果数据框包含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>
    

    所以取消引用仍然是一个好主意,因为这将立即评估向量并将其粘贴在过滤器表达式中。它不能再被列掩盖。内联显示为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">)
    

    【讨论】:

      【解决方案2】:

      当我们传递带引号的向量字符串而不是不带引号的向量时,我们需要使用rlang 中的syms

      MyFilter <- function(data, filtersVector) {
         filtersVector <- rlang::syms(filtersVector)
          data %>% 
            filter(Species %in% !!filtersVector)
      
       }
      
      out <- MyFilter(iris, c("setosa", "virginica"))
      dim(out)
      #[1] 100   5
      

      【讨论】:

      • 我意识到有一些 tidyeval 结构。但是,我对为什么上述内容不能按预期工作很感兴趣。它是否与函数 args 处于另一个环境而不是函数中定义的 var 或其他环境有关?
      • @deann 首先,我不明白为什么你需要quoenquosym 来传递带引号的向量。只需filter(Species %in% filtersVector) 就足够了。 enquo 类似于替换 `substitute,quo/enquo 都将对象转换为 quosure。但在这种情况下,我认为不需要这些
      • 好的,我意识到这个例子很愚蠢..但是我对为什么会发生这种情况或在哪里寻找解释感兴趣?
      • @deann 构造这些函数的方式,即quo 调用enquo,如果您查看enquo,它正在调用rlang_quosubstitute。这些是用于特定目的的功能。我不确定它是否针对这种情况进行了广泛的测试
      • 糟糕,我错过了
      猜你喜欢
      • 1970-01-01
      • 2018-08-10
      • 2019-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-25
      • 2019-04-20
      • 2017-12-19
      相关资源
      最近更新 更多