Supponi di non conoscere in anticipo la dimensione di data.frame. Può benissimo essere poche righe o pochi milioni. Devi avere una sorta di contenitore, che cresce dinamicamente. Prendendo in considerazione la mia esperienza e tutte le relative risposte in SO, vengo con 4 soluzioni distinte:
rbindlist
al data.frame
Usa data.table
il set
funzionamento veloce di e abbinalo al raddoppio manuale del tavolo quando necessario.
Usa RSQLite
e aggiungi alla tabella tenuta in memoria.
data.frame
La capacità di crescere e utilizzare un ambiente personalizzato (che ha semantica di riferimento) per memorizzare data.frame in modo che non venga copiato al ritorno.
Ecco un test di tutti i metodi per un numero piccolo e grande di righe aggiunte. Ogni metodo ha 3 funzioni ad esso associate:
create(first_element)
che restituisce l'oggetto di supporto appropriato con first_element
put in.
append(object, element)
che aggiunge il element
alla fine della tabella (rappresentato da object
).
access(object)
ottiene il data.frame
con tutti gli elementi inseriti.
rbindlist
al data.frame
Questo è abbastanza facile e diretto:
create.1<-function(elems)
{
return(as.data.table(elems))
}
append.1<-function(dt, elems)
{
return(rbindlist(list(dt, elems),use.names = TRUE))
}
access.1<-function(dt)
{
return(dt)
}
data.table::set
+ raddoppiando manualmente il tavolo quando necessario.
Memorizzerò la lunghezza reale della tabella in un rowcount
attributo.
create.2<-function(elems)
{
return(as.data.table(elems))
}
append.2<-function(dt, elems)
{
n<-attr(dt, 'rowcount')
if (is.null(n))
n<-nrow(dt)
if (n==nrow(dt))
{
tmp<-elems[1]
tmp[[1]]<-rep(NA,n)
dt<-rbindlist(list(dt, tmp), fill=TRUE, use.names=TRUE)
setattr(dt,'rowcount', n)
}
pos<-as.integer(match(names(elems), colnames(dt)))
for (j in seq_along(pos))
{
set(dt, i=as.integer(n+1), pos[[j]], elems[[j]])
}
setattr(dt,'rowcount',n+1)
return(dt)
}
access.2<-function(elems)
{
n<-attr(elems, 'rowcount')
return(as.data.table(elems[1:n,]))
}
SQL dovrebbe essere ottimizzato per l'inserimento rapido dei record, quindi inizialmente avevo grandi speranze per una RSQLite
soluzione
Questo è fondamentalmente copia e incolla della risposta di Karsten W. su un thread simile.
create.3<-function(elems)
{
con <- RSQLite::dbConnect(RSQLite::SQLite(), ":memory:")
RSQLite::dbWriteTable(con, 't', as.data.frame(elems))
return(con)
}
append.3<-function(con, elems)
{
RSQLite::dbWriteTable(con, 't', as.data.frame(elems), append=TRUE)
return(con)
}
access.3<-function(con)
{
return(RSQLite::dbReadTable(con, "t", row.names=NULL))
}
data.frame
il proprio ambiente di aggiunta di righe + personalizzato.
create.4<-function(elems)
{
env<-new.env()
env$dt<-as.data.frame(elems)
return(env)
}
append.4<-function(env, elems)
{
env$dt[nrow(env$dt)+1,]<-elems
return(env)
}
access.4<-function(env)
{
return(env$dt)
}
La suite di test:
Per comodità userò una funzione di test per coprirli tutti con chiamate indirette. (Ho controllato: usare do.call
invece di chiamare direttamente le funzioni non rende il codice misurabile più a lungo).
test<-function(id, n=1000)
{
n<-n-1
el<-list(a=1,b=2,c=3,d=4)
o<-do.call(paste0('create.',id),list(el))
s<-paste0('append.',id)
for (i in 1:n)
{
o<-do.call(s,list(o,el))
}
return(do.call(paste0('access.', id), list(o)))
}
Vediamo l'andamento per n = 10 inserimenti.
Ho anche aggiunto una funzione "placebo" (con suffisso 0
) che non esegue nulla, solo per misurare il sovraccarico della configurazione del test.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
Per le righe 1E5 (misurazioni effettuate su CPU Intel (R) Core (TM) i7-4710HQ a 2,50 GHz):
nr function time
4 data.frame 228.251
3 sqlite 133.716
2 data.table 3.059
1 rbindlist 169.998
0 placebo 0.202
Sembra che la soluzione basata su SQLite, sebbene riacquisti un po 'di velocità su dati di grandi dimensioni, non è neanche lontanamente vicina a data.table + crescita esponenziale manuale. La differenza è di quasi due ordini di grandezza!
Sommario
Se sai che aggiungerai un numero di righe piuttosto piccolo (n <= 100), vai avanti e utilizza la soluzione più semplice possibile: assegna semplicemente le righe a data.frame usando la notazione delle parentesi e ignora il fatto che data.frame è non pre-popolato.
Per tutto il resto usa data.table::set
e fai crescere il data.table in modo esponenziale (ad esempio usando il mio codice).