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 |