El mayor problema y la raíz de la ineficacia es indexar data.frame, me refiero a todas estas líneas donde las usa temp[,]
.
Intenta evitar esto tanto como sea posible. Tomé su función, cambio de indexación y aquí versión_A
dayloop2_A <- function(temp){
res <- numeric(nrow(temp))
for (i in 1:nrow(temp)){
res[i] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
res[i] <- temp[i,9] + res[i-1]
} else {
res[i] <- temp[i,9]
}
} else {
res[i] <- temp[i,9]
}
}
temp$`Kumm.` <- res
return(temp)
}
Como puede ver, creo un vector res
que reúne resultados. Al final lo agrego data.frame
y no necesito meterme con los nombres. Entonces, ¿qué tan mejor es?
Corro para cada función data.frame
con nrow
entre 1.000 y 10.000 antes de 1000 y medir el tiempo consystem.time
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
El resultado es
Puede ver que su versión depende exponencialmente de nrow(X)
. La versión modificada tiene una relación lineal, y el lm
modelo simple predice que para 850,000 filas el cálculo lleva 6 minutos y 10 segundos.
Poder de vectorización
Como Shane y Calimo afirman en sus respuestas, la vectorización es la clave para un mejor rendimiento. Desde su código puede moverse fuera del bucle:
- acondicionamiento
- inicialización de los resultados (que son
temp[i,9]
)
Esto lleva a este código
dayloop2_B <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in 1:nrow(temp)) {
if (cond[i]) res[i] <- temp[i,9] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Compare el resultado de estas funciones, esta vez nrow
de 10,000 a 100,000 por 10,000.
Afinando lo sintonizado
Otro ajuste es cambiar en una indexación de bucle temp[i,9]
a res[i]
(que son exactamente iguales en la iteración del i-ésimo bucle). Nuevamente es la diferencia entre indexar un vector e indexar a data.frame
.
Segunda cosa: cuando observa el bucle, puede ver que no hay necesidad de recorrer todo i
, sino solo los que se ajustan a la condición.
Así que, aquí vamos
dayloop2_D <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in (1:nrow(temp))[cond]) {
res[i] <- res[i] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
El rendimiento que obtiene depende en gran medida de una estructura de datos. Precisamente: en porcentaje de TRUE
valores en la condición. Para mis datos simulados, toma tiempo de cálculo para 850,000 filas por debajo del segundo.
Si quieres puedes ir más lejos, veo al menos dos cosas que se pueden hacer:
- escribir un
C
código para hacer cumsum condicional
si sabe que en su secuencia máxima de datos no es grande, puede cambiar el ciclo a vectorizado, algo así como
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
El código utilizado para simulaciones y figuras está disponible en GitHub .
res = c(1,2,3,4)
ycond
es todoTRUE
, entonces resultado final debe ser:1
,3
(causa1+2
),6
(causa segunda es ahora3
, y tercero es3
también),10
(6+4
). Haciendo simple suma que tienes1
,3
,5
,7
.Estrategias generales para acelerar el código R
Primero, averigua dónde está realmente la parte lenta. No es necesario optimizar el código que no se ejecuta lentamente. Para pequeñas cantidades de código, simplemente pensarlo puede funcionar. Si eso falla, RProf y herramientas de creación de perfiles similares pueden ser útiles.
Una vez que descubra el cuello de botella, piense en algoritmos más eficientes para hacer lo que quiere. Los cálculos solo deben ejecutarse una vez si es posible, por lo tanto:
El uso de funciones más eficientes puede producir ganancias de velocidad moderadas o grandes. Por ejemplo,
paste0
produce una pequeña ganancia de eficiencia pero.colSums()
sus parientes producen ganancias algo más pronunciadas.mean
Es particularmente lento .Entonces puede evitar algunos problemas particularmente comunes :
cbind
te ralentizará muy rápido.Intente una mejor vectorización , que a menudo puede ayudar, pero no siempre. En este sentido, los comandos inherentemente vectorizados como
ifelse
,diff
y similares proporcionarán más mejoras que laapply
familia de comandos (que proporcionan poco o ningún aumento de velocidad en un bucle bien escrito).También puede tratar de proporcionar más información a las funciones R . Por ejemplo, use en
vapply
lugar desapply
, y especifiquecolClasses
al leer datos basados en texto . Las ganancias de velocidad serán variables dependiendo de cuantas conjeturas elimines.A continuación, considere paquetes optimizados : el
data.table
paquete puede producir ganancias masivas de velocidad donde es posible su uso, en la manipulación de datos y en la lectura de grandes cantidades de datos (fread
).A continuación, intente obtener ganancias de velocidad a través de medios más eficientes para llamar a R :
Ra
yjit
en concierto para la compilación justo a tiempo (Dirk tiene un ejemplo en esta presentación ).Y, por último, si todo lo anterior aún no le da la velocidad que necesita, es posible que deba pasar a un idioma más rápido para el fragmento de código lento . La combinación de
Rcpp
yinline
aquí hace que reemplazar solo la parte más lenta del algoritmo con código C ++ sea particularmente fácil. Aquí, por ejemplo, es mi primer intento de hacerlo , y deslumbra incluso las soluciones R altamente optimizadas.Si aún tiene problemas después de todo esto, solo necesita más potencia informática. Examine la paralelización ( http://cran.r-project.org/web/views/HighPerformanceComputing.html ) o incluso soluciones basadas en GPU (
gpu-tools
).Enlaces a otras orientaciones
fuente
Si está utilizando
for
bucles, lo más probable es que esté codificando R como si fuera C o Java u otra cosa. El código R que se vectoriza correctamente es extremadamente rápido.Tome por ejemplo estos dos simples bits de código para generar una lista de 10,000 enteros en secuencia:
El primer ejemplo de código es cómo se codificaría un bucle utilizando un paradigma de codificación tradicional. Tarda 28 segundos en completarse
Puede obtener una mejora de casi 100 veces por la simple acción de preasignar memoria:
Pero usando la operación del vector base R usando el operador de colon,
:
esta operación es prácticamente instantánea:fuente
a[i]
que no cambia. Perosystem.time({a <- NULL; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- 1:1e5; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- NULL; a <- 2*(1:1e5)})
tiene un resultado similar.rep(1, 1e5)
: los tiempos son idénticos.Esto podría hacerse mucho más rápido omitiendo los bucles utilizando índices o
ifelse()
sentencias anidadas .fuente
i
valor depende dei-1
-th, por lo que no se pueden configurar de la manera en que lo hace (usandowhich()-1
).No me gusta reescribir el código ... También, por supuesto, ifelse y lapply son mejores opciones, pero a veces es difícil ajustarlo.
Con frecuencia uso data.frames como uno usaría listas como
df$var[i]
Aquí hay un ejemplo inventado:
versión de data.frame:
lista de versiones:
17 veces más rápido para usar una lista de vectores que un data.frame.
¿Algún comentario sobre por qué internamente data.frames son tan lentos a este respecto? Uno pensaría que operan como listas ...
Para un código aún más rápido, haga esto en
class(d)='list'
lugar ded=as.list(d)
yclass(d)='data.frame'
fuente
[<-.data.frame
, que de alguna manera se llama cuando lo haced$foo[i] = mark
y puede terminar haciendo una nueva copia del vector de posiblemente el data.frame completo en cada<-
modificación. Haría una pregunta interesante sobre SO.df$var[i]
notación pasa por la misma[<-.data.frame
función? Me di cuenta de que es bastante largo. Si no, ¿qué función usa?d$foo[i]=mark
se traduce aproximadamented <- `$<-`(d, 'foo', `[<-`(d$foo, i, mark))
, pero con algún uso de variables temporales.Como Ari mencionó al final de su respuesta, los paquetes
Rcpp
yinline
hacen que sea increíblemente fácil hacer las cosas rápido. Como ejemplo, intente esteinline
código (advertencia: no probado):Hay un procedimiento similar para
#include
las cosas, donde simplemente pasa un parámetroa cxxfunction, como
include=inc
. Lo realmente bueno de esto es que hace todo el enlace y la compilación por ti, por lo que la creación de prototipos es realmente rápida.Descargo de responsabilidad: no estoy totalmente seguro de que la clase de tmp deba ser numérica y no matriz numérica u otra cosa. Pero estoy casi seguro.
Editar: si aún necesita más velocidad después de esto, OpenMP es un recurso de paralelización bueno para
C++
. No he intentado usarloinline
, pero debería funcionar. La idea sería, en el caso de losn
núcleos, hacer que la iteración de buclek
se lleve a cabok % n
. Una introducción adecuada se encuentra en Matloff es el arte de la programación R , disponible aquí , en el capítulo 16, Recurrir a C .fuente
Las respuestas aquí son geniales. Un aspecto menor no cubierto es que la pregunta dice " Mi PC todavía funciona (aproximadamente 10 horas ahora) y no tengo idea sobre el tiempo de ejecución ". Siempre pongo el siguiente código en bucles cuando desarrollo para tener una idea de cómo los cambios parecen afectar la velocidad y también para monitorear cuánto tiempo tomará completar.
Funciona con lapply también.
Si la función dentro del bucle es bastante rápida pero el número de bucles es grande, considere imprimir de vez en cuando, ya que imprimir en la consola tiene una sobrecarga. p.ej
fuente
cat(sprintf("\nNow running... %40s, %s/%s \n", nm[i], i, n))
ya que generalmente estoy recorriendo cosas con nombre (con nombres ennm
).En R, a menudo puede acelerar el procesamiento del bucle utilizando las
apply
funciones familiares (en su caso, probablemente lo seríareplicate
). Echa un vistazo alplyr
paquete que proporciona barras de progreso.Otra opción es evitar los bucles por completo y reemplazarlos con aritmética vectorizada. No estoy seguro exactamente de lo que está haciendo, pero probablemente pueda aplicar su función a todas las filas a la vez:
Esto será mucho más rápido, y luego puede filtrar las filas con su condición:
La aritmética vectorizada requiere más tiempo y pensar en el problema, pero a veces puede ahorrar varios órdenes de magnitud en el tiempo de ejecución.
fuente
Procesar con
data.table
es una opción viable:Si ignora las posibles ganancias del filtrado de condiciones, es muy rápido. Obviamente, si puede hacer el cálculo en el subconjunto de datos, es útil.
fuente