7

私の研究で遭遇する繰り返し分析パラダイムは、すべての異なるグループ ID 値に基づいてサブセット化し、各グループに対して順番に統計分析を実行し、さらに処理/要約するために結果を出力マトリックスに入れる必要があるというものです。

通常、R でこれを行う方法は次のようなものです。

data.mat <- read.csv("...")  
groupids <- unique(data.mat$ID)  #Assume there are then 100 unique groups
  
results <- matrix(rep("NA",300),ncol=3,nrow=100)  

for(i in 1:100) {  
  tempmat <- subset(data.mat,ID==groupids[i])  

  # Run various stats on tempmat (correlations, regressions, etc), checking to  
  # make sure this specific group doesn't have NAs in the variables I'm using  
  # and assign results to x, y, and z, for example.  

  results[i,1] <- x  
  results[i,2] <- y  
  results[i,3] <- z  
}

これでうまくいきましたが、データのサイズと作業しているグループの数によっては、最大 3 日かかる場合があります。

並列処理に分岐する以外に、このようなものをより速く実行するための「トリック」はありますか? たとえば、ループを別のもの (ループ内で実行したい統計を含む関数を適用するようなもの) に変換したり、データのサブセットを実際に変数に割り当てる必要をなくしたりしますか?

編集:

おそらくこれは一般的な知識 (またはサンプリング エラー) にすぎませんが、コードの一部で、subset コマンドを使用するのではなく、ブラケットを使用してサブセット化を試みたところ、パフォーマンスがわずかに向上したようで、驚きました。上記と同じオブジェクト名を使用して、以下に使用および出力したコードがいくつかあります。

system.time(for(i in 1:1000){data.mat[data.mat$ID==groupids[i],]})  
   user  system elapsed  
 361.41   92.62  458.32
system.time(for(i in 1:1000){subset(data.mat,ID==groupids[i])})  
   user  system elapsed   
 378.44  102.03  485.94

アップデート:

回答の 1 つで、jorgusch は、data.table パッケージを使用してサブセット化を高速化することを提案しました。それで、今週初めに実行した問題にそれを適用しました。1,500,000 を少し超える行と 4 つの列 (ID、Var1、Var2、Var3) を持つデータセットで、各グループ (「ID」変数でインデックス付け) で 2 つの相関を計算したいと考えました。50,000 をわずかに超えるグループがあります。以下は私の最初のコードです(これは上記と非常に似ています):

data.mat <- read.csv("//home....")  
groupids <- unique(data.mat$ID)
  
results <- matrix(rep("NA",(length(groupids) * 3)),ncol=3,nrow=length(groupids))  

for(i in 1:length(groupids)) {  
  tempmat <- data.mat[data.mat$ID==groupids[i],] 

  results[i,1] <- groupids[i]  
  results[i,2] <- cor(tempmat$Var1,tempmat$Var2,use="pairwise.complete.obs")  
  results[i,3] <- cor(tempmat$Var1,tempmat$Var3,use="pairwise.complete.obs")    

}  

どれくらいの時間がかかったかを正確に測定するために、今それを再実行していますが、覚えている限り、朝オフィスに着いたときに実行を開始し、午後半ばに終了しました。図 5-7 時間。

data.table を使用するようにコードを再構築しています....

data.mat <- read.csv("//home....")  
data.mat <- data.table(data.mat)  
  
testfunc <- function(x,y,z) {  
  temp1 <- cor(x,y,use="pairwise.complete.obs")  
  temp2 <- cor(x,z,use="pairwise.complete.obs")  
  res <- list(temp1,temp2)  
  res  
}  

system.time(test <- data.mat[,testfunc(Var1,Var2,Var3),by="ID"])  
 user  system  elapsed  
16.41    0.05    17.44  

data.table を使用した結果を for ループを使用してすべての ID をサブセット化し、結果を手動で記録した結果と比較すると、同じ答えが得られたようです (ただし、もう少し徹底的に確認する必要があります)。かなりのスピードアップになりそうです。

更新 2:

サブセットを使用してコードを実行すると、最終的に再び終了しました。

   user     system   elapsed  
17575.79  4247.41   23477.00

更新 3:

同じく推奨された plyr パッケージを使用して、何かが異なって機能するかどうかを確認したかったのです。これは初めての使用なので、多少効率が悪いかもしれませんが、サブセット化された for ループに比べてかなり役に立ちました。

以前と同じ変数と設定を使用して...

