何时在dplyr中使用“Do”函数

20
我了解到Do函数用于将函数应用于每个组。
例如,如果我想从变量Index的"A"、"C"和"I"类别中提取前2行,则可以使用以下语法。
t <- mydata %>% filter(Index %in% c("A", "C", "I")) %>% group_by(Index) %>% do(head(.,2))

我知道在按索引分组之后,使用do函数来计算每个组的head(.,2)。
然而,在某些情况下,根本不需要使用do。例如,要计算变量Y2014按变量Index分组的平均值,应该使用以下代码。
t <- mydata %>% group_by(Index) %>% do(summarise(Mean_2014 = mean(Y2014)))

然而,上述语法返回错误。
Error in mean(Y2014) : object 'Y2014' not found

但是,如果我从语法中删除do,它会返回我想要的结果。

t <- mydata %>% group_by(Index) %>% summarise(Mean_2014 = mean(Y2014))

我对dplyr中do函数的使用感到困惑。它对我来说似乎并不一致。什么情况下应该使用do,什么情况下不应该使用do?为什么在第一种情况下要使用do,但在第二种情况下不需要?


2
对于 mean,您可以直接使用 summarise 而不需要使用 do。 对于第一个案例,也可以使用 slice 而不是 do,即 %>% slice(1:2)。 对于许多其他的 tidyverse 函数,如 map 等,很少需要使用 do。 一个可能的用例是在按组构建某些模型时。 - akrun
请参见例如3. 语法 / 4. do()这个(优秀的)答案中。do的另一个具体案例:在dplyr中导致长度不等于1或组长度的分组操作。相关dplyr问题 - Henrik
1个回答

16

问题下面的评论讨论了在很多情况下,您可以在dplyr或相关包中找到避免使用do的替代方法,并且问题中的示例都属于这种情况。但是,为了直接回答问题而不是通过替代方案:

使用和不使用do的区别

在数据框的上下文中,使用do和不使用do的关键区别在于:

  1. 没有自动插入点号do内部的代码将不会自动将点号插入第一个参数中。例如,在问题中,do(summarise(Mean_2014 = mean(Y2014)))的代码将变成do(summarise(., Mean_2014 = mean(Y2014))),需要加上点号,因为点号不会自动插入。这是因为do%>% 的右侧函数,而不是summarize。虽然这很重要,以便我们在需要时插入点号,但如果目的仅是避免自动将点号插入第一个参数,则我们可以使用大括号来获得该效果:whatever %>% { myfun(arg1, arg2) }也不会自动将点号插入myfun调用的第一个参数。

  2. 尊重group_by只有专门编写以尊重 group_by 的函数才会这样做。这里有两个问题。(1)只有专为group_by编写的函数才会针对每个组运行一次。 mutatesummarizedo是每个组运行一次的函数的示例(还有其他函数)。(2)即使函数对每个组运行一次,仍然存在如何处理点号的问题。我们关注两种情况(不是完整列表):(i)如果不使用do,则如果在表达式中的函数调用中使用点号作为参数,则它将引用忽略group_by的整个输入。这可能是magrittr的点替换规则的结果,因为它对group_by一无所知。(ii)另一方面,在do中,点始终指当前组的行。例如,请比较这两个输出并注意点在哪里运行:在使用do的第一种情况下,点引用3行,在第二种情况下引用所有6行。这是尽管summarize尊重group_by,因为它对每个组运行一次。

BOD$g <- c(1, 1, 1, 2, 2, 2)
BOD %>% group_by(g) %>% do(summarize(., nr = nrow(.)))
## # A tibble: 2 x 2
## # Groups: g [2]
##       g    nr
##   <dbl> <int>
## 1  1.00     3
## 2  2.00     3

BOD %>% group_by(g) %>% summarize(nr = nrow(.))
## # A tibble: 2 x 2
##       g    nr
##   <dbl> <int>
## 1  1.00     6
## 2  2.00     6

更多信息请参见?do

问题中的代码

现在我们将浏览问题中的代码。由于问题中从未定义mydata,因此我们使用下面的第一行代码来定义它以便提供具体示例。

mydata <- data.frame(Index = rep(c("A", "C", "I"), each = 3), Y2014 = 1)

mydata %>% 
       filter(Index %in% c("A", "C", "I")) %>% 
       group_by(Index) %>% 
       do(head(., 2))

## # A tibble: 6 x 2
## # Groups: Index [3]
##   Index  Y2014
##   <fctr> <dbl>
## 1 A       1.00
## 2 A       1.00
## 3 C       1.00
## 4 C       1.00
## 5 I       1.00
## 6 I       1.00

上面的代码对于每个三组生成2行,总共6行。如果省略了do关键字,那么就会忽略group_by,并且仅生成两行数据,其中点号被视为输入的全部9行,而不仅仅是每个分组的一部分。(在这种特定情况下,dplyr提供了自己的head替代方法,以避免这些问题,但为了阐明一般性原则,我们坚持使用问题中的代码。)

问题中的以下代码会生成一个错误,因为点插入没有在do内完成,所以应该作为summarize的第一个参数即数据框输入缺失:

mydata %>% 
       group_by(Index) %>% 
       do(summarise(Mean_2014 = mean(Y2014)))
## Error in mean(Y2014) : object 'Y2014' not found

如果我们从上面的代码中删除do,如问题中的最后一行代码所示,则它可以工作,因为点插入已执行。或者,如果我们添加点do(summarise(., Mean_2014 = mean(Y2014)))也可以工作,尽管在这种情况下do似乎是多余的,因��summarize已经遵守group_by,所以没有必要将其包装在do中。

mydata %>% 
       group_by(Index) %>% 
       summarise(Mean_2014 = mean(Y2014))

## # A tibble: 3 x 2
##   Index  Mean_2014
##   <fctr>     <dbl>
## 1 A           1.00
## 2 C           1.00
## 3 I           1.00

有趣。换句话说,只有使用tidyverse原生函数才能正确工作,而不需要使用“do”吗?也就是说,这个可以完全正常运行 BOD %>% group_by(g) %>% do(summarize(., nr = n()) 。问题在于nrow(.)是一个“base”函数吗? - ℕʘʘḆḽḘ
1
问题在于如何处理点。n()不涉及点。 - G. Grothendieck
好的,我明白了。总之,如果您调用一个涉及到数据框的函数时使用了“.”,请始终使用do。这样,我们可以确信所传递的数据框是正确的子组。这样讲清楚了吗? - ℕʘʘḆḽḘ
但是还有一种情况 gap_fits <- gap_nested %>% mutate(fit = map(data, ~ lm(lifeExp ~ year, data = .x))),就像 https://jennybc.github.io/purrr-tutorial/ls13_list-columns.html 中所示。在这种情况下,似乎只使用 .x 而不需要 do 就可以了。这有点不一致,你觉得呢? - ℕʘʘḆḽḘ
1
.x是以公式符号表示的函数参数。dot是管道的左侧。 - G. Grothendieck
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接