Zeile für Zeile einen R-Datenrahmen erstellen

107

Ich möchte einen Datenrahmen zeilenweise in R erstellen. Ich habe einige Suchvorgänge durchgeführt, und alles, was ich mir ausgedacht habe, ist der Vorschlag, eine leere Liste zu erstellen, einen Listenindexskalar beizubehalten und dann jedes Mal zur Liste hinzuzufügen einen einzeiligen Datenrahmen und erweitern Sie den Listenindex um eins. Schließlich do.call(rbind,)auf der Liste.

Während dies funktioniert, scheint es sehr umständlich. Gibt es nicht einen einfacheren Weg, um dasselbe Ziel zu erreichen?

Offensichtlich beziehe ich mich auf Fälle, in denen ich eine applyFunktion nicht verwenden kann und den Datenrahmen explizit zeilenweise erstellen muss. Gibt es zumindest eine Möglichkeit, bis pushzum Ende einer Liste zu gelangen, anstatt den zuletzt verwendeten Index explizit zu verfolgen?

David B.
quelle
1
Sie können append()[was wahrscheinlich als Einfügen bezeichnet werden sollte] verwenden oder c()Elemente am Ende einer Liste hinzufügen, was Ihnen hier jedoch nicht weiterhilft.
Hatmatrix
Es gibt nicht viele Funktionen in R dass die Rückkehr von Datenrahmen , wenn Sie sie zurückkehren [reihenweisen] aus lapply(), Map()und so weiter, aber Sie können auch einen Blick zu nehmen aggregate(), dapply() {heR.Misc}und cast() {reshape}zu sehen , ob Ihre Aufgaben nicht durch diese behandelt werden können Funktionen (diese geben alle Datenrahmen zurück).
Hatmatrix

Antworten:

96

Sie können sie zeilenweise erweitern, indem Sie sie anhängen oder verwenden rbind().

Das heißt nicht, dass du es solltest. Dynamisch wachsende Strukturen sind eine der am wenigsten effizienten Methoden zum Codieren in R.

Wenn Sie können, ordnen Sie Ihren gesamten data.frame im Voraus zu:

N <- 1e4  # total number of rows to preallocate--possibly an overestimate

DF <- data.frame(num=rep(NA, N), txt=rep("", N),  # as many cols as you need
                 stringsAsFactors=FALSE)          # you don't know levels yet

und fügen Sie dann während Ihrer Operationen jeweils eine Zeile ein

DF[i, ] <- list(1.4, "foo")

Das sollte für beliebige data.frame funktionieren und viel effizienter sein. Wenn Sie N überschritten haben, können Sie am Ende immer leere Zeilen verkleinern.

Dirk Eddelbuettel
quelle
6
Wollten Sie nicht N anstelle von 10 setzen und (1.4, "foo") anstelle von c (1.4, "foo") auflisten, um die 1.4 nicht in den Zeichenmodus zu zwingen?
Hatmatrix
Ja, ich wollte N bei der Erstellung von data.frame verwenden. Auch sehr guter Fang bezüglich des Zwangs zum Plaudern - das hatte ich verpasst.
Dirk Eddelbuettel
1
Es wäre besser, die Antwort zu bearbeiten, als sie in den Kommentaren zu belassen. Ich war verwirrt, als ich versuchte, diese Antwort zu finden.
Benutzer
4
data.tablescheint sogar schneller zu sein als die Vorabzuweisung mit data.frames. Testen hier: stackoverflow.com/a/11486400/636656
Ari B. Friedman
Trifft dies in R 3.1 noch zu, wo dies schneller sein sollte?
userJT
49

Man kann Zeilen hinzufügen zu NULL:

df<-NULL;
while(...){
  #Some code that generates new row
  rbind(df,row)->df
}

zum Beispiel

df<-NULL
for(e in 1:10) rbind(df,data.frame(x=e,square=e^2,even=factor(e%%2==0)))->df
print(df)
mbq
quelle
3
es gibt eine Matrix aus, keinen Datenrahmen
Olga
1
@Olga Nur wenn Sie Zeilen von Elementen gleichen Typs binden - übrigens ist es in diesem Fall besser, zu sapplytransponieren (oder zu vektorisieren).
mbq
1
@mbq Genau das, was ich mache. Ich habe auch festgestellt, dass beim Initialisieren mit df <-data.frame () ein Datenrahmen ausgegeben wird.
Olga
9

Dies ist ein dummes Beispiel für die Verwendung do.call(rbind,)auf der Ausgabe von Map()[ähnlich wie lapply()]

> DF <- do.call(rbind,Map(function(x) data.frame(a=x,b=x+1),x=1:3))
> DF
  x y
1 1 2
2 2 3
3 3 4
> class(DF)
[1] "data.frame"

Ich benutze dieses Konstrukt ziemlich oft.

hatmatrix
quelle
8

Der Grund, warum ich Rcpp so sehr mag, ist, dass ich nicht immer verstehe, wie R Core denkt, und mit Rcpp muss ich das meistens nicht.

Jeder Wert philosophisch gesprochen, du ist in einem Zustand der Sünde in Bezug auf das funktionale Paradigma, das, um sicherzustellen versucht , erscheint unabhängig von jedem anderen Wert; Das Ändern eines Werts sollte niemals zu einer sichtbaren Änderung eines anderen Werts führen, wie dies bei Zeigern der Fall ist, die die Darstellung in C teilen.

Die Probleme entstehen, wenn die funktionale Programmierung dem kleinen Fahrzeug signalisiert, sich aus dem Weg zu räumen, und das kleine Fahrzeug antwortet: "Ich bin ein Leuchtturm". Wenn Sie eine lange Reihe kleiner Änderungen an einem großen Objekt vornehmen, das Sie in der Zwischenzeit bearbeiten möchten, befinden Sie sich auf dem Gebiet des Leuchtturms.

