Rには大きなパフォーマンスの問題があります。data.frame
オブジェクトを反復処理する関数を作成しました。新しい列をdata.frame
に追加し、何かを蓄積するだけです。 (簡単な操作)。 data.frame
には約85万行あります。私のPCはまだ動作しており(現在約10時間)、ランタイムについてはわかりません。
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
temp[i,10] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
temp[i,10] <- temp[i,9] + temp[i-1,10]
} else {
temp[i,10] <- temp[i,9]
}
} else {
temp[i,10] <- temp[i,9]
}
}
names(temp)[names(temp) == "V10"] <- "Kumm."
return(temp)
}
この操作を高速化する方法はありますか?
最大の問題と非効率性の根本は、data.frameのインデックス付けです。temp[,]
を使用するすべての行を意味します。
これをできるだけ避けるようにしてください。私はあなたの機能を取り、インデックスを変更し、ここでversion_A
dayloop2_A <- function(temp){
res <- numeric(nrow(temp))
for (i in 1:nrow(temp)){
res[i] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
res[i] <- temp[i,9] + res[i-1]
} else {
res[i] <- temp[i,9]
}
} else {
res[i] <- temp[i,9]
}
}
temp$`Kumm.` <- res
return(temp)
}
ご覧のとおり、結果を収集するベクトルres
を作成します。最後にdata.frame
に追加し、名前をいじる必要はありません。それで、それはどれほど良いですか?
data.frame
で各関数をnrow
で1,000から10,000 x 1,000まで実行し、system.time
で時間を測定します
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
結果は
nrow(X)
からバージョンが指数関数的に依存していることがわかります。変更されたバージョンには線形関係があり、単純なlm
モデルは、850,000行の計算に6分10秒かかると予測しています。
ShaneとCalimoが答えに述べているように、ベクトル化はパフォーマンス向上の鍵です。コードからループの外側に移動できます:
temp[i,9]
)これはこのコードにつながります
dayloop2_B <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in 1:nrow(temp)) {
if (cond[i]) res[i] <- temp[i,9] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
今回はnrow
の10,000から10,000までのこの関数の結果を比較します。
もう1つの微調整は、ループ内でtemp[i,9]
をres[i]
に変更することです(i番目のループの繰り返しでまったく同じです)。ここでも、ベクトルのインデックス付けとdata.frame
のインデックス付けの違いです。
2番目:ループを見ると、すべてのi
をループする必要はなく、条件に適合するものだけを見ることができます。
だからここに行きます
dayloop2_D <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in (1:nrow(temp))[cond]) {
res[i] <- res[i] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
向上するパフォーマンスは、データ構造に大きく依存します。正確に-条件のTRUE
値の割合。シミュレートしたデータでは、1秒未満の850,000行の計算時間がかかります。
さらに先に進んでほしいと思っています。少なくとも2つのことができます。
C
コードを書くデータの最大シーケンスが大きくないことがわかっている場合は、ループをベクトル化されたwhileに変更できます
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
シミュレーションと数値に使用されるコードは GitHubで利用可能 です。
Rコードを高速化するための一般的な戦略
最初に、where遅い部分が実際にあることを理解します。ゆっくり実行されていないコードを最適化する必要はありません。少量のコードの場合、単純にそれを熟考するだけで機能します。それが失敗した場合、RProfおよび同様のプロファイリングツールが役立ちます。
ボトルネックを特定したら、より効率的なアルゴリズムについて考えてみてください。計算は、可能であれば1回だけ実行する必要があります。
より多くの効率的な関数を使用すると、中程度または大きな速度向上が得られます。たとえば、paste0
はわずかな効率の向上をもたらしますが、.colSums()
とその親類は多少顕著なゲインを実現します。 mean
は 特に遅い です。
そうすれば、特によくあるトラブルを避けることができます:
cbind
を使用すると、速度が大幅に低下します。より良いvectorizationを試してみてください。これは常に役立つとは限りません。この点で、ifelse
、diff
などの本質的にベクトル化されたコマンドは、apply
コマンドファミリよりも多くの改善を提供します(よく書かれたループで速度をほとんどまたはまったく向上させません)。
R関数にさらに情報を提供するを試みることもできます。たとえば、vapply
ではなく sapply
を使用し、テキストベースのデータを読み込むときに colClasses
を指定します。速度のゲインは、推測の量に応じて変わります。
次に、最適化されたパッケージを検討してください: data.table
パッケージは、データ操作や大量のデータの読み取り(fread
)での使用が可能な場合に、大幅な速度向上を実現できます。
次に、Rを呼び出すより効率的な手段を使用して速度の向上を試みます。
Ra
およびjit
パッケージをジャストインタイムコンパイルに合わせて使用します(Dirkには このプレゼンテーション の例があります)。そして最後に、上記のすべてで必要な速度が得られない場合は、遅いコードスニペットの速い言語に移行する必要があります。ここでRcpp
とinline
を組み合わせると、アルゴリズムの最も遅い部分のみをC++コードで置き換えることが特に簡単になります。ここで、たとえば、 初めての試み であり、高度に最適化されたRソリューションでさえも吹き飛ばされます。
それでもやはり問題が解決しない場合は、より多くの計算能力が必要です。 parallelization( http://cran.r-project.org/web/views/HighPerformanceComputing.html )またはGPUベースのソリューション(gpu-tools
)を調べます。
他のガイダンスへのリンク
for
ループを使用している場合、RをCまたはJavaまたは他の何かであるかのようにコーディングしている可能性があります。適切にベクトル化されたRコードは非常に高速です。
たとえば、次の2つの簡単なコードを使用して、10,000個の整数のリストを順番に生成します。
最初のコード例は、従来のコーディングパラダイムを使用してループをコーディングする方法です。完了するには28秒かかります
system.time({
a <- NULL
for(i in 1:1e5)a[i] <- i
})
user system elapsed
28.36 0.07 28.61
メモリを事前に割り当てるという単純なアクションにより、ほぼ100倍の改善を得ることができます。
system.time({
a <- rep(1, 1e5)
for(i in 1:1e5)a[i] <- i
})
user system elapsed
0.30 0.00 0.29
ただし、コロン演算子:
を使用したベースRベクトル演算を使用すると、この演算は実質的に瞬時に行われます。
system.time(a <- 1:1e5)
user system elapsed
0 0 0
これは、インデックスまたはネストされたifelse()
ステートメントを使用してループをスキップすることにより、はるかに高速になります。
idx <- 1:nrow(temp)
temp[,10] <- idx
idx1 <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
temp[idx1,10] <- temp[idx1,9] + temp[which(idx1)-1,10]
temp[!idx1,10] <- temp[!idx1,9]
temp[1,10] <- temp[1,9]
names(temp)[names(temp) == "V10"] <- "Kumm."
Ariが答えの最後で述べたように、Rcpp
およびinline
パッケージを使用すると、物事を非常に簡単に高速化できます。例として、このinline
コードを試してください(警告:未テスト):
body <- 'Rcpp::NumericMatrix nm(temp);
int nrtemp = Rccp::as<int>(nrt);
for (int i = 0; i < nrtemp; ++i) {
temp(i, 9) = i
if (i > 1) {
if ((temp(i, 5) == temp(i - 1, 5) && temp(i, 2) == temp(i - 1, 2) {
temp(i, 9) = temp(i, 8) + temp(i - 1, 9)
} else {
temp(i, 9) = temp(i, 8)
}
} else {
temp(i, 9) = temp(i, 8)
}
return Rcpp::wrap(nm);
'
settings <- getPlugin("Rcpp")
# settings$env$PKG_CXXFLAGS <- paste("-I", getwd(), sep="") if you want to inc files in wd
dayloop <- cxxfunction(signature(nrt="numeric", temp="numeric"), body-body,
plugin="Rcpp", settings=settings, cppargs="-I/usr/include")
dayloop2 <- function(temp) {
# extract a numeric matrix from temp, put it in tmp
nc <- ncol(temp)
nm <- dayloop(nc, temp)
names(temp)[names(temp) == "V10"] <- "Kumm."
return(temp)
}
#include
ingの事柄についても同様の手順があり、パラメーターを渡すだけです。
inc <- '#include <header.h>
include=inc
としてcxxfunctionに。これについて本当に素晴らしいのは、それがあなたのためにすべてのリンクとコンパイルを行うということです。そのため、プロトタイピングは非常に高速です。
免責事項:tmpのクラスが数値であり、数値行列などではないことを完全に確信していません。しかし、私はほとんど確信しています。
編集:この後さらに速度が必要な場合、 OpenMP はC++
に適した並列化機能です。私はinline
からそれを使用しようとしませんでしたが、それは動作するはずです。 n
コアの場合、ループの反復k
をk % n
で実行するという考え方です。適切な紹介はMatloffのThe Art of R Programmingにあります here 、16章、Resorting to C。
コードの書き換えは嫌いです...もちろん、ifelseとlapplyの方が優れたオプションですが、それを適合させるのが難しい場合もあります。
df$var[i]
などのリストを使用するように、頻繁にdata.framesを使用します
次に例を示します。
nrow=function(x){ ##required as I use nrow at times.
if(class(x)=='list') {
length(x[[names(x)[1]]])
}else{
base::nrow(x)
}
}
system.time({
d=data.frame(seq=1:10000,r=rnorm(10000))
d$foo=d$r
d$seq=1:5
mark=NA
for(i in 1:nrow(d)){
if(d$seq[i]==1) mark=d$r[i]
d$foo[i]=mark
}
})
system.time({
d=data.frame(seq=1:10000,r=rnorm(10000))
d$foo=d$r
d$seq=1:5
d=as.list(d) #become a list
mark=NA
for(i in 1:nrow(d)){
if(d$seq[i]==1) mark=d$r[i]
d$foo[i]=mark
}
d=as.data.frame(d) #revert back to data.frame
})
data.frameバージョン:
user system elapsed
0.53 0.00 0.53
リストバージョン:
user system elapsed
0.04 0.00 0.03
ベクトルのリストを使用するほうが、data.frameよりも17倍高速です。
この点で内部的にdata.framesが非常に遅い理由についてのコメントはありますか?彼らはリストのように動作すると思います...
さらに高速なコードを作成するには、class(d)='list'
およびd=as.list(d)
の代わりにclass(d)='data.frame'
を実行します
system.time({
d=data.frame(seq=1:10000,r=rnorm(10000))
d$foo=d$r
d$seq=1:5
class(d)='list'
mark=NA
for(i in 1:nrow(d)){
if(d$seq[i]==1) mark=d$r[i]
d$foo[i]=mark
}
class(d)='data.frame'
})
head(d)
ここでの答えは素晴らしいです。カバーされていないマイナーな側面の1つは、質問に「私のPCはまだ動作しています(現在約10時間)であり、ランタイムについてはわかりません」ということです。開発中は、次のコードを常にループに入れて、変更が速度にどのように影響するように見えるか、また完了までにかかる時間を監視します。
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
cat(round(i/nrow(temp)*100,2),"% \r") # prints the percentage complete in realtime.
# do stuff
}
return(blah)
}
Lapplyでも動作します。
dayloop2 <- function(temp){
temp <- lapply(1:nrow(temp), function(i) {
cat(round(i/nrow(temp)*100,2),"% \r")
#do stuff
})
return(temp)
}
ループ内の関数は非常に高速ですが、ループの数が多い場合は、コンソール自体への印刷にはオーバーヘッドがあるため、頻繁に印刷することを検討してください。例えば.
dayloop2 <- function(temp){
for (i in 1:nrow(temp)){
if(i %% 100 == 0) cat(round(i/nrow(temp)*100,2),"% \r") # prints every 100 times through the loop
# do stuff
}
return(temp)
}
Rでは、apply
ファミリー関数を使用することでループ処理を高速化できます(この場合、おそらくreplicate
になります)。進捗バーを提供するplyr
パッケージをご覧ください。
別のオプションは、ループを完全に回避し、それらをベクトル化された算術に置き換えることです。あなたが何をしているのか正確にはわかりませんが、おそらく一度にすべての行に関数を適用できます:
temp[1:nrow(temp), 10] <- temp[1:nrow(temp), 9] + temp[0:(nrow(temp)-1), 10]
これははるかに高速になり、条件に応じて行をフィルタリングできます。
cond.i <- (temp[i, 6] == temp[i-1, 6]) & (temp[i, 3] == temp[i-1, 3])
temp[cond.i, 10] <- temp[cond.i, 9]
ベクトル化された算術演算はより多くの時間と問題について考える必要がありますが、実行時間を数桁節約できる場合があります。
data.table
を使用した処理は実行可能なオプションです。
n <- 1000000
df <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
colnames(df) <- paste("col", 1:9, sep = "")
library(data.table)
dayloop2.dt <- function(df) {
dt <- data.table(df)
dt[, Kumm. := {
res <- .I;
ifelse (res > 1,
ifelse ((col6 == shift(col6, fill = 0)) & (col3 == shift(col3, fill = 0)) ,
res <- col9 + shift(res)
, # else
res <- col9
)
, # else
res <- col9
)
}
,]
res <- data.frame(dt)
return (res)
}
res <- dayloop2.dt(df)
m <- microbenchmark(dayloop2.dt(df), times = 100)
#Unit: milliseconds
# expr min lq mean median uq max neval
#dayloop2.dt(df) 436.4467 441.02076 578.7126 503.9874 575.9534 966.1042 10
条件フィルタリングから得られる可能性のあるゲインを無視すると、非常に高速になります。明らかに、データのサブセットに対して計算を行うことができれば、役立ちます。