Rで集計に使える関数 - by

byを使って個票データ(microdata)を足し上げよう
R
公開

2022年11月21日

個票データを集計するシリーズ、今回はbyを見ていきましょう。

使用するデータ

引き続きdatasets::CO2を例に使います。

head(datasets::CO2)
  Plant   Type  Treatment conc uptake
1   Qn1 Quebec nonchilled   95   16.0
2   Qn1 Quebec nonchilled  175   30.4
3   Qn1 Quebec nonchilled  250   34.8
4   Qn1 Quebec nonchilled  350   37.2
5   Qn1 Quebec nonchilled  500   35.3
6   Qn1 Quebec nonchilled  675   39.2

by

byとはどんな関数なのか、ヘルプページの最初にはこう書いてあります。

Function by is an object-oriented wrapper for tapply applied to data frames.

tapplyのオブジェクト指向バージョン!なにかかっこいい。

何がどうオブジェクト指向なのか。それは引数でdata frameを受け取り、その構造を活かした処理を行えることです。 byの引数は次のようになっています。

  • 1つ目に集計するdata frameを指定
  • 2つ目に分類に使いたい列を指定
  • 3つ目に分類ごとのdata frameを処理する関数を指定

tapplyと異なり、2つ目の引数はList型でなくてもよいです。(2つ以上の列を渡す場合はList型にする必要があります)

3つ目の引数がよく分からないかも知れません。 これは、引数1つ目のdata frameを渡して何かしらの処理を行う関数、のようなイメージです。実際には2つ目の引数で分割されたものが順番に渡されます。 つまり、分類ごとに分割されたdata frameに対する処理を与えるのが3つ目の引数です。

具体的に、結果数値のuptakeを分類のType別に合計した値を出してみましょう。

by(CO2, CO2$Type, function(x){sum(x$uptake)})
CO2$Type: Quebec
[1] 1408.8
------------------------------------------------------------------------------------------------------------------------------------------------------ 
CO2$Type: Mississippi
[1] 877.1

こうなります。

引数3つ目にはあらかじめ定義しておいた関数を指定してもよいですが、このように無名関数(ラムダ式)を記述することもできます。 一応、R4.1.0からは次のような省略形も使えますね。

by(CO2, CO2$Type, \(x){sum(x$uptake)})

分類を2つに増やすとどうなるでしょう。

by(CO2, CO2[c("Type", "Treatment")], function(x){sum(x$uptake)})
Type: Quebec
Treatment: nonchilled
[1] 742
------------------------------------------------------------------------------------------------------------------------------------------------------ 
Type: Mississippi
Treatment: nonchilled
[1] 545
------------------------------------------------------------------------------------------------------------------------------------------------------ 
Type: Quebec
Treatment: chilled
[1] 666.8
------------------------------------------------------------------------------------------------------------------------------------------------------ 
Type: Mississippi
Treatment: chilled
[1] 332.1

このように分類の組み合わせごとに分割された結果が得られます。

いや待て。tapplyよりもだいぶ難しくないか?結果も見にくいし。

と思った方、正しいです。

今回はデータの集計をしようということで、1つの値の足し上げのみを行っているため わざわざdata frameを保持しておく必要がなく、byの使い方としてはいまいちでした。

使おうと思えば集計に使うこともできますよということで。

byの使いみち

byを活用するには3つ目の引数に使う関数がポイントです。

data frameを受け取れる関数、例えば要約を示してくれるsummaryを使ってみましょう。

by(CO2, CO2$Type, summary)
CO2$Type: Quebec
     Plant            Type         Treatment       conc          uptake     
 Qn1    :7   Quebec     :42   nonchilled:21   Min.   :  95   Min.   : 9.30  
 Qn2    :7   Mississippi: 0   chilled   :21   1st Qu.: 175   1st Qu.:30.32  
 Qn3    :7                                    Median : 350   Median :37.15  
 Qc1    :7                                    Mean   : 435   Mean   :33.54  
 Qc3    :7                                    3rd Qu.: 675   3rd Qu.:40.15  
 Qc2    :7                                    Max.   :1000   Max.   :45.50  
 (Other):0                                                                  
------------------------------------------------------------------------------------------------------------------------------------------------------ 
CO2$Type: Mississippi
     Plant            Type         Treatment       conc          uptake     
 Mn3    :7   Quebec     : 0   nonchilled:21   Min.   :  95   Min.   : 7.70  
 Mn2    :7   Mississippi:42   chilled   :21   1st Qu.: 175   1st Qu.:13.88  
 Mn1    :7                                    Median : 350   Median :19.30  
 Mc2    :7                                    Mean   : 435   Mean   :20.88  
 Mc3    :7                                    3rd Qu.: 675   3rd Qu.:28.05  
 Mc1    :7                                    Max.   :1000   Max.   :35.50  
 (Other):0                                                                  

このようにTypeごとにsummaryを実行した結果を得ることができます。少し使いどころが見えてきたでしょうか。

もう1つ、lmを使ってuptakeconcで説明する線形回帰モデルを作ってみましょう。 普通に書くとこうです。

lm(formula=uptake~conc, data=CO2)

Call:
lm(formula = uptake ~ conc, data = CO2)

Coefficients:
(Intercept)         conc  
   19.50029      0.01773  

これをTypeごとにデータを分割して作るためにbyを使ってみます。

by(CO2, CO2$Type, lm, formula=uptake~conc)
CO2$Type: Quebec

Call:
FUN(formula = ..1, data = data[x, , drop = FALSE])

Coefficients:
(Intercept)         conc  
   23.50304      0.02308  

------------------------------------------------------------------------------------------------------------------------------------------------------ 
CO2$Type: Mississippi

Call:
FUN(formula = ..1, data = data[x, , drop = FALSE])

Coefficients:
(Intercept)         conc  
   15.49754      0.01238  

concに対するuptakeの上昇率が、Type=QuebecMississippiで異なることが分かりますね。

ちなみに上のbyの使い方には少し高度なテクニックが入っています。それについてはまた改めて記事にしたいと思います。

さらに言うと、こういう使い方の場合は筆者はlapply+splitを使う方が好みです。それもいずれまた・・・

今回はbyを使った集計と、より実用的な使い方のご紹介でした。