dplyrのsummarise, doの使い分けについてのメモ

[r-wakalang](https://github.com/TokyoR/r-wakalang)のチャットで`do()`についての話が盛り上がったので、調べたことをまとめます（@hoxo_mさんと@teranomagiさんがほとんど調べつくした内容ですけど）。

参考：http://d.hatena.ne.jp/teramonagi/20151031/1446283091

### grouped_df

dplyrには、`group_by()`という関数がある。

これは、データとグループ分けに使う変数を指定して`grouped_df`というオブジェクトをつくる。

```r
d <- data_frame(val = 1:6, cat = rep(letters[1:3], each = 2))
g <- group_by(d, cat)

is(g)
#> [1] "grouped_df"

g
#> Source: local data frame [6 x 2]
#> Groups: cat [3]
#> 
#>     val   cat
#>   (int) (chr)
#> 1     1     a
#> 2     2     a
#> 3     3     b
#> 4     4     b
#> 5     5     c
#> 6     6     c
```

`Groups`という要素ができている。`mutate()`、`summarise()`（`summarize()`）や`do()`などは、この`Groups`に設定されている変数にしたがって分割したデータに処理を実行する。違いを見てみる。

### mutate()

列名を指定した`mean()`は分割されたグループごとの平均になっている。他方、`.`を指定した`nrow()`はデータ全体の行数になっている。

```r
g %>%
  mutate(m = mean(val), n = nrow(.))
#> Source: local data frame [6 x 4]
#> Groups: cat [3]
#> 
#>     val   cat     m     n
#>   (int) (chr) (dbl) (int)
#> 1     1     a   1.5     6
#> 2     2     a   1.5     6
#> 3     3     b   3.5     6
#> 4     4     b   3.5     6
#> 5     5     c   5.5     6
#> 6     6     c   5.5     6
```

`.`は、`mutate()`や`summarise()`の中では`Groups`の有無にかかわらずデータ全体を表す。例えば、以下のようにすると違いがわかりやすい。列名`val`を指定した`m`は分割されたグループごとの平均だが、`.$val`を指定した`m2`はデータ全体の平均になっている。

```r
g %>%
  mutate(m = mean(val), m2 = mean(.$val))
#> Source: local data frame [6 x 4]
#> Groups: cat [3]
#> 
#>     val   cat     m    m2
#>   (int) (chr) (dbl) (dbl)
#> 1     1     a   1.5   3.5
#> 2     2     a   1.5   3.5
#> 3     3     b   3.5   3.5
#> 4     4     b   3.5   3.5
#> 5     5     c   5.5   3.5
#> 6     6     c   5.5   3.5
```

### summarise()

`summarise()`での列名や`.`の展開のされ方は、`mutate()`と同じ。ただし、`summarise()`はグループごとに1行しか結果を出さない。

また、`mutate()`の場合は実行結果も`grouped_df`になる（`Groups`はそのまま残っている）が、`summarise()`の結果は`Groups`が一つ減った`grouped_df`になる。減った結果`Groups`がひとつもなければ`tbl_df`になる。

```r
g %>%
  summarise(m = mean(val), n = nrow(.))
#> Source: local data frame [3 x 3]
#> 
#>     cat     m     n
#>   (chr) (dbl) (int)
#> 1     a   1.5     6
#> 2     b   3.5     6
#> 3     c   5.5     6
```

### do()

`do()`の場合は、上2つとは違って、`.`がグループに分割されたデータを表す。その代わり、列名での指定はエラーになる。

また、結果がlistにくるまれた状態になっているのも違う点。

```r
g %>%
  do(m = mean(val), n = nrow(.))
#> Error in mean(val) : object 'val' not found

g %>%
  do(m = mean(.$val), n = nrow(.))
#> Source: local data frame [3 x 3]
#> Groups: <by row>
#> 
#>     cat        m        n
#>   (chr)   (list)   (list)
#> 1     a <dbl[1]> <int[1]>
#> 2     b <dbl[1]> <int[1]>
#> 3     c <dbl[1]> <int[1]>
```

ここで、「`Groups: <by row>`」となっていることに注目。これは`rowwise_df`というオブジェクトになっている。

```r
is(.Last.value)
#> [1] "rowwise_df"
```

`rowwise_df`は、`rowwise()`のヘルプに説明がある。

> Currently `rowwise` grouping only works with data frames. Its main impact is to allow you to work with list-variables in `summarise` and `mutate` without having to use `[[1]]`. This makes `summarise()` on a rowwise tbl effectively equivalent to plyr's ldply.

つまり、`rowwise_df`に対して`summarise()`するときは、`[[1]]`とかしなくてもlistの中の値を参照できる。なので、summarise()ではその列名を指定すれば大丈夫。

```r
g %>%
  do(m = mean(.$val), n = nrow(.)) %>%
  summarise(cat, m, n)
#> Source: local data frame [3 x 3]
#> 
#>     cat     m     n
#>   (chr) (dbl) (int)
#> 1     a   1.5     2
#> 2     b   3.5     2
#> 3     c   5.5     2
```

例えばこんなふうに`do()`で複数の値を結果に入れても、`summarise()`でそれをまとめてひとつの値にできれば大丈夫。

```r
group_by(iris, Species) %>%
  do(sl_range = range(.$Sepal.Length)) %>%
  summarise(sl_range = sprintf("%.1f-%.1f", sl_range[1], sl_range[2]))
#> Source: local data frame [3 x 1]
#> 
#>   sl_range
#>      (chr)
#> 1  4.3-5.8
#> 2  4.9-7.0
#> 3  4.9-7.9
```

### まとめ

|           | 列名の解釈 | `.`の解釈  |                      結果の型 |
|:--        |:--                      |:--            |:--        |
| `mutate()` | グループ分けされたデータの列の値 | データフレーム全体 | `grouped_df` |
| `summarise()` | グループ分けされたデータの列の値 | データフレーム全体 | `Groups`が一つ減った`grouped_df`（減った結果`Groups`がひとつもなければ`tbl_df`）  |
| `do()`     |  -   | グループ分けされたデータフレーム | `rowwise_df` |