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
rowwisegrouping only works with data frames. Its main impact is to allow you to work with list-variables insummariseandmutatewithout having to use[[1]]. This makessummarise()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 |