2011-05-12 12 views
8

Tengo una lista de marcos de datos para los que estoy seguro de que todos contienen al menos una fila (de hecho, algunos contienen solo una fila, y otros contienen un número dado de filas), y que todos tienen las mismas columnas (nombres y tipos). En caso de que importe, también estoy seguro de que no hay NA en ninguna parte de las filas.Rendimiento de rbind.data.frame

La situación se pueden simular de esta manera:

#create one row 
onerowdfr<-do.call(data.frame, c(list(), rnorm(100) , lapply(sample(letters[1:2], 100, replace=TRUE), function(x){factor(x, levels=letters[1:2])}))) 
colnames(onerowdfr)<-c(paste("cnt", 1:100, sep=""), paste("cat", 1:100, sep="")) 
#reuse it in a list 
someParts<-lapply(rbinom(200, 1, 14/200)*6+1, function(reps){onerowdfr[rep(1, reps),]}) 

he establecido los parámetros (de la asignación al azar) para que se aproximan a mi verdadera situación.

Ahora, quiero unir todos estos dataframes en un solo marco de datos. Pensé usando rbind haría el truco, así:

system.time(
result<-do.call(rbind, someParts) 
) 

Ahora, en mi sistema (que no es particularmente lento), y con los ajustes anteriores, esta toma es la salida del system.time:

user system elapsed 
    5.61 0.00 5.62 

Casi 6 segundos para rbind-ing 254 (en mi caso) filas de 200 variables? Seguramente tiene que haber una manera de mejorar el rendimiento aquí? En mi código, tengo que hacer cosas similares muy a menudo (es una imputación múltiple), así que necesito que esto sea lo más rápido posible.

+0

En mi trabajo, combiné una lista de marcos de datos usando una técnica de Dominik aquí http://stackoverflow.com/questions/7224938/can-i-rbind-be-parallelized-in-r/8071176#8071176 que es relativamente más rápido que do.call cuanto más grande es, y encontré un rendimiento aún mejor cuando leo los datos de la lista original con caracteres en lugar de factores. Usar rbind pasó mucho tiempo en el juego; Estoy especulando que es para verificar los niveles de factores para agregar. – ARobertson

Respuesta

13

¿Puede construir sus matrices con variables numéricas solamente y convertir a un factor al final? rbind es mucho más rápido en matrices numéricas.

En mi sistema, utilizando tramas de datos:

> system.time(result<-do.call(rbind, someParts)) 
    user system elapsed 
    2.628 0.000 2.636 

La construcción de la lista con todas las matrices numéricas lugar:

onerowdfr2 <- matrix(as.numeric(onerowdfr), nrow=1) 
someParts2<-lapply(rbinom(200, 1, 14/200)*6+1, 
        function(reps){onerowdfr2[rep(1, reps),]}) 

resultados en mucho más rápido rbind.

> system.time(result2<-do.call(rbind, someParts2)) 
    user system elapsed 
    0.001 0.000 0.001 

EDITAR: Aquí hay otra posibilidad; simplemente combina cada columna por turno.

> system.time({ 
+ n <- 1:ncol(someParts[[1]]) 
+ names(n) <- names(someParts[[1]]) 
+ result <- as.data.frame(lapply(n, function(i) 
+       unlist(lapply(someParts, `[[`, i)))) 
+ }) 
    user system elapsed 
    0.810 0.000 0.813 

Aún así, no es tan rápido como el uso de matrices.

EDIT 2:

Si sólo tiene valores numéricos y los factores, no es tan difícil de convertir a todo lo numérico, rbind ellos, y convertir las columnas necesarias de nuevo a factores. Esto supone que todos los factores tienen exactamente los mismos niveles. La conversión a un factor a partir de un número entero también es más rápida que a partir de un valor numérico, por lo que forzo al número entero primero.

someParts2 <- lapply(someParts, function(x) 
        matrix(unlist(x), ncol=ncol(x))) 
result<-as.data.frame(do.call(rbind, someParts2)) 
a <- someParts[[1]] 
f <- which(sapply(a, class)=="factor") 
for(i in f) { 
    lev <- levels(a[[i]]) 
    result[[i]] <- factor(as.integer(result[[i]]), levels=seq_along(lev), labels=lev) 
} 

El momento en mi sistema es:

user system elapsed 
    0.090 0.00 0.091 
+1

@ Aaron: Los datos son una simulación, la cuestión de OP comienza con los dataframes. –

+0

@Joris: está cerca; puedes extraer cada tipo en su propia lista de matrices, 'rbind' cada lista de tipos, luego crear un data.frame. –

+0

@Joris: Es cierto, esto no responde a la pregunta específica del póster (¿cómo puedo acelerar 'rbind.data.frame'?). Pero tal vez con el conocimiento de que las matrices de encuadernación son más rápidas, puede reescribir su código para evitar el uso de marcos de datos, o convertir a marcos de datos más tarde. Me encantaría ver formas de acelerar realmente 'rbind.data.frame'. – Aaron

5
No

un gran impulso, pero intercambiando rbind para rbind.fill del paquete plyr llama alrededor del 10% de descuento en el tiempo de ejecución (con la muestra de datos, en mi máquina).

3

Esto es ~ 25% más rápido, pero tiene que haber una manera mejor ...

system.time({ 
    N <- do.call(sum, lapply(someParts, nrow)) 
    SP <- as.data.frame(lapply(someParts[[1]], function(x) rep(x,N))) 
    k <- 0 
    for(i in 1:length(someParts)) { 
    j <- k+1 
    k <- k + nrow(someParts[[i]]) 
    SP[j:k,] <- someParts[[i]] 
    } 
}) 
+0

¿Reescribir en código de ensamblaje? –

+0

Construyendo fuera de esto, traté de llenar el marco de datos columna por columna al tomar la columna adecuada de cada elemento con un 'lapply'; parece ser aún más rápido. Ver editar mi respuesta. – Aaron

4

Si realmente quiere manipular sus data.frame s más rápido, sugeriría a utilizar el paquete data.table y la función rbindlist(). No realicé pruebas exhaustivas, pero para mi conjunto de datos (3000 dataframes, 1000 filas x 40 columnas cada uno) rbindlist() toma solo 20 segundos.

1

Asegúrate de que estás uniendo el marco de datos al marco de datos. Se topó con una gran degradación de perf cuando se vincula la lista al marco de datos.