Las apply
funciones en R no proporcionan un rendimiento mejorado sobre otras funciones de bucle (por ejemplo for
). Una excepción a esto es lapply
que puede ser un poco más rápido porque funciona más en código C que en R (vea esta pregunta para ver un ejemplo de esto ).
Pero, en general, la regla es que debe usar una función de aplicación para mayor claridad, no para el rendimiento .
Agregaría a esto que las funciones de aplicación no tienen efectos secundarios , lo cual es una distinción importante cuando se trata de programación funcional con R. Esto puede anularse usando assign
o <<-
, pero puede ser muy peligroso. Los efectos secundarios también hacen que un programa sea más difícil de entender ya que el estado de una variable depende del historial.
Editar:
Solo para enfatizar esto con un ejemplo trivial que calcula recursivamente la secuencia de Fibonacci; Esto podría ejecutarse varias veces para obtener una medida precisa, pero el punto es que ninguno de los métodos tiene un rendimiento significativamente diferente:
> 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
Edición 2:
Con respecto al uso de paquetes paralelos para R (por ejemplo, rpvm, rmpi, snow), estos generalmente proporcionan apply
funciones familiares (incluso el foreach
paquete es esencialmente equivalente, a pesar del nombre). Aquí hay un ejemplo simple de la sapply
función en snow
:
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
Este ejemplo utiliza un clúster de socket, para el que no es necesario instalar ningún software adicional; de lo contrario, necesitará algo como PVM o MPI (consulte la página de agrupación de Tierney ). snow
tiene las siguientes funciones de aplicación:
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, ...)
Tiene sentido que las apply
funciones se usen para la ejecución paralela ya que no tienen efectos secundarios . Cuando cambia un valor variable dentro de un for
bucle, se establece globalmente. Por otro lado, todas las apply
funciones se pueden usar de forma segura en paralelo porque los cambios son locales a la llamada a la función (a menos que intente usar assign
o <<-
, en cuyo caso, puede introducir efectos secundarios). No es necesario decir que es fundamental tener cuidado con las variables locales frente a las globales, especialmente cuando se trata de una ejecución paralela.
Editar:
Aquí hay un ejemplo trivial para demostrar la diferencia entre for
y en *apply
lo que respecta a los efectos secundarios:
> 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
Tenga df
en cuenta cómo se altera en el entorno primario for
pero no *apply
.
apply
familia de funciones. Por lo tanto, la estructuración de programas para que utilicen aplicar les permite ser paralelizados a un costo marginal muy pequeño.snowfall
paquete y probar los ejemplos en su viñeta.snowfall
se basa en elsnow
paquete y abstrae los detalles de la paralelización aún más, lo que hace que sea muy simple ejecutarapply
funciones paralelizadas .foreach
desde entonces está disponible y parece ser muy cuestionado sobre SO.lapply
es "un poco más rápido" que unfor
bucle. Sin embargo, no veo nada que lo sugiera. Solo menciona quelapply
es más rápido quesapply
, lo cual es un hecho bien conocido por otras razones (sapply
intenta simplificar la salida y, por lo tanto, tiene que hacer una gran cantidad de verificación de tamaño de datos y posibles conversiones). Nada relacionado confor
. ¿Me estoy perdiendo de algo?A veces, la aceleración puede ser sustancial, como cuando tienes que anidar for-loops para obtener el promedio basado en una agrupación de más de un factor. Aquí tiene dos enfoques que le dan exactamente el mismo resultado:
Ambos dan exactamente el mismo resultado, siendo una matriz de 5 x 10 con los promedios y las filas y columnas con nombre. Pero :
Ahí tienes. ¿Qué gané? ;-)
fuente
*apply
es más rápido. Pero creo que el punto más importante son los efectos secundarios (actualicé mi respuesta con un ejemplo).data.table
es aún más rápido y creo que "más fácil".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
es una función especializada para una tarea específica, que es por eso que es más rápido que un bucle. No puede hacer lo que puede hacer un bucle for (mientras que laapply
lata regular ). Estás comparando manzanas con naranjas.... y como acabo de escribir en otra parte, ¡vapply es tu amigo! ... es como sapply, pero también especifica el tipo de valor de retorno que lo hace mucho más rápido.
1 de enero de 2020 actualización:
fuente
for
los bucles son más rápidos en mi computadora con Windows 10 de 2 núcleos. Hice esto con5e6
elementos: un bucle fue de 2.9 segundos frente a 3.1 segundosvapply
.He escrito en otra parte que un ejemplo como el de Shane realmente no enfatiza la diferencia en el rendimiento entre los diversos tipos de sintaxis de bucle porque todo el tiempo se gasta dentro de la función en lugar de realmente estresar el bucle. Además, el código compara injustamente un bucle for sin memoria con funciones familiares de aplicación que devuelven un valor. Aquí hay un ejemplo ligeramente diferente que enfatiza el punto.
Si planea guardar el resultado, aplicar las funciones familiares puede ser mucho más que azúcar sintáctica.
(la simple eliminación de la lista de z es solo 0.2s, por lo que el lapply es mucho más rápido. Inicializar la z en el bucle for es bastante rápido porque estoy dando el promedio de las últimas 5 de 6 carreras, de modo que me muevo fuera del sistema. apenas afecta las cosas)
Sin embargo, una cosa más a tener en cuenta es que hay otra razón para usar las funciones familiares independientemente de su rendimiento, claridad o falta de efectos secundarios. Un
for
ciclo típicamente promueve poner lo más posible dentro del ciclo. Esto se debe a que cada bucle requiere la configuración de variables para almacenar información (entre otras posibles operaciones). Las declaraciones de aplicación tienden a estar sesgadas al revés. Muchas veces desea realizar múltiples operaciones en sus datos, varias de las cuales se pueden vectorizar pero otras no. En R, a diferencia de otros lenguajes, es mejor separar esas operaciones y ejecutar las que no están vectorizadas en una declaración de aplicación (o versión vectorizada de la función) y las que están vectorizadas como operaciones vectoriales verdaderas. Esto a menudo acelera enormemente el rendimiento.Tomando el ejemplo de Joris Meys donde reemplaza un bucle for tradicional con una práctica función R, podemos usarlo para mostrar la eficiencia de escribir código de una manera más amigable R para una aceleración similar sin la función especializada.
Esto termina siendo mucho más rápido que el
for
bucle y solo un poco más lento que latapply
función optimizada incorporada. No es porquevapply
sea mucho más rápido quefor
sino porque solo está realizando una operación en cada iteración del bucle. En este código, todo lo demás está vectorizado. En elfor
bucle tradicional de Joris Meys, se producen muchas operaciones (7?) En cada iteración y hay bastante configuración solo para que se ejecute. Tenga en cuenta también cuánto más compacto es esto que lafor
versión.fuente
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
, y vapply es aún mejor:1.19 0.00 1.19
sapply
50% más lentofor
y ellapply
doble de rápido.y
en1:1e6
, nonumeric(1e6)
(un vector de ceros). Tratando de asignarfoo(0)
az[0]
una y otra no ilustra bien un típicofor
uso bucle. De lo contrario, el mensaje es perfecto.Al aplicar funciones sobre subconjuntos de un vector,
tapply
puede ser bastante más rápido que un bucle for. Ejemplo:apply
, sin embargo, en la mayoría de las situaciones no proporciona ningún aumento de velocidad, y en algunos casos puede ser mucho más lento:Pero para estas situaciones tenemos
colSums
yrowSums
:fuente
microbenchmark
es mucho más preciso quesystem.time
. Si intenta compararsystem.time(f3(mat))
ysystem.time(f4(mat))
obtendrá resultados diferentes casi cada vez. A veces, solo una prueba de referencia adecuada puede mostrar la función más rápida.