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 insummarise
andmutate
without 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 |