webentwicklung-frage-antwort-db.com.de

So hängen Sie Zeilen an einen R-Datenrahmen an

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.

107
Gyan Veda

Aktualisieren

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.


Ursprüngliche Antwort

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:

  • Sie sollten stringsAsFactors verwenden, wenn die Zeichen nicht in Faktoren umgewandelt werden sollen. Verwenden Sie: df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
103

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.

31
Julián Urbano

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:

  1. rbindlist zum data.frame

  2. Verwenden Sie die schnelle set -Operation von data.table Und koppeln Sie sie, indem Sie die Tabelle bei Bedarf manuell verdoppeln.

  3. Verwenden Sie RSQLite und hängen Sie es an die im Speicher befindliche Tabelle an.

  4. 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.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 + 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,]))
}

SQL sollte für schnelles Einfügen von Datensätzen optimiert sein, daher hatte ich anfangs große Hoffnungen auf eine RSQLite Lösung

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

Die Testsuite:

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)

Timings for adding n=10 rows

Timings for n=100 rowsTimings for n=1000 rows

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!

Zusammenfassung

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

12
Adam Ryczkowski

Update mit purrr, tidyr & dplyr

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
3
Agile Bean

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:

  1. der zu modifizierende Vektor/die zu modifizierende Spalte.
  2. wert, der in den modifizierten Vektor einbezogen werden soll.
  3. ein Index, nach dem die Werte angehängt werden sollen.

einfach...!! Entschuldigung im Falle von ...!

2

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
1
Pisca46