203

R でパフォーマンスに大きな問題がありdata.frameます。オブジェクトを反復処理する関数を作成しました。単に新しい列を a に追加し、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)
}

この操作を高速化する方法はありますか?

4

10 に答える 10

454

最大の問題と非効率性の根源は、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します。名前をいじる必要はありません。それで、それはどれほど良いですか?

1,000~10,000で各機能を1,000ずつ実行して時間data.frameを計測nrowsystem.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 秒かかると予測されます。

ベクトル化の力

シェーンとカリモが回答で述べているように、ベクトル化はパフォーマンスを向上させるための鍵です。コードから、ループの外に移動できます。

  • コンディショニング
  • 結果の初期化 (これは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 から 100,000 までを 10,000 ずつ比較します。

パフォーマンス

チューニングのチューニング

もう 1 つの微調整は、ループのインデックスを変更することですtemp[i,9](res[i]これは、i 番目のループ反復でまったく同じです)。これも、ベクトルのインデックス付けと a のインデックス付けの違い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で入手できます。

于 2010-06-03T22:34:17.143 に答える
136

R コードを高速化するための一般的な戦略

まず、遅い部分が実際にどこにあるかを把握します。実行速度が遅いコードを最適化する必要はありません。少量のコードの場合は、考え抜くだけでうまくいきます。それが失敗した場合は、RProf や同様のプロファイリング ツールが役立ちます。

ボトルネックを把握したら、目的を達成するためのより効率的なアルゴリズムについて考えます。計算は、可能であれば 1 回だけ実行する必要があるため、次のようにします。

より効率的な関数を使用すると、速度が中程度または大幅に向上する可能性があります。たとえば、paste0はわずかな効率の向上をもたらしますが、.colSums()およびその親戚はやや顕著な向上をもたらします。 特に遅いmeanです。

次に、いくつかの特に一般的なトラブルを回避できます。

  • cbind本当にすぐに遅くなります。
  • 毎回展開するのではなく、データ構造を初期化してから入力します。
  • 事前割り当てを使用しても、値渡しアプローチではなく参照渡しアプローチに切り替えることができますが、手間をかける価値はないかもしれません。
  • 回避すべき落とし穴については、R Infernoをご覧ください。

より良いベクトル化を試してみてください。これは多くの場合に役立ちますが、常に役立つとは限りません。この点で、 、 などの本質的にベクトル化されたコマンドはifelse、コマンド群diffよりも改善さapplyれます (よく作成されたループよりも速度がほとんど、またはまったく向上しません)。

R 関数により多くの情報を提供することもできます。たとえば、 ではvapplyなくを使用sapplycolClassesし、テキストベースのデータを読み取るときに指定します。速度の向上は、推測をどれだけ排除するかによって異なります。

次に、最適化されたパッケージを検討します。このdata.tableパッケージは、データ操作や大量のデータの読み取りにおいて、使用可能な場合に大幅な速度向上を実現できます ( fread)。

次に、R を呼び出すより効率的な方法で速度向上を試みます。

  • R スクリプトをコンパイルします。または、Rajitパッケージを組み合わせてジャストインタイム コンパイルを使用します (Dirk はこのプレゼンテーションで例を示しています)。
  • 最適化された BLAS を使用していることを確認してください。これらにより、全体的な速度が向上します。正直なところ、R がインストール時に最も効率的なライブラリを自動的に使用しないのは残念です。Revolution R がここで行った作業をコミュニティ全体に還元することを願っています。
  • Radford Neal は一連の最適化を行い、そのうちのいくつかは R Core に採用され、その他の多くはpqRにフォークされました。

最後に、上記のすべてを行っても必要な速度が得られない場合は、遅いコード スニペット用により高速な言語に移行する必要があるかもしれません。Rcppとhereの組み合わせによりinline、アルゴリズムの最も遅い部分だけを C++ コードに置き換えることが特に簡単になります。たとえば、これは私の最初の試みであり、高度に最適化された R ソリューションでさえ吹き飛ばします。

それでも問題が解決しない場合は、より多くのコンピューティング パワーが必要です。並列化( http://cran.r-project.org/web/views/HighPerformanceComputing.html ) または GPU ベースのソリューション ( ) を調べてくださいgpu-tools

他のガイダンスへのリンク

于 2011-12-12T13:17:29.657 に答える
37

ループを使用している場合は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 
于 2011-06-28T06:55:05.120 に答える
17

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."
于 2010-05-25T22:15:27.627 に答える
8

私はコードを書き直すのが嫌いです...もちろん ifelse と lapply の方が良いオプションですが、それを適合させるのが難しい場合があります。

次のようなリストを使用する場合と同じように、頻繁に data.frames を使用します。df$var[i]

以下は構成例です。

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 が非常に遅い理由について何かコメントはありますか? それらはリストのように動作すると思うでしょう...

さらに高速なコードを作成するには、 andclass(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)
于 2016-08-03T03:37:48.153 に答える
8

アリが回答の最後で述べたように、Rcppandinlineパッケージを使用すると、非常に簡単に高速化できます。例として、次のコードを試してください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)
}

パラメータを渡すだけの ing の同様の手順があり#includeます

inc <- '#include <header.h>

として cxxfunction へinclude=inc。これの本当に優れている点は、リンクとコンパイルのすべてが自動的に行われるため、プロトタイピングが非常に高速になることです。

免責事項: tmp のクラスが数値行列などではなく、数値であるべきかどうかは完全にはわかりません。しかし、私はほとんど確信しています。

編集: この後もさらに速度が必要な場合は、OpenMPは .NET に適した並列化機能ですC++。から使ってみたことはありませんがinline、うまくいくはずです。アイデアは、nコアの場合、ループの反復kを によって実行することk % nです。適切な紹介は、Matloff のThe Art of R Programmingにあり、ここから入手できます。第 16 章、Resorting to Cにあります。

于 2012-07-26T06:15:50.487 に答える
2

Rでは、ファミリ関数を使用してループ処理を高速化できることがよくありapplyます(あなたの場合、おそらくreplicate. plyrプログレスバーを提供するパッケージを見てください。

もう 1 つのオプションは、ループを完全に回避し、ベクトル化された演算に置き換えることです。何をしているのか正確にはわかりませんが、関数を一度にすべての行に適用できる可能性があります。

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]

ベクトル化された算術演算には、より多くの時間と問題について考える必要がありますが、実行時間を数桁節約できる場合があります。

于 2010-05-26T08:37:10.547 に答える
1

での処理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

条件フィルタリングから得られる可能性を無視すると、非常に高速です。明らかに、データのサブセットに対して計算を行うことができれば、役に立ちます。

于 2016-05-10T00:03:25.153 に答える