data.mat <- read.csv("//home....")  
system.time(hmm <- ddply(data.mat,"ID",function(df)c(cor(df$Var1,df$Var2,  use="pairwise.complete.obs"),cor(df$Var1,df$Var3,use="pairwise.complete.obs"))))  
  user  system elapsed  
250.25    7.35  272.09  
4

4 に答える 4

6

plyrこれは、パッケージが簡単にするために設計されたものとほぼ同じです。しかし、それが物事をはるかに速くすることはありそうにありません-ほとんどの時間はおそらく統計を行うのに費やされます。

于 2010-03-27T01:31:15.787 に答える
3

に加えplyrて、package を使用foreachして明示的なループ カウンターを除外することもできますが、パフォーマンス上の利点が得られるかどうかはわかりません。

ForeachdoMCただし、マルチコア ワークステーション ( /multicoreパッケージを使用) を使用している場合 (詳細については、doMC および foreachの概要を参照してください)を使用すると、並列チャンク処理への非常に単純なインターフェイスが提供されます。学生。それが唯一の理由ではない場合、plyr非常に良い解決策です。

于 2010-03-27T11:59:42.707 に答える
2

ベクトル化して、中間結果の不要なコピーを作成しないようにすることを既に提案しているので、確かに正しい方向に進んでいます。私がやったことをしないように注意してください。ベクトル化すると常にパフォーマンスが向上すると仮定してください (Python + NumPy、MATLAB などの他の言語のように)。

例:

# small function to time the results:
time_this = function(...) {
  start.time = Sys.time(); eval(..., sys.frame(sys.parent(sys.parent()))); 
  end.time = Sys.time(); print(end.time - start.time)
}

# data for testing: a 10000 x 1000 matrix of random doubles
a = matrix(rnorm(1e7, mean=5, sd=2), nrow=10000)

# two versions doing the same thing: calculating the mean for each row
# in the matrix
x = time_this( for (i in 1:nrow(a)){ mean( a[i,] ) } )
y = time_this( apply(X=a, MARGIN=1, FUN=mean) )

print(x)    # returns => 0.5312099
print(y)    # returns => 0.661242

「apply」バージョンは、実際には「for」バージョンよりも遅くなります。( Infernoの作成者によると、これを行っている場合、ベクトル化していないということは、「ループ隠蔽」を行っているということです。)

ただし、ビルトインを使用することでパフォーマンスを向上させることができます。以下では、組み込み関数「rowMeans」を使用して、上記の 2 つと同じ操作の時間を計っています。

z = time_this(rowMeans(a))
print(z)    # returns => 0.03679609

「for」ループ (およびベクトル化されたバージョン) と比べて桁違いに改善されています。

apply ファミリの他のメンバーは、ネイティブの「for」ループの単なるラッパーではありません。

a = abs(floor(10*rnorm(1e6)))

time_this(sapply(a, sqrt))
# returns => 6.64 secs

time_this(for (i in 1:length(a)){ sqrt(a[i])})
# returns => 1.33 secs

「sapply」は、「for」ループと比較して約5 倍遅くなります。

最後に、w/r/t ベクトル化対 'for' ループ。ベクトル化された関数を使用できる場合、ループを使用することはないと思います。後者は通常、キーストロークが少なく、より自然な方法です (私にとって)。これは別の種類のパフォーマンスの向上であると思います。

于 2010-03-27T02:21:07.510 に答える
2

個人的には、plyr はあまり理解しにくいと思います。また、より高速なdata.tableを好みます。たとえば、各 ID の列 my_column の標準偏差を実行したいとします。

dt <- datab.table[df] # one time operation...changing format of df to table
result.sd <- dt[,sd(my_column),by="ID"] # result with each ID and SD in second column 

この種の 3 つのステートメントと最後に cbind が必要です。これだけで十分です。新しい構文でサブセット コマンドを使用せずに、1 つの ID に対してのみ dt do some action を使用することもできます。

result.sd.oneiD<- dt[ID="oneID",sd(my_column)]  

最初のステートメントは行 (i) を参照し、2 番目のステートメントは列 (j) を参照します。

プレーヤーよりも読みやすく、「サブセット」内でサブドメインを作成できるため、柔軟性が高い場合... ドキュメントでは、SQL に似たメソッドを使用することが説明されています。たとえば、by は SQL の "group by" に相当します。SQL を知っていれば、おそらくもっと多くのことを実行できますが、パッケージを使用する必要はありません。最後に、各操作が並列であるだけでなく、data.table が計算に必要なデータを取得するため、非常に高速です。ただし、サブセットはマトリックス全体のレベルを維持し、メモリ内をドラッグします。

于 2010-03-27T17:23:30.270 に答える