2012-07-14 11 views
36

De acuerdo con Creating an R dataframe row-by-row, no es ideal para anexar a data.frame usando rbind, ya que crea una copia del data.frame entero cada vez. ¿Cómo acumulo datos en R dando como resultado un data.frame sin incurrir en esta penalización? El formato intermedio no necesita ser un data.frame.Crecimiento de un data.frame de manera eficiente de memoria

+0

Editado para dejar claro lo que estoy bastante seguro de que quería decir. Por favor revertir si me equivoqué. –

+0

Si todavía está interesado, [aquí hay otro punto de referencia de otro conjunto de formas diferentes de crecer data.frame] (http://stackoverflow.com/questions/20689650/how-to-append-rows-to-an-r -data-frame/38052208 # 38052208) cuando no se conoce el tamaño por adelantado. –

Respuesta

38

Primera aproximación

intenté acceder a cada elemento de una hoja.de.datos preasignados:

res <- data.frame(x=rep(NA,1000), y=rep(NA,1000)) 
tracemem(res) 
for(i in 1:1000) { 
    res[i,"x"] <- runif(1) 
    res[i,"y"] <- rnorm(1) 
} 

Pero tracemem se vuelve loco (por ejemplo, el hoja.de.datos está siendo copiado a una nueva dirección cada vez).

enfoque alternativo (no funciona bien)

Un enfoque (no estoy seguro que es más rápido ya que no he referenciado aún) es crear una lista de data.frames, entonces stack a todos juntos:

makeRow <- function() data.frame(x=runif(1),y=rnorm(1)) 
res <- replicate(1000, makeRow(), simplify=FALSE) # returns a list of data.frames 
library(taRifx) 
res.df <- stack(res) 

Desafortunadamente al crear la lista creo que será difícil preasignar. Por ejemplo:

> tracemem(res) 
[1] "<0x79b98b0>" 
> res[[2]] <- data.frame() 
tracemem[0x79b98b0 -> 0x71da500]: 

En otras palabras, la sustitución de un elemento de la lista hace que la lista se copie. Supongo que toda la lista, pero es posible que sea solo ese elemento de la lista. No estoy íntimamente familiarizado con los detalles de la administración de memoria de R.

Probablemente el mejor enfoque

Al igual que con muchos de velocidad o los procesos de memoria limitada en estos días, el mejor enfoque bien puede ser el uso de data.table en lugar de un data.frame. Desde data.table tiene la := asignar por el operador de referencia, se puede actualizar sin volver a copiar:

library(data.table) 
dt <- data.table(x=rep(0,1000), y=rep(0,1000)) 
tracemem(dt) 
for(i in 1:1000) { 
    dt[i,x := runif(1)] 
    dt[i,y := rnorm(1)] 
} 
# note no message from tracemem 

Pero, como señala @MatthewDowle, set() es la forma apropiada de hacer esto dentro de un bucle. Si lo hace, lo hace más rápido aún:

library(data.table) 
n <- 10^6 
dt <- data.table(x=rep(0,n), y=rep(0,n)) 

dt.colon <- function(dt) { 
    for(i in 1:n) { 
    dt[i,x := runif(1)] 
    dt[i,y := rnorm(1)] 
    } 
} 

dt.set <- function(dt) { 
    for(i in 1:n) { 
    set(dt,i,1L, runif(1)) 
    set(dt,i,2L, rnorm(1)) 
    } 
} 

library(microbenchmark) 
m <- microbenchmark(dt.colon(dt), dt.set(dt),times=2) 

(resultados mostrados abajo)

Benchmarking

con la carrera del bucle de 10.000 veces, tabla de datos es casi un orden total de magnitud más rápido:

Unit: seconds 
      expr  min   lq  median   uq  max 
1 test.df() 523.49057 523.49057 524.52408 525.55759 525.55759 
2 test.dt() 62.06398 62.06398 62.98622 63.90845 63.90845 
3 test.stack() 1196.30135 1196.30135 1258.79879 1321.29622 1321.29622 

benchmarks

y comparación de := con set():

> m 
Unit: milliseconds 
      expr  min  lq median  uq  max 
1 dt.colon(dt) 654.54996 654.54996 656.43429 658.3186 658.3186 
2 dt.set(dt) 13.29612 13.29612 15.02891 16.7617 16.7617 

Tenga en cuenta que aquí es n 10^6 no 10^5 como en los puntos de referencia trazada anteriormente.Entonces hay un orden de magnitud más de trabajo, y el resultado se mide en milisegundos en lugar de segundos. Impresionante de hecho.

+3

Por lo que puedo decir, su último ejemplo no hace crecer el data.table. Simplemente sobrescribe la primera fila 1,000 veces. – Andrie

+0

@Andrie. Oops. Solucionado eso. Gracias por mencionarlo. –

+3

Eso está bien, pero ¿has visto el ejemplo de velocidad en la parte inferior de '?": = "' Comparando ': =' dentro de un bucle con 'set()' dentro de un bucle. ': =' tiene sobrecarga (p.verificando la existencia y el tipo de argumentos pasados ​​a '[.data.table'], que es el motivo por el cual' set() 'se proporciona para el uso dentro de loops. –

5

Me gusta RSQLite para el caso: dbWriteTable(...,append=TRUE) declaraciones durante la recopilación, y dbReadTable declaración al final.

Si los datos son lo suficientemente pequeños, se puede usar el archivo ": memoria:", si es grande, el disco duro.

Por supuesto, no puede competir en términos de velocidad:

makeRow <- function() data.frame(x=runif(1),y=rnorm(1)) 

library(RSQLite) 
con <- dbConnect(RSQLite::SQLite(), ":memory:") 

collect1 <- function(n) { 
    for (i in 1:n) dbWriteTable(con, "test", makeRow(), append=TRUE) 
    dbReadTable(con, "test", row.names=NULL) 
} 

collect2 <- function(n) { 
    res <- data.frame(x=rep(NA, n), y=rep(NA, n)) 
    for(i in 1:n) res[i,] <- makeRow()[1,] 
    res 
} 

> system.time(collect1(1000)) 
    User  System verstrichen 
    7.01  0.00  7.05 
> system.time(collect2(1000)) 
    User  System verstrichen 
    0.80  0.01  0.81 

Pero podría ser mejor si los data.frame s tienen más de una fila. Y no necesita saber el número de filas por adelantado.

+0

La idea es genial, pero [está lejos de ser eficiente] (http://stackoverflow.com/questions/20689650/how-to-append-rows-to-an-r-data-frame/38052208#38052208). Lo puse en una prueba en otro hilo. –

5

También podría tener un objeto de la lista vacía donde los elementos se llenan con marcos de datos; luego recoge los resultados al final con sapply o similar. Se puede encontrar un ejemplo here. Esto no incurrirá en las penalidades de hacer crecer un objeto.

5

Bien, estoy muy sorprendido de que nadie mencionó la conversión a una matriz sin embargo ...

Comparando con el dt.colon y dt.set funciones definidas por Ari B. Friedman, la conversión a una matriz tiene el mejor tiempo de ejecución (un poco más rápido que dt.colon). Todas las afectaciones dentro de una matriz se realizan por referencia, por lo que no se realiza una copia de memoria innecesaria en este código.

CÓDIGO:

library(data.table) 
n <- 10^4 
dt <- data.table(x=rep(0,n), y=rep(0,n)) 

use.matrix <- function(dt) { 
    mat = as.matrix(dt) # converting to matrix 
    for(i in 1:n) { 
    mat[i,1] = runif(1) 
    mat[i,2] = rnorm(1) 
    } 
    return(as.data.frame(mat)) # converting back to a data.frame 
} 


dt.colon <- function(dt) { # same as Ari's function 
    for(i in 1:n) { 
    dt[i,x := runif(1)] 
    dt[i,y := rnorm(1)] 
    } 
} 

dt.set <- function(dt) { # same as Ari's function 
    for(i in 1:n) { 
    set(dt,i,1L, runif(1)) 
    set(dt,i,2L, rnorm(1)) 
    } 
} 

library(microbenchmark) 
microbenchmark(dt.colon(dt), dt.set(dt), use.matrix(dt),times=10) 

RESULTADO:

Unit: milliseconds 
      expr  min   lq  median   uq  max neval 
    dt.colon(dt) 7107.68494 7193.54792 7262.76720 7277.24841 7472.41726 10 
    dt.set(dt) 93.25954 94.10291 95.07181 97.09725 99.18583 10 
use.matrix(dt) 48.15595 51.71100 52.39375 54.59252 55.04192 10 

Pros de la utilización de una matriz:

  • este es el método más rápido hasta ahora
  • que no tiene que aprender/usar objetos data.table

Con de la utilización de una matriz:

  • sólo se puede manejar un tipo de datos en una matriz (en particular, si tuviera tipos mixtos en las columnas de su hoja.de.datos, entonces todos serán convertidos a carácter por la línea: estera = as.matrix (dt) # convertir a la matriz)
Cuestiones relacionadas