1. ホーム
  2. r

[解決済み] グループごとの平均を計算する

2023-06-20 04:54:51

質問

このような大きなデータフレームがあります。

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

これを回避するために、単純な byas.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 は、巨大なデータセットを分析する予定があるのなら学ぶ価値があります。 byaggregate そして 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 を操作することが、明らかに望ましい方法です。ある種のアプローチ( aggregatedcast ) は非常に遅く見え始めています。

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 もうまく機能します。 dplyrdata.frame の方が一桁近く遅くなります。 そのため split / sapply 戦略はグループの数ではうまくスケールしないようです (つまり split() は遅いと思われ sapply は速い)。 by は比較的効率的であり続けています。5秒という時間は、ユーザーにとっては確かに目立ちますが、この大きさのデータセットにとってはまだ不合理ではありません。 それでも、このサイズのデータセットを日常的に扱っているのであれば data.table が良いのは明らかです。最高のパフォーマンスを得るには100% data.table、あるいは dplyrdplyr を使って data.table を実行可能な代替手段として使用します。