Функции apply
в R не предоставляют улучшенная производительность по сравнению с другими функциями цикла (например, для
). Единственным исключением является lapply
, который может быть немного быстрее, потому что он выполняет больше работы в коде C, чем в R (см. этот вопрос для примера ).
Но в целом правило таково, что вы должны использовать функцию применения для ясности, а не для производительности .
Я бы добавил к этому, что функции apply не имеют побочных эффектов , что является важным отличием, когда дело доходит до функционального программирования с 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 (см. страницу кластеризации Тирни ). snow
имеет следующие функции apply:
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
или << -
], и в этом случае вы можете вызвать побочные эффекты). Излишне говорить, что очень важно быть осторожным с локальными и глобальными переменными, особенно при параллельном выполнении.
Изменить:
Вот тривиальный пример, демонстрирующий разницу между для
и * применить
в отношении побочных эффектов:
> 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
в родительской среде изменяется на для
, но не на * apply
.
Иногда ускорение может быть значительным, например, когда вам нужно вложить циклы for, чтобы получить среднее значение на основе группировки более чем одного фактора. Здесь у вас есть два подхода, которые дают вам один и тот же результат:
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
Ну вот. Что я выиграл? ;-)