web-dev-qa-db-ja.com

apply()メソッドがRのforループよりも遅いのはなぜですか?

ベストプラクティスの問題として、関数を作成して行列全体でapply()する方がよいのか、それとも単純に関数を介して行列をループする方がよいのかを判断しようとしています。両方の方法で試してみましたが、apply()の方が遅いことに驚きました。タスクは、ベクトルを取得して正または負のいずれかとして評価し、正の場合は1、負の場合は-1のベクトルを返すことです。 mash()関数がループし、squish()関数がapply()関数に渡されます。

_million  <- as.matrix(rnorm(100000))

mash <- function(x){
  for(i in 1:NROW(x))
    if(x[i] > 0) {
      x[i] <- 1
    } else {
      x[i] <- -1
    }
    return(x)
}

squish <- function(x){
  if(x >0) {
    return(1)
  } else {
    return(-1)
  }
}


ptm <- proc.time()
loop_million <- mash(million)
proc.time() - ptm


ptm <- proc.time()
apply_million <- apply(million,1, squish)
proc.time() - ptm
_

_loop_million_結果:

_user  system elapsed 
0.468   0.008   0.483 
_

_apply_million_結果:

_user  system elapsed 
1.401   0.021   1.423 
_

パフォーマンスが低下した場合、forループよりもapply()を使用する利点は何ですか?私のテストに欠陥はありますか?結果として得られた2つのオブジェクトを手がかりとして比較したところ、次のことがわかりました。

_> class(apply_million)
[1] "numeric"
> class(loop_million)
[1] "matrix"
_

それは謎を深めるだけです。 apply()関数は単純な数値ベクトルを受け入れることができないため、最初にas.matrix()を使用してキャストしました。しかし、それからそれは数値を返します。 forループは、単純な数値ベクトルで問題ありません。そして、渡されたものと同じクラスのオブジェクトを返します。

21
Milktrader

チェイスが言ったように:ベクトル化の力を使用してください。ここでは、2つの悪い解決策を比較しています。

適用ソリューションが遅い理由を明確にするには:

Forループ内では、実際には行列のベクトル化されたインデックスを使用します。つまり、型の変換は行われていません。ここでは少し大雑把に説明しますが、基本的に内部計算では次元を無視します。それらは属性として保持され、行列を表すベクトルとともに返されます。説明する :

_> x <- 1:10
> attr(x,"dim") <- c(5,2)
> y <- matrix(1:10,ncol=2)
> all.equal(x,y)
[1] TRUE
_

ここで、applyを使用すると、行列は内部で100,000行のベクトルに分割され、すべての行ベクトル(つまり、単一の数値)が関数に渡され、最終的に結果が適切な形式に結合されます。この場合、apply関数はベクトルが最適であると見なすため、すべての行の結果を連結する必要があります。これには時間がかかります。

また、sapply関数は最初にas.vector(unlist(...))を使用して何かをベクトルに変換し、最後に答えを適切な形式に単純化しようとします。また、これには時間がかかるため、ここでもサプリが遅くなる可能性があります。それでも、それは私のマシンにはありません。

適用がここでの解決策になる場合(そしてそうではない場合)、比較することができます:

_> system.time(loop_million <- mash(million))
   user  system elapsed 
   0.75    0.00    0.75    
> system.time(sapply_million <- matrix(unlist(sapply(million,squish,simplify=F))))
   user  system elapsed 
   0.25    0.00    0.25 
> system.time(sapply2_million <- matrix(sapply(million,squish)))
   user  system elapsed 
   0.34    0.00    0.34 
> all.equal(loop_million,sapply_million)
[1] TRUE
> all.equal(loop_million,sapply2_million)
[1] TRUE
_
12
Joris Meys

適用(およびプライヤー)ファミリーの関数のポイントは、速度ではなく表現力です。また、ループで必要な簿記コードを排除するため、バグを防ぐ傾向があります。

最近、stackoverflowに関する回答はスピードを強調しすぎています。コンピューターが高速化し、R-coreがRの内部を最適化するにつれて、コードはそれ自体で高速になります。コードがそれ自体でよりエレガントになったり、理解しやすくなることは決してありません。

この場合、両方の長所を活用できます。ベクトル化を使用したエレガントな回答であり、非常に高速です。(million > 0) * 2 - 1

40
hadley

必要に応じて、ベクトルに対してlapplyまたはsapplyを使用できます。ただし、ジョブに適切なツール(この場合はifelse())を使用してみませんか?

> ptm <- proc.time()
> ifelse_million <- ifelse(million > 0,1,-1)
> proc.time() - ptm
   user  system elapsed 
  0.077   0.007   0.093 

> all.equal(ifelse_million, loop_million)
[1] TRUE

比較のために、forループとsapplyを使用した2つの比較可能な実行を次に示します。

> ptm <- proc.time()
> apply_million <- sapply(million, squish)
> proc.time() - ptm
   user  system elapsed 
  0.469   0.004   0.474 
> ptm <- proc.time()
> loop_million <- mash(million)
> proc.time() - ptm
   user  system elapsed 
  0.408   0.001   0.417 
6
Chase

この場合、インデックスベースの置換を行う方が、ifelse()*apply()ファミリ、またはループよりもはるかに高速です。

> million  <- million2 <- as.matrix(rnorm(100000))
> system.time(million3 <- ifelse(million > 0, 1, -1))
   user  system elapsed 
  0.046   0.000   0.044 
> system.time({million2[(want <- million2 > 0)] <- 1; million2[!want] <- -1}) 
   user  system elapsed 
  0.006   0.000   0.007 
> all.equal(million2, million3)
[1] TRUE

これらすべてのツールを指先で使用することは非常に価値があります。 (数か月または数年後にコードを理解する必要があるため)最も意味のあるものを使用して、計算時間が法外になったら、より最適化されたソリューションに移行し始めることができます。

Forループの速度の利点のより良い例。

for_loop <- function(x){
    out <- vector(mode="numeric",length=NROW(x))
    for(i in seq(length(out)))
        out[i] <- max(x[i,])
    return(out)
    }

apply_loop <- function(x){
    apply(x,1,max)
}

million  <- matrix(rnorm(1000000),ncol=10)
> system.time(apply_loop(million))
  user  system elapsed 
  0.57    0.00    0.56 
> system.time(for_loop(million))
  user  system elapsed 
  0.32    0.00    0.33 

編集

エドゥアルドによって提案されたバージョン。

max_col <- function(x){
    x[cbind(seq(NROW(x)),max.col(x))]
}

行ごと

> system.time(for_loop(million))
   user  system elapsed 
   0.99    0.00    1.11 
> system.time(apply_loop(million))
  user  system elapsed 
   1.40    0.00    1.44 
> system.time(max_col(million))
  user  system elapsed 
  0.06    0.00    0.06 

列別

> system.time(for_loop(t(million)))
  user  system elapsed 
  0.05    0.00    0.05 
> system.time(apply_loop(t(million)))
  user  system elapsed 
  0.07    0.00    0.07 
> system.time(max_col(t(million)))
  user  system elapsed 
  0.04    0.00    0.06 
3
Wojciech Sobala