個票データを集計するシリーズ、今回はby
を見ていきましょう。
使用するデータ
引き続き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
を使ってuptake
をconc
で説明する線形回帰モデルを作ってみましょう。 普通に書くとこうです。
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=Quebec
とMississippi
で異なることが分かりますね。
ちなみに上のby
の使い方には少し高度なテクニックが入っています。それについてはまた改めて記事にしたいと思います。
さらに言うと、こういう使い方の場合は筆者はlapply
+split
を使う方が好みです。それもいずれまた・・・
今回はby
を使った集計と、より実用的な使い方のご紹介でした。