回答:
apply
R の関数は、他のループ関数(などfor
)よりもパフォーマンスが向上していません。これの1つの例外lapply
は、RよりもCコードでより多くの作業を行うため、少し速くなる可能性があります(この例については、この質問を参照してください))。
ただし、一般的には、明確にするために、パフォーマンスではなく、適用関数を使用するというルールがあります。
これに加えて、適用関数には副作用がありません。これは、Rを使用した関数型プログラミングに関しては重要な違いです。これは、assign
またはを使用してオーバーライド<<-
できますが、非常に危険な場合があります。変数の状態は履歴に依存するため、副作用によってプログラムが理解しにくくなります。
編集:
フィボナッチ数列を再帰的に計算する簡単な例でこれを強調するだけです。これを複数回実行して正確な測定値を取得することもできますが、重要な点は、どのメソッドもパフォーマンスが大きく異なることはないということです。
> fibo <- function(n) {
+ if ( n < 2 ) n
+ else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
user system elapsed
7.48 0.00 7.52
> system.time(sapply(0:26, fibo))
user system elapsed
7.50 0.00 7.54
> system.time(lapply(0:26, fibo))
user system elapsed
7.48 0.04 7.54
> library(plyr)
> system.time(ldply(0:26, fibo))
user system elapsed
7.52 0.00 7.58
編集2:
Rの並列パッケージ(rpvm、rmpi、snowなど)の使用に関しては、これらは一般にapply
ファミリー関数を提供します(foreach
名前にかかわらず、パッケージは基本的に同等です)。次に、sapply
関数の簡単な例を示しますsnow
。
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
この例では、追加のソフトウェアをインストールする必要のないソケットクラスターを使用しています。それ以外の場合は、PVMやMPIのようなものが必要になります(Tierneyのクラスタリングページを参照)。 snow
次の適用機能があります。
parLapply(cl, x, fun, ...)
parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
parApply(cl, X, MARGIN, FUN, ...)
parRapply(cl, x, fun, ...)
parCapply(cl, x, fun, ...)
それは理にかなってapply
、彼らはので、関数は並列実行のために使用されるべき一切持っていない副作用を。for
ループ内で変数値を変更すると、グローバルに設定されます。一方、apply
変更は関数呼び出しに対してローカルであるため、すべての関数を安全に並行して使用できます(使用しようとしない限り)assign
か、<<-
、副作用が発生する可能性があります)。言うまでもなく、特に並列実行を処理する場合は、ローカル変数とグローバル変数に注意することが重要です。
編集:
副作用に関する限りfor
、*apply
これまでの違いを示す簡単な例を次に示します。
> df <- 1:10
> # *apply example
> lapply(2:3, function(i) df <- df * i)
> df
[1] 1 2 3 4 5 6 7 8 9 10
> # for loop example
> for(i in 2:3) df <- df * i
> df
[1] 6 12 18 24 30 36 42 48 54 60
df
によって親環境のがどのように変更されfor
たかに注意してください*apply
。
snowfall
パッケージを見て、ビネットで例を試すことをお勧めします。 パッケージのsnowfall
上に構築しsnow
、並列化の詳細を抽象化して、並列化されたapply
関数を実行するのを非常に簡単にします。
foreach
が、それ以降利用可能になり、SOで多くのことを尋ねられているようです。
lapply
、for
ループより「少し速い」場合の例として別の質問にリンクします。しかし、そこにはそのような示唆は見られません。あなたはそれlapply
がよりも速いことだけを述べますがsapply
、これは他の理由でよく知られている事実です(sapply
出力を簡略化しようとするため、多くのデータサイズチェックと潜在的な変換を行わなければなりません)。に関連するものはありませんfor
。何か不足していますか?
複数の因子のグループに基づいて平均を取得するためにforループをネストする必要がある場合など、スピードアップが大幅になることがあります。ここでは、まったく同じ結果が得られる2つのアプローチがあります。
set.seed(1) #for reproducability of the results
# The data
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))
# the function forloop that averages X over every combination of Y and Z
forloop <- function(x,y,z){
# These ones are for optimization, so the functions
#levels() and length() don't have to be called more than once.
ylev <- levels(y)
zlev <- levels(z)
n <- length(ylev)
p <- length(zlev)
out <- matrix(NA,ncol=p,nrow=n)
for(i in 1:n){
for(j in 1:p){
out[i,j] <- (mean(x[y==ylev[i] & z==zlev[j]]))
}
}
rownames(out) <- ylev
colnames(out) <- zlev
return(out)
}
# Used on the generated data
forloop(X,Y,Z)
# The same using tapply
tapply(X,list(Y,Z),mean)
どちらも結果はまったく同じで、平均値と名前付きの行と列を含む5 x 10の行列です。だが :
> system.time(forloop(X,Y,Z))
user system elapsed
0.94 0.02 0.95
> system.time(tapply(X,list(Y,Z),mean))
user system elapsed
0.06 0.00 0.06
どうぞ。私は何を獲得しましたか?;-)
*apply
より速いです。しかし、もっと重要な点は副作用だと思います(例で私の答えを更新しました)。
data.table
は、さらに速く、「より簡単」だと思います。library(data.table)
dt<-data.table(X,Y,Z,key=c("Y,Z"))
system.time(dt[,list(X_mean=mean(X)),by=c("Y,Z")])
tapply
特定のタスクのための専門的な機能であるのです、それは速くforループよりも理由ですが。forループができることはできません(通常のapply
ことができます)。リンゴとオレンジを比較しています。
...そして私が他の場所で書いたように、vapplyはあなたの友達です!...それはsapplyに似ていますが、戻り値の型も指定するので、はるかに高速になります。
foo <- function(x) x+1
y <- numeric(1e6)
system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)})
# user system elapsed
# 3.54 0.00 3.53
system.time(z <- lapply(y, foo))
# user system elapsed
# 2.89 0.00 2.91
system.time(z <- vapply(y, foo, numeric(1)))
# user system elapsed
# 1.35 0.00 1.36
2020年1月1日更新:
system.time({z1 <- numeric(1e6); for(i in seq_along(y)) z1[i] <- foo(y[i])})
# user system elapsed
# 0.52 0.00 0.53
system.time(z <- lapply(y, foo))
# user system elapsed
# 0.72 0.00 0.72
system.time(z3 <- vapply(y, foo, numeric(1)))
# user system elapsed
# 0.7 0.0 0.7
identical(z1, z3)
# [1] TRUE
for
私のWindows 10、2コアコンピューターではループが高速です。私はこれを5e6
要素で行いました-ループは2.9秒でしたが、では3.1秒でしたvapply
。
Shaneのような例では、さまざまな種類のループ構文間のパフォーマンスの違いに実際にはストレスがかからないことを他の場所で書いています。さらに、コードは、メモリを持たないforループと、値を返すApplyファミリー関数を不当に比較します。ここでは、ポイントを強調する少し異なる例を示します。
foo <- function(x) {
x <- x+1
}
y <- numeric(1e6)
system.time({z <- numeric(1e6); for(i in y) z[i] <- foo(i)})
# user system elapsed
# 4.967 0.049 7.293
system.time(z <- sapply(y, foo))
# user system elapsed
# 5.256 0.134 7.965
system.time(z <- lapply(y, foo))
# user system elapsed
# 2.179 0.126 3.301
結果を保存することを計画している場合は、ファミリー関数を適用することは、構文上の糖よりもはるかに多くなる可能性があります。
(zの単純なunlistはたったの0.2秒なので、ラッププライははるかに高速です。forループでのzの初期化は非常に高速です。これは、6回のうち最後の5回の実行の平均を与えて、システムの外側に移動すると、物事にはほとんど影響しません)
ただし、もう1つ注意すべき点は、パフォーマンス、明確さ、または副作用の欠如に関係なく、applyファミリー関数を使用する別の理由があることです。あfor
ループは、通常、ループ内で可能な限り入れて推進しています。これは、各ループで(他の可能な操作の中でも)情報を格納するための変数の設定が必要なためです。適用ステートメントは、逆にバイアスされる傾向があります。多くの場合、データに対して複数の操作を実行する必要があり、そのいくつかはベクトル化できますが、一部はベクトル化できない場合があります。Rでは、他の言語とは異なり、それらの操作を分離し、applyステートメント(または関数のベクトル化バージョン)でベクトル化されていないものと、真のベクトル操作としてベクトル化されているものを実行するのが最善です。これにより、パフォーマンスが大幅にスピードアップすることがよくあります。
Joris Meysの例を使用して、従来のforループを便利なR関数に置き換えます。これを使用して、専用の関数を使用せずに、同様のスピードアップでよりRフレンドリーな方法でコードを記述する効率を示すことができます。
set.seed(1) #for reproducability of the results
# The data - copied from Joris Meys answer
X <- rnorm(100000)
Y <- as.factor(sample(letters[1:5],100000,replace=T))
Z <- as.factor(sample(letters[1:10],100000,replace=T))
# an R way to generate tapply functionality that is fast and
# shows more general principles about fast R coding
YZ <- interaction(Y, Z)
XS <- split(X, YZ)
m <- vapply(XS, mean, numeric(1))
m <- matrix(m, nrow = length(levels(Y)))
rownames(m) <- levels(Y)
colnames(m) <- levels(Z)
m
これは、for
ループよりもはるかに速くなり、組み込みの最適化されたtapply
関数よりも少し遅くなります。これは、vapply
がそれよりもはるかに高速でfor
あるからではなく、ループの各反復で1つの操作しか実行していないためです。このコードでは、その他すべてがベクトル化されています。Joris Meysの従来のfor
ループでは、多くの(7?)操作が各反復で発生しており、それを実行するためだけのかなりの設定があります。これがfor
バージョンよりもはるかにコンパクトであることにも注意してください。
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
し、vapplyにも優れている:1.19 0.00 1.19
sapply
50%遅くfor
、lapply
2倍速くなりました。
y
することを意味します。割り当てしようと何度もしてよく、典型的な図示していないループの使用を。それ以外の場合、メッセージは適切です。1:1e6
numeric(1e6)
foo(0)
z[0]
for
ベクトルのサブセットに関数を適用する場合tapply
は、forループよりもかなり高速です。例:
df <- data.frame(id = rep(letters[1:10], 100000),
value = rnorm(1000000))
f1 <- function(x)
tapply(x$value, x$id, sum)
f2 <- function(x){
res <- 0
for(i in seq_along(l <- unique(x$id)))
res[i] <- sum(x$value[x$id == l[i]])
names(res) <- l
res
}
library(microbenchmark)
> microbenchmark(f1(df), f2(df), times=100)
Unit: milliseconds
expr min lq median uq max neval
f1(df) 28.02612 28.28589 28.46822 29.20458 32.54656 100
f2(df) 38.02241 41.42277 41.80008 42.05954 45.94273 100
apply
ただし、ほとんどの状況では速度は向上せず、場合によってはさらに遅くなることもあります。
mat <- matrix(rnorm(1000000), nrow=1000)
f3 <- function(x)
apply(x, 2, sum)
f4 <- function(x){
res <- 0
for(i in 1:ncol(x))
res[i] <- sum(x[,i])
res
}
> microbenchmark(f3(mat), f4(mat), times=100)
Unit: milliseconds
expr min lq median uq max neval
f3(mat) 14.87594 15.44183 15.87897 17.93040 19.14975 100
f4(mat) 12.01614 12.19718 12.40003 15.00919 40.59100 100
しかし、このような状況のために、私たちは持っているcolSums
とrowSums
:
f5 <- function(x)
colSums(x)
> microbenchmark(f5(mat), times=100)
Unit: milliseconds
expr min lq median uq max neval
f5(mat) 1.362388 1.405203 1.413702 1.434388 1.992909 100
microbenchmark
よりも正確であることに注意することが重要ですsystem.time
。比較しようとするsystem.time(f3(mat))
と、system.time(f4(mat))
ほとんど毎回異なる結果が得られます。時には、適切なベンチマークテストだけが最速の関数を示すことができます。
apply
、関数ファミリを通じて並列化も実装します。したがって、applyを使用するようにプログラムを構造化すると、非常に小さな限界コストでプログラムを並列化できます。