之间有什么区别。和.data?

What is the difference between . and .data?

我正在尝试加深对 dplyr 中使用点号 (.) 和 dplyr 中使用 .data 代词的理解。我正在编写的激发此 post 的代码看起来像这样:

cat_table <- tibble(
  variable = vector("character"), 
  category = vector("numeric"), 
  n        = vector("numeric")
) 

for(i in c("cyl", "vs", "am")) {
  cat_stats <- mtcars %>% 
    count(.data[[i]]) %>% 
    mutate(variable = names(.)[1]) %>%
    rename(category = 1)
  
  cat_table <- bind_rows(cat_table, cat_stats)
}
# A tibble: 7 x 3
  variable category     n
  <chr>       <dbl> <dbl>
1 cyl             4    11
2 cyl             6     7
3 cyl             8    14
4 vs              0    18
5 vs              1    14
6 am              0    19
7 am              1    13

代码做了我想做的,并不是这个问题的重点。我只是提供它作为上下文。

我正在努力加深对为什么它做我想做的事情的理解。更具体地说,为什么我不能交替使用 ..data。我已经阅读了 Programming with dplyr 文章,但我想在我看来,..data 都只是表示“到目前为止我们的结果在管道中”。但是,似乎我过于简化了我对它们如何工作的心智模型,因为当我在下面的 names() 中使用 .data 时出现错误:

mtcars %>% 
  count(.data[["cyl"]]) %>% 
  mutate(variable = names(.data)[1])
Error: Problem with `mutate()` input `variable`.
x Can't take the `names()` of the `.data` pronoun
ℹ Input `variable` is `names(.data)[1]`.
Run `rlang::last_error()` to see where the error occurred.

当我在 count():

中使用 . 时,我得到了(对我来说)意想不到的结果
mtcars %>% 
  count(.[["cyl"]]) %>% 
  mutate(variable = names(.)[1])
  .[["cyl"]]  n   variable
1          4 11 .[["cyl"]]
2          6  7 .[["cyl"]]
3          8 14 .[["cyl"]]

我怀疑它与“请注意 .data 不是数据框;它是一个特殊的结构,一个代词,允许您直接访问当前变量,使用 .data$x 或间接地使用 .data[[var]]。不要指望其他函数可以使用它,”来自 Programming with dplyr 一文。这告诉我什么 .data 不是 -- 数据框 -- 但是,我仍然不确定 .data 是什么 以及它与 ..

的区别

我试过这样计算:

mtcars %>% 
  count(.data[["cyl"]]) %>% 
  mutate(variable = list(.data))

但是,结果 <S3: rlang_data_pronoun> 对我来说没有任何意义可以帮助我理解。如果有人对此有更好的了解,我将不胜感激。谢谢!

首先,我认为 .data 的意图有点令人困惑,直到人们还考虑了它的兄弟代词 .env

.magrittr::%>% 设置和使用的东西;因为 dplyr re-exports 它就在那里。每当你引用它时,它都是一个真实的对象,所以 names(.)nrow(.) 等都按预期工作。它确实反映了管道中到目前为止的数据。

另一方面,

.data 是在 rlang 中定义的,目的是消除符号解析的歧义。与 .env 一起,它使您可以非常清楚地知道要在何处解析特定符号(当预期存在歧义时)。来自?.data,我认为这是一个清晰的对比:

disp <- 10
mtcars %>% mutate(disp = .data$disp * .env$disp)
mtcars %>% mutate(disp = disp * disp)

然而,如帮助页面所述,.data(和.env)只是一个“代词”(我们有动词,所以现在我们也有代词),所以它只是一个指针,用于解释应该解析符号的整洁内部结构。这只是某种暗示。

所以你的说法

both . and .data just mean "our result up to this point in the pipeline."

不正确:. 表示到目前为止的数据,.data 只是对内部的声明性提示。


考虑另一种思考 .data 的方式:假设我们有两个函数可以完全消除引用符号所针对的环境的歧义:

  • get_internally,这个符号必须总是引用一个列名,如果列不存在,它不会接触到封闭环境;和
  • get_externally,此符号必须始终引用封闭环境中的 variable/object,它永远不会匹配列。

