Edited at

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

More than 3 years have passed since last update.

r-wakalangのチャットでdo()についての話が盛り上がったので、調べたことをまとめます(@hoxo_mさんと@teranomagiさんがほとんど調べつくした内容ですけど)。

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


grouped_df

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

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

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()はデータ全体の行数になっている。

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はデータ全体の平均になっている。

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になる。

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にくるまれた状態になっているのも違う点。

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というオブジェクトになっている。

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()ではその列名を指定すれば大丈夫。

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()でそれをまとめてひとつの値にできれば大丈夫。

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