学生の分析を手伝っていたところ、突然、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()で標準のデータフレームに変換しています。