那样的话,翻译上面的例子,可以用

disp <- 10
mtcars %>%
  mutate(disp = get_internally(disp) * get_externally(disp))

在那种情况下,get_internally 不是框架似乎更明显,因此您不能调用 names(get_internally) 并期望它做一些有意义的事情(除了 NULL ).就像 names(mutate).

所以不要将 .data 视为对象,而应将其视为消除符号环境歧义的机制。我认为它使用的 $ 既是 terse/easy-to-use 又是 absolutely-misleading:它不是 list-like 或 environment-like 对象,即使它正在如此对待。

顺便说一句:可以为 $ 编写任何 S3 方法,使任何 classed-object 看起来像 frame/environment:

`$.quux` <- function(x, nm) paste0("hello, ", nm, "!")
obj <- structure(0, class = "quux")
obj$r2evans
# [1] "hello, r2evans!"
names(obj)
# NULL

$ 访问器的存在并不总是意味着该对象是 frame/env。)

比较 mtcars %>% count(.data[["cyl"]])mtcars %>% count(.[["cyl"]])

mtcars %>% count(.[["cyl"]])
  .[["cyl"]]  n
1          4 11
2          6  7
3          8 14


mtcars %>% count(.data[["cyl"]])
  cyl  n
1   4 11
2   6  7
3   8 14

. 字面意思就是之前的结果。所以第一个类似于:

. <- mtcars
count(., .[["cyl"]])

第二个是shorthand,用于通过字符串“cyl”查找变量,并将前一个结果作为变量的搜索路径。例如,假设您拼错了变量名称:

mtcars %>% count(.[["cyll"]])
   n
1 32

mtcars %>% count(.data[["cyll"]])
Error: Must group by variables found in `.data`.
* Column `cyll` is not found.

使用 . 不会引发错误,因为索引到 non-existing 列是 returns NULL.[=20 的有效 base-R 操作=]

使用 .data 会抛出异常,因为使用 non-existent 变量:

mtcars %>% count(cyll)

也抛出。

.变量来自magrittr,与管道有关。它的意思是“通过管道输送到这个表达式中的值”。通常对于管道,前一个表达式的值成为下一个表达式中的参数 1,但这为您提供了一种在其他参数中使用它的方法。

.data 对象对于 dplyr 是特殊的(虽然它是在 rlang 包中实现的)。它本身没有任何有用的值,但在 dplyr“整洁评估”框架中进行评估时,它在许多方面的作用就好像它是 dataframe/tibble 的值一样。当有歧义时使用它:如果你有一个与数据框列同名 foo 的变量,那么 .data$foo 表示它是你想要的列(如果找不到它会给出错误,不像 data$foo 会给出 NULL)。您也可以使用 .env$foo,表示忽略该列并从调用环境中获取变量。

.data.env 都特定于 dplyr 函数和其他使用相同特殊计算方案的函数,而 . 是一个常规变量,可用于任何函数。

编辑添加:你问为什么 names(.data) 不起作用。如果@r2evans 优秀的答案还不够,这里有一个不同的看法:我怀疑问题是 names() 不是 dplyr 函数,即使 names.rlang_fake_data_pronoun 是一个方法rlang。所以表达式 names(.data) 是使用常规求值而不是整洁求值来求值的。该方法不知道要查看什么数据框,因为在那个上下文中没有。

理论层面:

. 是 magrittr 代词。它表示通过 %>%.

管道输入的整个输入(与 dplyr 一起使用时通常是数据帧)

.data 是整洁的评估代词。从技术上讲它根本不是一个数据框架,它是一个评估环境。

实用层面:

.永远不会被dplyr修改。它保持不变,直到到达下一个管道表达式。另一方面,.data 始终是最新的。这意味着您可以引用以前创建的变量:

mtcars %>%
  mutate(
    cyl2 = cyl + 1,
    am3 = .data[["cyl2"]] + 10
  )

在分组数据框的情况下,您还可以参考列切片

mtcars %>%
  group_by(cyl) %>%
  mutate(cyl2 = .data[["cyl"]] + 1)

如果您改用 .[["cyl"]],整个数据框将被子集化,您将收到一个错误,因为输入大小与组切片大小不同。棘手!