In der C ++ STL push_back()ist eine Lebensweise. Es versucht nicht, funktionsfähig zu sein, aber es versucht, gängige Programmiersprachen effizient zu berücksichtigen .

Mit etwas Klugheit hinter den Kulissen kann man manchmal arrangieren, einen Fuß in jeder Welt zu haben. Snapshot-basierte Dateisysteme sind ein gutes Beispiel (das sich aus Konzepten wie Union Mounts entwickelt hat, die auch beide Seiten bedienen).

Wenn R Core dies tun wollte, könnte der zugrunde liegende Vektorspeicher wie ein Union Mount funktionieren. Ein Verweis auf den Vektorspeicher ist möglicherweise für Indizes gültig 1:N, während ein anderer Verweis auf denselben Speicher für Indizes gültig ist 1:(N+1). Es könnte reservierten Speicher geben, auf den noch nichts anderes verweist, als es für einen schnellen Zweck geeignet ist push_back(). Sie verstoßen nicht gegen das Funktionskonzept, wenn Sie außerhalb des Bereichs anhängen, den eine vorhandene Referenz für gültig hält.

Wenn Sie Zeilen schrittweise anhängen, geht Ihnen der reservierte Speicherplatz aus. Sie müssen von allem neue Kopien erstellen, wobei der Speicher mit einem gewissen Inkrement multipliziert wird. Die von mir verwendeten STL-Implementierungen multiplizieren den Speicher bei Erweiterung der Zuordnung in der Regel mit 2. Ich dachte, ich hätte in R Internals gelesen, dass es eine Speicherstruktur gibt, in der der Speicher um 20% erhöht wird. In beiden Fällen erfolgen Wachstumsoperationen mit logarithmischer Häufigkeit im Verhältnis zur Gesamtzahl der angehängten Elemente. Amortisiert ist dies normalerweise akzeptabel.

Als Tricks hinter den Kulissen gehen, habe ich Schlimmeres gesehen. Jedes Mal, wenn Sie push_back()eine neue Zeile in den Datenrahmen einfügen, muss eine Indexstruktur der obersten Ebene kopiert werden. Die neue Zeile kann an eine gemeinsame Darstellung angehängt werden, ohne dass alte Funktionswerte beeinträchtigt werden. Ich glaube nicht einmal, dass dies den Müllsammler sehr erschweren würde. da ich nicht vorschlage, dass push_front()alle Referenzen Präfixreferenzen auf der Vorderseite des zugewiesenen Vektorspeichers sind.

Allan Stokes
quelle
2

Dirk Eddelbuettels Antwort ist die beste; Hier stelle ich nur fest, dass Sie es vermeiden können, die Datenrahmenabmessungen oder Datentypen nicht vorab anzugeben. Dies ist manchmal nützlich, wenn Sie mehrere Datentypen und viele Spalten haben:

row1<-list("a",1,FALSE) #use 'list', not 'c' or 'cbind'!
row2<-list("b",2,TRUE)  

df<-data.frame(row1,stringsAsFactors = F) #first row
df<-rbind(df,row2) #now this works as you'd expect.
John
quelle
Meinten Sie df<-rbind(df, row2)?
Timothy C. Quinn
1

Ich habe diesen Weg gefunden, um Datenrahmen durch Rohdaten ohne Matrix zu erstellen.

Mit automatischem Spaltennamen

df<-data.frame(
        t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
        ,row.names = NULL,stringsAsFactors = FALSE
    )

Mit Spaltenname

df<-setNames(
        data.frame(
            t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
            ,row.names = NULL,stringsAsFactors = FALSE
        ), 
        c("col1","col2","col3")
    )
phili_b
quelle
0

Wenn Sie Vektoren haben, die zu Zeilen werden sollen, verketten Sie sie mit c(), übergeben Sie sie zeilenweise an eine Matrix und konvertieren Sie diese Matrix in einen Datenrahmen.

Zum Beispiel Zeilen

dummydata1=c(2002,10,1,12.00,101,426340.0,4411238.0,3598.0,0.92,57.77,4.80,238.29,-9.9)
dummydata2=c(2002,10,2,12.00,101,426340.0,4411238.0,3598.0,-3.02,78.77,-9999.00,-99.0,-9.9)
dummydata3=c(2002,10,8,12.00,101,426340.0,4411238.0,3598.0,-5.02,88.77,-9999.00,-99.0,-9.9)

kann folgendermaßen in einen Datenrahmen konvertiert werden:

dummyset=c(dummydata1,dummydata2,dummydata3)
col.len=length(dummydata1)
dummytable=data.frame(matrix(data=dummyset,ncol=col.len,byrow=TRUE))

Zugegeben, ich sehe zwei Hauptbeschränkungen: (1) Dies funktioniert nur mit Single-Mode-Daten, und (2) Sie müssen Ihre letzten # Spalten kennen, damit dies funktioniert (dh ich gehe davon aus, dass Sie nicht mit a arbeiten zerlumptes Array, dessen größte Zeilenlänge a priori unbekannt ist ).

Diese Lösung scheint einfach zu sein, aber aufgrund meiner Erfahrung mit Typkonvertierungen in R bin ich sicher, dass sie später neue Herausforderungen mit sich bringt. Kann jemand dies kommentieren?

Keegan Smith
quelle
0

Abhängig vom Format Ihrer neuen Zeile können Sie diese verwenden, tibble::add_rowwenn Ihre neue Zeile einfach ist und in "Wertepaaren" angegeben werden kann. Oder Sie könnten dplyr::bind_rows"eine effiziente Implementierung des allgemeinen Musters von do.call (rbind, dfs)" verwenden.

Arthur Yip
quelle