[解決済み] グループごとの平均を計算する
質問
このような大きなデータフレームがあります。
df <- data.frame(dive = factor(sample(c("dive1","dive2"), 10, replace=TRUE)),
speed = runif(10)
)
> df
dive speed
1 dive1 0.80668490
2 dive1 0.53349584
3 dive2 0.07571784
4 dive2 0.39518628
5 dive1 0.84557955
6 dive1 0.69121443
7 dive1 0.38124950
8 dive2 0.22536126
9 dive1 0.04704750
10 dive2 0.93561651
私の目標は、ある列の値が他の列の値と等しいときに、その列の値の平均を求め、これをすべての値について繰り返すことです。例えば、上記の例では、列
speed
というカラムの一意な値に対して
dive
. そのため
dive==dive1
の平均は
speed
の平均はこのようになり、各値の
dive
.
どのように解決するのですか?
Rで行う方法はたくさんあります。具体的には
by
,
aggregate
,
split
そして
plyr
,
cast
,
tapply
,
data.table
,
dplyr
といった具合です。
大まかに言えば、これらの問題は分割-適用-結合の形式をとっています。 Hadley Wickham は
美しい記事
を書きました。この問題の全カテゴリーについてのより深い洞察を与えてくれるもので、読む価値があります。 彼の
plyr
パッケージは、一般的なデータ構造のための戦略を実装しています。
dplyr
はデータフレーム用に調整されたより新しい実装です。これらは同じ形式の問題を解決することができますが、今回の問題よりもさらに複雑なものとなります。 これらはデータ操作の問題を解決するための一般的なツールとして学ぶ価値があります。
性能は非常に大きなデータセットでの問題であり、その点では
data.table
. しかし、中規模以下のデータセットしか扱わないのであれば、時間をかけてでも
data.table
を学ぶ時間を取ることは、おそらく努力に値しないでしょう。
dplyr
は高速に動作するため、高速化したいけれども
data.table
.
以下の他のソリューションの多くは、追加のパッケージを必要としません。 それらのいくつかは、中~大規模なデータセットでかなり高速に動作するものさえあります。 それらの主な欠点は、メタファーか柔軟性のどちらかです。 メタファーとは、何か他のもののために設計されたツールを、この特定のタイプの問題を「賢い」方法で解決するように強制することです。 柔軟性とは、類似の問題を幅広く解決したり、整頓された出力を簡単に生成したりする能力が欠けていることを意味します。
例
base
機能
tapply
:
tapply(df$speed, df$dive, mean)
# dive1 dive2
# 0.5419921 0.5103974
aggregate
:
aggregate
はdata.frameを取り込み、data.frameを出力し、数式インターフェイスを使用します。
aggregate( speed ~ dive, df, mean )
# dive speed
# 1 dive1 0.5790946
# 2 dive2 0.4864489
by
:
最も使いやすい形は、ベクトルを受け取ってそれに関数を適用するものです。しかし、その出力はあまり操作しやすい形ではありません。
res.by <- by(df$speed, df$dive, mean)
res.by
# df$dive: dive1
# [1] 0.5790946
# ---------------------------------------
# df$dive: dive2
# [1] 0.4864489
これを回避するために、単純な
by
は
as.data.frame
メソッドで
taRifx
ライブラリのメソッドが動作します。
library(taRifx)
as.data.frame(res.by)
# IDX1 value
# 1 dive1 0.6736807
# 2 dive2 0.4051447
split
:
名前の通り、split-apply-combine戦略の"split"の部分のみを実行するものです。 残りの部分を動作させるために、小さな関数を書いておきます。
sapply
を apply-combine に使う小さな関数を書きます。
sapply
は自動的に結果を可能な限り単純化します。 この場合、結果は1次元だけなので、data.frameではなくvectorということになります。
splitmean <- function(df) {
s <- split( df, df$dive)
sapply( s, function(x) mean(x$speed) )
}
splitmean(df)
# dive1 dive2
# 0.5790946 0.4864489
外部パッケージ
data.table :
library(data.table)
setDT(df)[ , .(mean_speed = mean(speed)), by = dive]
# dive mean_speed
# 1: dive1 0.5419921
# 2: dive2 0.5103974
dplyr
:
library(dplyr)
group_by(df, dive) %>% summarize(m = mean(speed))
plyr
(前身である
dplyr
)
以下は
公式ページ
には、次のように書かれています。
plyr
:
で既に可能です。
base
R関数(例えば
split
と
は
apply
関数のファミリー)がありますが
plyr
はそれを少し簡単にします
を使っています。
- 名前、引数、出力の完全な一貫性
-
による便利な並列化
foreach
パッケージ - data.frame、matrices、lists からの入力と出力
- 長時間稼働のオペレーションを把握するためのプログレスバー
- 内蔵のエラー回復機能、および情報豊富なエラーメッセージ
- すべての変換で維持されるラベル
言い換えれば、分割-適用-結合の操作のためのツールを一つ学ぶとしたら、それは
plyr
.
library(plyr)
res.plyr <- ddply( df, .(dive), function(x) mean(x$speed) )
res.plyr
# dive V1
# 1 dive1 0.5790946
# 2 dive2 0.4864489
リシェイプ2 :
は
reshape2
ライブラリは、分割・適用・結合を主目的として設計されていません。 その代わり、2つの部分からなるメルト/キャスト戦略を用いて
様々なデータ整形を行うことができます。
. しかし、集約関数が使えるので、この問題に使うことができます。 分割-適用-結合の操作のための最初の選択肢ではありませんが、そのリシェイプ機能は強力なので、このパッケージも学ぶべきでしょう。
library(reshape2)
dcast( melt(df), variable ~ dive, mean)
# Using dive as id variables
# variable dive1 dive2
# 1 speed 0.5790946 0.4864489
ベンチマーク
10列、2グループ
library(microbenchmark)
m1 <- microbenchmark(
by( df$speed, df$dive, mean),
aggregate( speed ~ dive, df, mean ),
splitmean(df),
ddply( df, .(dive), function(x) mean(x$speed) ),
dcast( melt(df), variable ~ dive, mean),
dt[, mean(speed), by = dive],
summarize( group_by(df, dive), m = mean(speed) ),
summarize( group_by(dt, dive), m = mean(speed) )
)
> print(m1, signif = 3)
Unit: microseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 302 325 343.9 342 362 396 100 b
aggregate(speed ~ dive, df, mean) 904 966 1012.1 1020 1060 1130 100 e
splitmean(df) 191 206 249.9 220 232 1670 100 a
ddply(df, .(dive), function(x) mean(x$speed)) 1220 1310 1358.1 1340 1380 2740 100 f
dcast(melt(df), variable ~ dive, mean) 2150 2330 2440.7 2430 2490 4010 100 h
dt[, mean(speed), by = dive] 599 629 667.1 659 704 771 100 c
summarize(group_by(df, dive), m = mean(speed)) 663 710 774.6 744 782 2140 100 d
summarize(group_by(dt, dive), m = mean(speed)) 1860 1960 2051.0 2020 2090 3430 100 g
autoplot(m1)
<イグ
いつものように
data.table
はもう少しオーバーヘッドがあるので、小さなデータセットでは平均的な値になります。 しかし、これらはマイクロ秒なので、その差は些細なものです。 ここではどのアプローチもうまく機能するので、それに基づいて選択する必要があります。
-
すでに精通しているもの、または慣れ親しみたいもの (
plyr
は、その柔軟性のために常に学ぶ価値があります。data.table
は、巨大なデータセットを分析する予定があるのなら学ぶ価値があります。by
とaggregate
そしてsplit
はすべてRの基本関数であるため、普遍的に利用可能です) - 返す出力(numeric、data.frame、または data.table -- 後者は data.frame を継承)。
1,000万行、10グループ
しかし、大きなデータセットがある場合はどうでしょうか? 10^7行を10グループに分割して試してみましょう。
df <- data.frame(dive=factor(sample(letters[1:10],10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)
m2 <- microbenchmark(
by( df$speed, df$dive, mean),
aggregate( speed ~ dive, df, mean ),
splitmean(df),
ddply( df, .(dive), function(x) mean(x$speed) ),
dcast( melt(df), variable ~ dive, mean),
dt[,mean(speed),by=dive],
times=2
)
> print(m2, signif = 3)
Unit: milliseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 720 770 799.1 791 816 958 100 d
aggregate(speed ~ dive, df, mean) 10900 11000 11027.0 11000 11100 11300 100 h
splitmean(df) 974 1040 1074.1 1060 1100 1280 100 e
ddply(df, .(dive), function(x) mean(x$speed)) 1050 1080 1110.4 1100 1130 1260 100 f
dcast(melt(df), variable ~ dive, mean) 2360 2450 2492.8 2490 2520 2620 100 g
dt[, mean(speed), by = dive] 119 120 126.2 120 122 212 100 a
summarize(group_by(df, dive), m = mean(speed)) 517 521 531.0 522 532 620 100 c
summarize(group_by(dt, dive), m = mean(speed)) 154 155 174.0 156 189 321 100 b
autoplot(m2)
<イグ
次に
data.table
または
dplyr
での動作で
data.table
を操作することが、明らかに望ましい方法です。ある種のアプローチ(
aggregate
と
dcast
) は非常に遅く見え始めています。
1,000万行、1,000グループ
グループ数が多くなれば、その差はより顕著になります。 とは 1,000グループ と同じ10^7行の場合。
df <- data.frame(dive=factor(sample(seq(1000),10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)
# then run the same microbenchmark as above
print(m3, signif = 3)
Unit: milliseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 776 791 816.2 810 828 925 100 b
aggregate(speed ~ dive, df, mean) 11200 11400 11460.2 11400 11500 12000 100 f
splitmean(df) 5940 6450 7562.4 7470 8370 11200 100 e
ddply(df, .(dive), function(x) mean(x$speed)) 1220 1250 1279.1 1280 1300 1440 100 c
dcast(melt(df), variable ~ dive, mean) 2110 2190 2267.8 2250 2290 2750 100 d
dt[, mean(speed), by = dive] 110 111 113.5 111 113 143 100 a
summarize(group_by(df, dive), m = mean(speed)) 625 630 637.1 633 644 701 100 b
summarize(group_by(dt, dive), m = mean(speed)) 129 130 137.3 131 142 213 100 a
autoplot(m3)
<イグ
だから
data.table
はうまくスケーリングし続けていますし
dplyr
で動作している
data.table
もうまく機能します。
dplyr
に
data.frame
の方が一桁近く遅くなります。 そのため
split
/
sapply
戦略はグループの数ではうまくスケールしないようです (つまり
split()
は遅いと思われ
sapply
は速い)。
by
は比較的効率的であり続けています。5秒という時間は、ユーザーにとっては確かに目立ちますが、この大きさのデータセットにとってはまだ不合理ではありません。 それでも、このサイズのデータセットを日常的に扱っているのであれば
data.table
が良いのは明らかです。最高のパフォーマンスを得るには100% data.table、あるいは
dplyr
と
dplyr
を使って
data.table
を実行可能な代替手段として使用します。
関連
-
[解決済み] グループ化関数(tapply、by、aggregate)と*applyファミリ
-
[解決済み] Rの代入演算子"="と"<-"の違いは何ですか?
-
[解決済み] 関数のソースコードを見るにはどうしたらいいですか?
-
[解決済み] require()とlibrary()の違いは何ですか?
-
[解決済み] リストやデータフレームの要素にアクセスするためのブラケット[ ]とダブルブラケット[[ ]]の違いについて
-
[解決済み] 先頭と末尾の空白を削除するにはどうしたらよいですか?
-
[解決済み】data.table vs dplyr:一方がうまくできない、またはうまくできないことを行うことができますか?
-
[解決済み】変数をグループ別に合計する方法
-
[解決済み】set.seed関数を使用する理由
-
[解決済み] ggplot2 で個々のファセットにテキストをアノテートする
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
R: エラー: $ 演算子は原子ベクトルに対して無効です。
-
SocketTimeoutExceptionです。読み込みがタイムアウトしました
-
[解決済み] 非常に大きなテーブルをデータフレームとして高速に読み込む
-
[解決済み] ベクトル中のxの値を持つ要素の個数を数える
-
[解決済み] Rでtrycatchの書き方
-
[解決済み] data.frameの1つの列の名前を変更する方法は?
-
[解決済み】data.frameのグループごとの平均値【重複】について
-
[解決済み] FUN内のlapplyインデックス名へのアクセス
-
[解決済み] データフレームのカラムのデータ型を決定する
-
[解決済み] 2つの単語の最初の文字を大文字にします。