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 .