StatsBeginner: 初学者の統計学習ノート

初学者が統計学、機械学習、R、Pythonの勉強の過程をメモっていくノート。

Rでピボットテーブル

学生の分析を手伝っていたところ、突然、Rでピボットテーブルみたいな集計をしたくなりました。
度数分布表はtable関数でつくれますが、ピボットテーブルってどうやるのかな、と。
ググるといろいろやり方が出てきますが、とりあえず、{dplyr}のgroup_byと{tidyr}のspreadを使うとできました。(もっと簡単な方法ありそうなので誰か教えて下さい。)


あと、実はその前段で、年齢のデータを「10代、20代、30代」みたいに階級別したかったので、そこには{berryFunctions}というパッケージのclassify関数を使ってみました。


まずパッケージを読み込みます。
{psych}の中に入っているTal_Orというデータセットを使います。
dplyrとtidyrは、tidyverseでもいいと思います。(両方まとめて入ります。)

> library(psych)  # データセットを使うため
> library(berryFunctions)  # classify用
> library(dplyr)  # tidyverseでまとめてもよい
> library(tidyr)  # tidyverseでまとめてもよい
> 


データセットを読み込んで中身をみてみます。

> data(Tal_Or)
> head(Tal_Or)
  cond pmi import reaction gender age
1    1 7.0      6     5.25      1  51
2    0 6.0      1     1.25      1  40
3    1 5.5      6     5.00      1  26
4    0 6.5      6     2.75      2  21
5    0 6.0      5     2.50      1  27
6    0 5.5      1     1.25      1  25
> 


これの、genderとageを軸につかって、reactionの値を集計したり、データの個数を数えたりしてみます。
その前に、年齢を、「10代、20代...」と階級に分けるために、classify関数をつかってみましょう。

> # 年齢階級ラベルを作成
> ageclass <- classify(Tal_Or$age, 
+                       method='custom', 
+                       breaks=c(10,19,29,39,49,59,69)
+                       )$index
> 


ヘルプをみると他の使い方も載っていますが、上の例では、methodには「自由に分割点を決める」という意味の'custom'を指定し、breaksに、分割点を与えています。
下記の参考例をみるとわかるように、

> # 参考:
> # 右端と左端の値は含まれる。その他の分割点は「以下」を表す。
> # なので10代、20代、30代、40代に分けたければ、両端は20と40
> # にしておいて、区切りは19、29、39を与える。
> classify(c(10,19,20,21,30,39,40), 
+          method='custom', 
+          breaks=c(10,19,29,39,49)
+          )$index
[1] 1 1 2 2 3 3 4
> 


breaksに与えた数字が両端と区切りになるので、ここでは数字を7つ与えているので6つの階級に区切られるのですが、注意点として、両端の値は「含む」であり、区切りは(未満ではなく)「以下」を表すようなので、「10代、20代...60代」とするときは、c(10,19,29,39,49,59,69)になるわけです。


以下、group_by、summarise、spreadを組み合わせて、ピボットテーブルを作ります。
簡単にいうと、軸にしたい2つの変数名でまずgroup_byする。
で、集計対象となる変数を合計したり平均とったりするのであれば、summariseの中で、変数名をつけて集計します。
最後に、spreadに、「列」にしたい軸変数の名前(そこで選ばなかったほうの軸変数が行になる)と、summariseの中で設けた集計変数名を指定します。
データの個数をカウントしたい場合(つまり2つの軸で度数のクロス集計をしたい場合)は、dplyrの中に入ってるn()という関数が使えます。

> # 年齢階級をくっつける
> Tal_Or <- data.frame(Tal_Or, ageclass)
> 
> # 性別と年齢階級でピボットテーブルを作成。
> # 値は"reaction"という変数にする。
> 
> # reactionの平均値を集計
> Tal_Or %>% 
+   dplyr::group_by(gender,ageclass) %>%
+   dplyr::summarise(mean_react = mean(reaction)) %>%
+   tidyr::spread(gender, mean_react)
# A tibble: 6 x 3
  ageclass   `1`   `2`
     <int> <dbl> <dbl>
1        1  2.5   4.92
2        2  3.53  3.44
3        3  4.25 NA   
4        4  1.25 NA   
5        5  5.25 NA   
6        6  2.38 NA   
> 
> # 行と列を入れ替え
> Tal_Or %>% 
+   dplyr::group_by(gender,ageclass) %>%
+   dplyr::summarise(mean_react = mean(reaction)) %>%
+   tidyr::spread(ageclass, mean_react)
# A tibble: 2 x 7
# Groups:   gender [2]
  gender   `1`   `2`   `3`   `4`   `5`   `6`
   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1      1  2.5   3.53  4.25  1.25  5.25  2.38
2      2  4.92  3.44 NA    NA    NA    NA   
> 
> # データの個数を集計
> Tal_Or %>% 
+   dplyr::group_by(gender,ageclass) %>%
+   dplyr::summarise(sample_size=n()) %>%
+   tidyr::spread(ageclass, sample_size)
# A tibble: 2 x 7
# Groups:   gender [2]
  gender   `1`   `2`   `3`   `4`   `5`   `6`
   <dbl> <int> <int> <int> <int> <int> <int>
1      1     1    37     1     1     1     2
2      2     3    77    NA    NA    NA    NA
> 


`1` `2` `3` `4` `5` `6`ってのは、さっき設けた年齢階級の階級番号ですね。一番下のテーブルでいうと、行が性別(gender)になっていて、1 or 2に分かれています。で、列が年齢階級になっていて、`1`が10代、`6`は60代を表しています。
つくったピボットテーブルに何か操作したいなら、下記のように、データフレームにします。

> # データフレームに
> Tal_Or %>% 
+   dplyr::group_by(gender,ageclass) %>%
+   dplyr::summarise(sample_size=n()) %>%
+   tidyr::spread(ageclass, sample_size) %>%
+   as.data.frame() -> pivot_table
> print(pivot_table)
  gender 1  2  3  4  5  6
1      1 1 37  1  1  1  2
2      2 3 77 NA NA NA NA
> 


私は、dplyrのクラスのままだとわけが分からなくなるので、as.data.frame()で標準のデータフレームに変換しています。