Las applyfunciones en R no proporcionan un rendimiento mejorado sobre otras funciones de bucle (por ejemplo for). Una excepción a esto es lapplyque 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 assigno <<-, 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 applyfunciones familiares (incluso el foreachpaquete es esencialmente equivalente, a pesar del nombre). Aquí hay un ejemplo simple de la sapplyfunció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 ). snowtiene 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 applyfunciones se usen para la ejecución paralela ya que no tienen efectos secundarios . Cuando cambia un valor variable dentro de un forbucle, se establece globalmente. Por otro lado, todas las applyfunciones 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 assigno <<-, 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 fory en *applylo 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 dfen cuenta cómo se altera en el entorno primario forpero no *apply.
applyfamilia 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.snowfallpaquete y probar los ejemplos en su viñeta.snowfallse basa en elsnowpaquete y abstrae los detalles de la paralelización aún más, lo que hace que sea muy simple ejecutarapplyfunciones paralelizadas .foreachdesde entonces está disponible y parece ser muy cuestionado sobre SO.lapplyes "un poco más rápido" que unforbucle. Sin embargo, no veo nada que lo sugiera. Solo menciona quelapplyes más rápido quesapply, lo cual es un hecho bien conocido por otras razones (sapplyintenta 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
*applyes más rápido. Pero creo que el punto más importante son los efectos secundarios (actualicé mi respuesta con un ejemplo).data.tablees 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")])tapplyes 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 laapplylata 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
forlos bucles son más rápidos en mi computadora con Windows 10 de 2 núcleos. Hice esto con5e6elementos: 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
forciclo 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
forbucle y solo un poco más lento que latapplyfunción optimizada incorporada. No es porquevapplysea mucho más rápido queforsino porque solo está realizando una operación en cada iteración del bucle. En este código, todo lo demás está vectorizado. En elforbucle 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 laforversió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.19sapply50% más lentofory ellapplydoble de rápido.yen1:1e6, nonumeric(1e6)(un vector de ceros). Tratando de asignarfoo(0)az[0]una y otra no ilustra bien un típicoforuso bucle. De lo contrario, el mensaje es perfecto.Al aplicar funciones sobre subconjuntos de un vector,
tapplypuede 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
colSumsyrowSums:fuente
microbenchmarkes 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.