dplyr: by + do.call("rbind")するならdoすべし

"グループ毎にXXXしたい"という時のXXXの内容が複雑な場合、関数として処理を切り出したくて、以下のように書いてみた。
処理の内容は"Speciesのグループ毎にその最終行のPetal.Lengthの値を取得する"だ。

> process <- function(df){
+   df[nrow(df),]$Petal.Length
+ }
> iris %>% 
+   group_by(Species) %>%
+   summarize(result = process(.))
Source: local data frame [3 x 2]

     Species result
      (fctr)  (dbl)
1     setosa    5.1
2 versicolor    5.1
3  virginica    5.1

・・・が、この結果は間違いで正しくは以下のようにdo関数をかませて処理を書かねばならない(adviced by hoxo_m)。

iris %>% 
  group_by(Species) %>% 
  do(result = process(.)) %>%
  summarize(Species, result)  

これはbaseの関数を使っても一応かけて、以下の結果と等価だが、明らかに上の書き方の方が筋が良い。したがって

  • by + do.call("rbind") = do

として覚えることにする。

> iris %>% 
+   by(iris$Species, FUN=function(df){
+     group <- head(df$Species, 1)
+     data.frame(group, value=df[nrow(df),]$Petal.Length)
+   }) %>% do.call("rbind", .)
                group value
setosa         setosa   1.4
versicolor versicolor   4.1
virginica   virginica   5.1

結局do関数とは?

HELPを見ても意味が分からなかったので、例で覚える。要するに

  • 各グループに対するめんどくさ系な処理(単一の列ではなく、data.frame全体を使ってやる処理など)を書く

という印象だ。

> by_cyl <- group_by(mtcars, cyl)
> do(by_cyl, head(., 2))
Source: local data frame [6 x 11]
Groups: cyl [3]

    mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
  (dbl) (dbl) (dbl) (dbl) (dbl) (dbl) (dbl) (dbl) (dbl) (dbl) (dbl)
1  22.8     4 108.0    93  3.85 2.320 18.61     1     1     4     1
2  24.4     4 146.7    62  3.69 3.190 20.00     1     0     4     2
3  21.0     6 160.0   110  3.90 2.620 16.46     0     1     4     4
4  21.0     6 160.0   110  3.90 2.875 17.02     0     1     4     4
5  18.7     8 360.0   175  3.15 3.440 17.02     0     0     3     2
6  14.3     8 360.0   245  3.21 3.570 15.84     0     0     3     4
> group_by(mtcars, cyl) %>% summarize(n())
Source: local data frame [3 x 2]

    cyl   n()
  (dbl) (int)
1     4    11
2     6     7
3     8    14