我刚刚遇到dplyr
的一些奇怪行为,其中summarize
一直引用以前组中的对象。
这里有一个简单的可重复的例子来说明令人惊讶的行为:
library(dplyr, warn.conflicts = FALSE)
tibble(x = rep(letters[1:3], times = 4),
y = rnorm(12)) %>%
group_by(x) %>%
summarize(z1 = sum(y),
z2 = {
attr(y, "test") <- "test"
sum(y)
})
#> # A tibble: 3 × 3
#> x z1 z2
#> <chr> <dbl> <dbl>
#> 1 a 0.602 0.602
#> 2 b 1.22 0.602
#> 3 c -0.310 0.602
创建于2022-10-31由reprex包(v2.0.1(
我期望z1
和z2
是相同的。我不明白为什么为向量y
设置属性意味着在以后的迭代中,对y
的"正确"元素的引用会被遮蔽。
通过在最后一行中使用sum(.data$y)
可以很容易地解决这个问题,但我想了解summarize
的非标准评估中的作用域规则。任何指向有用文档或解释当前行为在tidyverse非标准评估框架中有意义的指针都将受到赞赏。
我使用的是R 4.1.1和dplyr 1.0.7。
这是一个与作用域有关的问题。如果写入summarize
中的变量y
,则数据的第一组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
只要我们从本地帧中删除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
或者更好的是,不要写入与数据帧中的变量同名的局部变量:
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
创建于2022-10-31,reprex v2.0.2