2012-07-14 6 views
36

Secondo Creating an R dataframe row-by-row, non è ideale per aggiungere uno data.frame utilizzando rbind, poiché ogni volta crea una copia dell'intero data.frame. Come faccio ad accumulare dati in R risultante in un data.frame senza incorrere in questa penalità? Il formato intermedio non deve essere un data.frame.Crescere un data.frame in modo efficiente dalla memoria

+0

a cura di fare quello chiaro sono abbastanza sicuro che volevi dire. Si prega di annullare se ho incasinato. –

+0

Se sei ancora interessato, [ecco un altro punto di riferimento di un altro insieme di modi diversi per far crescere data.frame] (http://stackoverflow.com/questions/20689650/how-to-append-rows-to-an-r -data-frame/38052208 # 38052208) quando non si conoscono le dimensioni in anticipo. –

risposta

38

primo approccio

Ho provato accedere a ciascun elemento di un data.frame preallocato:

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) 
} 

Ma tracemem impazzisce (ad esempio la data.frame viene copiato un nuovo indirizzo ogni volta).

approccio alternativo (non funziona nemmeno)

Un approccio (non sono sicuro che sia più veloce in quanto non ho ancora benchmark) è quello di creare un elenco di data.frames, poi stack tutti insieme:

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) 

Sfortunatamente nel creare l'elenco penso che sarà difficile pre-allocare. Ad esempio:

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

In altre parole, la sostituzione di un elemento dell'elenco provoca la copia dell'elenco. Presumo l'intera lista, ma è possibile che sia solo quell'elemento della lista. Non ho familiarità con i dettagli della gestione della memoria di R.

Probabilmente il miglior approccio

Come molte velocità o processi di memoria limitata questi giorni, l'approccio migliore potrebbe essere quella di utilizzare data.table invece di un data.frame. Poiché data.table ha la := assegnazione da operatore di riferimento, è possibile aggiornare senza ri-copia:

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 

Ma, come sottolinea @MatthewDowle, set() è il modo appropriato per farlo all'interno di un ciclo. In questo modo lo rende ancora più veloce:

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) 

(risultati riportati di seguito)

Benchmarking

Con la corsa ciclo 10.000 volte, tabella dei dati è quasi un ordine completo di grandezza più veloce:

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

e il confronto di := 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 

Nota che n qui è 10^6 non 10^5 come nei benchmark tracciati sopra.Quindi c'è un ordine di grandezza più lavoro, e il risultato è misurato in millisecondi, non secondi. Impressionante davvero.

+3

Per quanto ne so, il tuo ultimo esempio non fa crescere data.table. Basta sovrascrivere la prima riga 1.000 volte. – Andrie

+0

@Andrie. Ops. Risolto il problema. Grazie per segnalarlo. –

+3

Questo è buono ma hai visto l'esempio di velocità nella parte inferiore di '?": = "' Confrontando ': =' all'interno di un ciclo su 'set()' all'interno di un ciclo. ': =' ha un overhead (ad es.controllando l'esistenza e il tipo di argomenti passati a '[.data.table'), che è il motivo per cui' set() 'è previsto per l'uso all'interno di loop. –

5

Mi piace RSQLite per questo: dbWriteTable(...,append=TRUE) dichiarazioni durante la raccolta e la dichiarazione dbReadTable alla fine.

Se i dati sono abbastanza piccoli, è possibile utilizzare il file ": memory:", se è grande, il disco rigido.

Naturalmente, non può competere in termini di velocità:

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 

Ma potrebbe essere meglio se le data.frame s hanno più di una riga. E non è necessario conoscere il numero di righe in anticipo.

+0

L'idea è interessante, ma non è molto efficiente (http://stackoverflow.com/questions/20689650/how-to-append-rows-to-an-r-data-frame/38052208#38052208). L'ho messo su un test su un altro thread. –

5

Si potrebbe anche avere un oggetto elenco vuoto in cui gli elementi sono riempiti con i dataframes; poi raccogli i risultati alla fine con un po 'o simili. Un esempio può essere trovato here. Questo non incorre in sanzioni per la crescita di un oggetto.

5

Beh, sono molto sorpreso che nessuno ha menzionato la conversione ad ancora una matrice ...

Paragonando al dt.colon e dt.set funzioni definite dal Ari B. Friedman, la conversione a una matrice ha il miglior tempo di esecuzione (leggermente più veloce di dt.colon). Tutte le influenze all'interno di una matrice vengono eseguite per riferimento, quindi non viene eseguita alcuna copia di memoria non necessaria in questo codice.

CODICE:

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) 

RISULTATO:

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 

A favore di utilizzando una matrice:

  • questo è il metodo più veloce finora
  • non si dispone di impara/usa gli oggetti data.table

Con di utilizzare una matrice:

  • si può gestire un solo tipo di dati in una matrice (in particolare, se tu avessi tipi misti nelle colonne del vostro data.frame, allora saranno tutti convertiti a carattere dalla linea: mat = as.matrix (dt) # conversione in matrice)
Problemi correlati