LoginSignup
9
9

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-10-31

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
9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9