ベストプラクティスの問題として、関数を作成して行列全体で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
ループは、単純な数値ベクトルで問題ありません。そして、渡されたものと同じクラスのオブジェクトを返します。
チェイスが言ったように:ベクトル化の力を使用してください。ここでは、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
_
適用(およびプライヤー)ファミリーの関数のポイントは、速度ではなく表現力です。また、ループで必要な簿記コードを排除するため、バグを防ぐ傾向があります。
最近、stackoverflowに関する回答はスピードを強調しすぎています。コンピューターが高速化し、R-coreがRの内部を最適化するにつれて、コードはそれ自体で高速になります。コードがそれ自体でよりエレガントになったり、理解しやすくなることは決してありません。
この場合、両方の長所を活用できます。ベクトル化を使用したエレガントな回答であり、非常に高速です。(million > 0) * 2 - 1
。
必要に応じて、ベクトルに対して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
この場合、インデックスベースの置換を行う方が、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