Ich habe mich in StackOverflow umgesehen, kann jedoch keine spezifische Lösung für mein Problem finden, bei der Zeilen an einen R-Datenrahmen angehängt werden.
Ich initialisiere einen leeren 2-Spalten-Datenrahmen wie folgt.
df = data.frame(x = numeric(), y = character())
Dann ist es mein Ziel, eine Liste von Werten zu durchlaufen und in jeder Iteration einen Wert an das Ende der Liste anzuhängen. Ich habe mit dem folgenden Code begonnen.
for (i in 1:10) {
df$x = rbind(df$x, i)
df$y = rbind(df$y, toString(i))
}
Ich habe auch erfolglos die Funktionen c
, append
und merge
ausprobiert. Bitte lassen Sie mich wissen, wenn Sie Vorschläge haben.
Da ich nicht weiß, was Sie versuchen, teile ich Ihnen noch einen Vorschlag: Ordnen Sie Vektoren für jede Spalte den gewünschten Typ zu, fügen Sie Werte in diese Vektoren ein und erstellen Sie am Ende Ihren data.frame
.
Fahren Sie mit Julians f3
(Einem vorab zugewiesenen data.frame
) Als bisher schnellste Option fort, definiert als:
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(n), y = character(n), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
Hier ist ein ähnlicher Ansatz, bei dem jedoch data.frame
Als letzter Schritt erstellt wird.
# Use preallocated vectors
f4 <- function(n) {
x <- numeric(n)
y <- character(n)
for (i in 1:n) {
x[i] <- i
y[i] <- i
}
data.frame(x, y, stringsAsFactors=FALSE)
}
microbenchmark
aus dem "microbenchmark" -Paket gibt uns einen umfassenderen Einblick als system.time
:
library(microbenchmark)
microbenchmark(f1(1000), f3(1000), f4(1000), times = 5)
# Unit: milliseconds
# expr min lq median uq max neval
# f1(1000) 1024.539618 1029.693877 1045.972666 1055.25931 1112.769176 5
# f3(1000) 149.417636 150.529011 150.827393 151.02230 160.637845 5
# f4(1000) 7.872647 7.892395 7.901151 7.95077 8.049581 5
f1()
(der Ansatz unten) ist unglaublich ineffizient, da es häufig data.frame
Aufruft und das Wachsen von Objekten in R. auf diese Weise im Allgemeinen langsam ist. f3()
wird aufgrund der Vorbelegung erheblich verbessert , aber die Struktur data.frame
selbst könnte hier zum Engpass gehören. f4()
versucht, diesen Engpass zu umgehen, ohne den gewünschten Ansatz zu beeinträchtigen.
Das ist wirklich keine gute Idee, aber wenn Sie es so machen wollten, können Sie es versuchen:
for (i in 1:10) {
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
Beachten Sie, dass es in Ihrem Code ein weiteres Problem gibt:
stringsAsFactors
verwenden, wenn die Zeichen nicht in Faktoren umgewandelt werden sollen. Verwenden Sie: df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
Vergleichen wir die drei vorgeschlagenen Lösungen miteinander:
# use rbind
f1 <- function(n){
df <- data.frame(x = numeric(), y = character())
for(i in 1:n){
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
df
}
# use list
f2 <- function(n){
df <- data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
for(i in 1:n){
df[i,] <- list(i, toString(i))
}
df
}
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(1000), y = character(1000), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
system.time(f1(1000))
# user system elapsed
# 1.33 0.00 1.32
system.time(f2(1000))
# user system elapsed
# 0.19 0.00 0.19
system.time(f3(1000))
# user system elapsed
# 0.14 0.00 0.14
Die beste Lösung besteht darin, Speicherplatz vorab zuzuweisen (wie in R vorgesehen). Die nächstbeste Lösung ist die Verwendung von list
, und die schlechteste Lösung (zumindest basierend auf diesen Timing-Ergebnissen) scheint rbind
zu sein.
Angenommen, Sie kennen die Größe des data.frame einfach nicht im Voraus. Es kann durchaus ein paar Zeilen oder ein paar Millionen sein. Sie brauchen eine Art Container, der dynamisch wächst. Unter Berücksichtigung meiner Erfahrung und aller damit zusammenhängenden Antworten in SO Ich komme mit 4 verschiedenen Lösungen:
rbindlist
zum data.frame
Verwenden Sie die schnelle set
-Operation von data.table
Und koppeln Sie sie, indem Sie die Tabelle bei Bedarf manuell verdoppeln.
Verwenden Sie RSQLite
und hängen Sie es an die im Speicher befindliche Tabelle an.
Die eigene Fähigkeit von data.frame
, 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 eine kleine und eine große Anzahl von angehängten Zeilen. Jeder Methode sind drei Funktionen zugeordnet:
create(first_element)
, das das entsprechende Sicherungsobjekt mit first_element
zurückgibt.
append(object, element)
, das das element
an das Ende der Tabelle anfügt (dargestellt durch object
).
access(object)
erhält den data.frame
mit allen eingefügten Elementen.
rbindlist
zum data.frameDas 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
+ Manuelles Verdoppeln der Tabelle bei Bedarf.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,]))
}
RSQLite
LösungDies ist im Grunde Copy & Paste von Karsten W. Antwort auf ä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
S eigene + benutzerdefinierte Umgebung zum Anhängen von Zeilen.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)
}
Der Einfachheit halber werde ich eine Testfunktion verwenden, um sie alle mit indirekten Aufrufen abzudecken. (Ich habe überprüft, dass die Verwendung von do.call
Anstelle des direkten Aufrufs der Funktionen den Code nicht länger messbar macht.).
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)))
}
Lassen Sie uns die Leistung für n = 10 Einfügungen sehen.
Ich habe auch 'Placebo'-Funktionen hinzugefügt (mit dem Suffix 0
), Die nichts bewirken - 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 etwas 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!
Wenn Sie wissen, dass Sie eine relativ kleine Anzahl von Zeilen anhängen werden (n <= 100), gehen Sie vor und verwenden Sie die einfachste Lösung: Ordnen Sie die Zeilen dem data.frame in Klammern zu und ignorieren Sie die Tatsache, dass data.frame ist nicht vorbelegt.
Verwenden Sie für alles andere data.table::set
Und vergrößern Sie die data.table exponentiell (z. B. mit meinem Code).
Da die Frage bereits datiert ist (6 Jahre), fehlt den Antworten eine Lösung mit neueren Paketen tidyr und purrr. Für Leute, die mit diesen Paketen arbeiten, möchte ich eine Lösung zu den vorherigen Antworten hinzufügen - alles sehr interessant, besonders.
Der größte Vorteil von purrr und tidyr ist meiner Meinung nach eine bessere Lesbarkeit. purrr ersetzt lapply durch die flexiblere map () - Familie, tidyr bietet die superintuitive Methode add_row - macht einfach was es sagt :)
map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })
Diese Lösung ist kurz und intuitiv zu lesen und relativ schnell:
system.time(
map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
user system elapsed
0.756 0.006 0.766
Es skaliert fast linear, sodass für 1e5-Zeilen die Leistung wie folgt ist:
system.time(
map_df(1:100000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
user system elapsed
76.035 0.259 76.489
womit er gleich nach data.table (wenn Sie das Placebo ignorieren) im Benchmark von @Adam Ryczkowski an zweiter Stelle steht:
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
Nehmen wir einen Vektorpunkt mit Zahlen von 1 bis 5
point = c(1,2,3,4,5)
wenn wir irgendwo innerhalb des Vektors eine Nummer 6 anhängen wollen, kann der folgende Befehl nützlich sein
i) Vektoren
new_var = append(point, 6 ,after = length(point))
ii) Spalten einer Tabelle
new_var = append(point, 6 ,after = length(mtcars$mpg))
Der Befehl append
hat drei Argumente:
einfach...!! Entschuldigung im Falle von ...!
Eine allgemeinere Lösung für könnte die folgende sein.
extendDf <- function (df, n) {
withFactors <- sum(sapply (df, function(X) (is.factor(X)) )) > 0
nr <- nrow (df)
colNames <- names(df)
for (c in 1:length(colNames)) {
if (is.factor(df[,c])) {
col <- vector (mode='character', length = nr+n)
col[1:nr] <- as.character(df[,c])
col[(nr+1):(n+nr)]<- rep(col[1], n) # to avoid extra levels
col <- as.factor(col)
} else {
col <- vector (mode=mode(df[1,c]), length = nr+n)
class(col) <- class (df[1,c])
col[1:nr] <- df[,c]
}
if (c==1) {
newDf <- data.frame (col ,stringsAsFactors=withFactors)
} else {
newDf[,c] <- col
}
}
names(newDf) <- colNames
newDf
}
Die Funktion extendDf () erweitert einen Datenrahmen um n Zeilen.
Als Beispiel:
aDf <- data.frame (l=TRUE, i=1L, n=1, c='a', t=Sys.time(), stringsAsFactors = TRUE)
extendDf (aDf, 2)
# l i n c t
# 1 TRUE 1 1 a 2016-07-06 17:12:30
# 2 FALSE 0 0 a 1970-01-01 01:00:00
# 3 FALSE 0 0 a 1970-01-01 01:00:00
system.time (eDf <- extendDf (aDf, 100000))
# user system elapsed
# 0.009 0.002 0.010
system.time (eDf <- extendDf (eDf, 100000))
# user system elapsed
# 0.068 0.002 0.070