在 dplyr::summarize() 中设置向量的属性有什么影响?

con*_*-ae 7 r dplyr non-standard-evaluation

我刚刚遇到了一些奇怪的行为,其中dplyr不断summarize引用前一组中的对象。

\n

这是一个简单的可重复示例来说明令人惊讶的行为:

\n
library(dplyr, warn.conflicts = FALSE)\ntibble(x = rep(letters[1:3], times = 4),\n       y = rnorm(12)) %>%\n  group_by(x) %>%\n  summarize(z1 = sum(y),\n            z2 = {\n              attr(y, "test") <- "test"\n              sum(y)\n            })\n#> # A tibble: 3 \xc3\x97 3\n#>   x         z1    z2\n#>   <chr>  <dbl> <dbl>\n#> 1 a      0.602 0.602\n#> 2 b      1.22  0.602\n#> 3 c     -0.310 0.602\n
Run Code Online (Sandbox Code Playgroud)\n

由reprex 包于 2022 年 10 月 31 日创建(v2.0.1)

\n

我期望的是z1z2是相同的。我不明白为什么为向量设置属性y意味着在以后的迭代中,对“正确”元素的引用y被隐藏。

\n

通过在最后一行使用可以轻松解决该问题sum(.data$y),但我想了解summarize. 任何指向有用文档或解释为什么当前行为在 tidyverse 非标准评估框架中有意义的指针都是值得赞赏的。

\n
\n

我正在使用 R 4.1.1 和 dplyr 1.0.7。

\n

All*_*ron 4

这是一个与范围界定相关的问题。如果您写入yinside变量summarize,则数据y变量的第一个分组将被复制到一个名为 的局部变量中y,该变量与y数据框中的 不同。因为它是局部变量,所以它在y传递的数据帧中之前的搜索路径上找到。由于内部后续组的计算使用相同的环境summarize,因此该局部变量对于每个组都保持不变。

如果我们这样做,我们就可以看到这一点:

library(dplyr, warn.conflicts = FALSE)

set.seed(1)

tibble(x = rep(letters[1:3], times = 4),
       y = rnorm(12)) %>%
  group_by(x) %>% 
  summarize(z1 = sum(y),
            z2 = {
              y <- y
              sum(y)
            }) 
#> # A tibble: 3 x 3
#>   x         z1    z2
#>   <chr>  <dbl> <dbl>
#> 1 a      1.15   1.15
#> 2 b      2.76   1.15
#> 3 c     -0.690  1.15
Run Code Online (Sandbox Code Playgroud)

只要我们y从本地框架中删除变量的本地副本,这种情况就不会发生:

library(dplyr, warn.conflicts = FALSE)

set.seed(1)

tibble(x = rep(letters[1:3], times = 4),
       y = rnorm(12)) %>%
  group_by(x) %>% 
  summarize(z1 = sum(y),
            z2 = {
              attr(y, "test") <- "test"
              x <- sum(y)
              rm(y)
              x
            }) 
#> # A tibble: 3 x 3
#>   x         z1     z2
#>   <chr>  <dbl>  <dbl>
#> 1 a      1.15   1.15 
#> 2 b      2.76   2.76 
#> 3 c     -0.690 -0.690
Run Code Online (Sandbox Code Playgroud)

或者更好的是,不要写入与数据框中的变量同名的局部变量:

tibble(x = rep(letters[1:3], times = 4),
       y = rnorm(12)) %>%
  group_by(x) %>% 
  summarize(z1 = sum(y),
            z2 = {
              new_y <- y
              attr(new_y, "test") <- "test"
              sum(new_y)
            }) 
#> # A tibble: 3 x 3
#>   x         z1     z2
#>   <chr>  <dbl>  <dbl>
#> 1 a      1.15   1.15 
#> 2 b      2.76   2.76 
#> 3 c     -0.690 -0.690
Run Code Online (Sandbox Code Playgroud)

创建于 2022 年 10 月 31 日,使用reprex v2.0.2

  • @const-ae,您可以以与包围循环的大括号相同的方式来考虑 summarise 中的大括号,而不是包围函数的大括号。函数有自己的评估框架,并且与调用框架隔离,尽管它可以访问它。循环在同一评估框架中多次运行相同的代码(因此可以覆盖变量),这就是“summarize”中的代码在这里所做的事情。就好像您认为它应该表现为可以访问“y”的匿名函数,但事实并非如此。 (2认同)
  • @const-ae 我不知道有关“summarise”内部范围的任何文档,但“summarise”的参数被捕获为 _quosures_,即具有关联环境的表达式。这项工作主要在“dplyr:::summarise_cols”内部完成,如果您跟踪逻辑,您会发现您的代码确实在同一环境中循环运行。 (2认同)
  • @const-ae 至于“为什么?”,我不确定,但对于我作为一个普通的 R 用户来说,这是我所期望的行为。如果想避免名称冲突,可以运行匿名函数,即 `z2 = (function(){attr(y, 'test') &lt;- 'test'; sum(y)})()`,同样如此给出了我在 R 中期望的行为。 (2认同)