Angenommen, Sie kennen die Größe des data.frame einfach nicht im Voraus. Es können durchaus ein paar Zeilen oder ein paar Millionen sein. Sie benötigen eine Art Container, der dynamisch wächst. Unter Berücksichtigung meiner Erfahrung und aller damit verbundenen Antworten in SO komme ich mit 4 verschiedenen Lösungen:
rbindlist
zum data.frame
Verwenden Sie data.table
die schnelle set
Bedienung und koppeln Sie sie bei Bedarf manuell mit dem Verdoppeln des Tisches.
Verwenden Sie RSQLite
die im Speicher befindliche Tabelle und hängen Sie sie an.
data.frame
Die eigene Fähigkeit zu wachsen und eine benutzerdefinierte Umgebung (mit Referenzsemantik) zum Speichern des data.frame zu verwenden, damit er bei der Rückgabe nicht kopiert wird.
Hier finden Sie einen Test aller Methoden für kleine und große Anzahl angehängter Zeilen. Jeder Methode sind 3 Funktionen zugeordnet:
create(first_element)
das gibt das entsprechende Hintergrundobjekt mit first_element
put in zurück.
append(object, element)
das hängt das element
an das Ende der Tabelle (dargestellt durch object
).
access(object)
bekommt das data.frame
mit allen eingefügten Elementen.
rbindlist
zum data.frame
Das ist ganz einfach und unkompliziert:
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
+ Verdoppeln Sie den Tisch bei Bedarf manuell.
Ich werde die wahre Länge der Tabelle in einem rowcount
Attribut speichern .
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 sollte für das schnelle Einfügen von Datensätzen optimiert werden, daher hatte ich anfangs große Hoffnungen RSQLite
Lösung
Dies ist im Grunde ein Kopieren und Einfügen von Karsten W. Antwort auf einen ähnlichen Thread.
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
eigene zeilenanhängende + benutzerdefinierte Umgebung.
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)
}
Die Testsuite:
Der Einfachheit halber werde ich eine Testfunktion verwenden, um sie alle mit indirekten Aufrufen abzudecken. (Ich habe überprüft: do.call
Wenn Sie die Funktionen nicht direkt aufrufen, wird der Code nicht länger messbar.)
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)))
}
Sehen wir uns die Leistung für n = 10 Einfügungen an.
Ich habe auch eine 'Placebo'-Funktion (mit Suffix 0
) hinzugefügt , die nichts ausführt - nur um den Overhead des Testaufbaus zu messen.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
Für 1E5-Zeilen (Messungen mit Intel (R) Core (TM) i7-4710HQ-CPU bei 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
Es sieht so aus, als ob die SQLite-basierte Lösung, obwohl sie bei großen Datenmengen wieder an Geschwindigkeit gewinnt, bei weitem nicht in der Nähe von data.table + manuellem exponentiellem Wachstum liegt. Der Unterschied beträgt fast zwei Größenordnungen!
Zusammenfassung
Wenn Sie wissen, dass Sie eine relativ kleine Anzahl von Zeilen anhängen (n <= 100), verwenden Sie die einfachste mögliche Lösung: Weisen Sie die Zeilen einfach dem data.frame in Klammernotation zu und ignorieren Sie die Tatsache, dass es sich um den data.frame handelt nicht vorbestellt.
Für alles andere verwenden data.table::set
und erweitern Sie die data.table exponentiell (z. B. mit meinem Code).