Warum ist rbindlist "besser" als rbind?

135

Ich gehe die Dokumentation von data.tableund habe auch einige der Gespräche hier auf SO bemerkt rbindlist, die besser sein sollen als rbind.

Ich würde gerne wissen, warum es rbindlistbesser ist als rbindund in welchen Szenarien sich das rbindlistwirklich auszeichnet rbind.

Gibt es einen Vorteil in Bezug auf die Speichernutzung?

Chinmay Patil
quelle

Antworten:

155

rbindlistist eine optimierte Version von do.call(rbind, list(...)), die dafür bekannt ist, dass sie bei der Verwendung langsam istrbind.data.frame


Wo zeichnet es sich wirklich aus?

Einige Fragen, die zeigen, wo rbindlistGlanz ist

Schnelle vektorisierte Zusammenführung der Liste der Datenrahmen nach Zeilen

Probleme beim Konvertieren einer langen Liste von Datenrahmen (~ 1 Million) in einzelne Datenrahmen mit do.call und ldply

Diese haben Benchmarks, die zeigen, wie schnell es sein kann.


rbind.data.frame ist aus einem bestimmten Grund langsam

rbind.data.frameführt viele Überprüfungen durch und stimmt mit dem Namen überein. (dh rbind.data.frame berücksichtigt die Tatsache, dass Spalten möglicherweise in unterschiedlicher Reihenfolge vorliegen und nach Namen übereinstimmen), rbindlistführt diese Art der Überprüfung nicht durch und wird nach Position verbunden

z.B

do.call(rbind, list(data.frame(a = 1:2, b = 2:3), data.frame(b = 1:2, a = 2:3)))
##    a b
## 1  1 2
## 2  2 3
## 3  2 1
## 4  3 2

rbindlist(list(data.frame(a = 1:5, b = 2:6), data.frame(b = 1:5, a = 2:6)))
##     a b
##  1: 1 2
##  2: 2 3
##  3: 1 2
##  4: 2 3

Einige andere Einschränkungen von rbindlist

Früher hatte esfactors aufgrund eines Fehlers, der inzwischen behoben wurde , Schwierigkeiten, damit umzugehen :

rbindlist zwei data.tables, wobei eine den Faktor und die andere den Zeichentyp für eine Spalte hat ( Bug # 2650 )

Es gibt Probleme mit doppelten Spaltennamen

siehe Warnmeldung: in rbindlist (allargs): NAs durch Zwang eingeführt: möglicher Fehler in data.table? ( Bug # 2384 )


rbind.data.frame-Rownamen können frustrierend sein

rbindlistkann mit lists data.framesund umgehen und data.tablesgibt eine data.table ohne Rücknamen zurück

Sie können mit do.call(rbind, list(...)) see in ein Wirrwarr von Rownamen geraten

Wie vermeide ich das Umbenennen von Zeilen, wenn ich rbind in do.call verwende?


Speichereffizienz

In Bezug auf Speicher rbindlistwird in implementiert C, so ist Speicher effizient, es verwendet setattr, um Attribute durch Referenz festzulegen

rbind.data.frameimplementiert in ist R, hat es eine Menge Zuweisung und Verwendungen attr<-(und class<-und rownames<-von denen alle (intern) Kopien des erstellten data.frame erstellen.

mnel
quelle
1
Zu Ihrer Information attr<-, class<-und (glaube ich) rownames<-alle ändern sich an Ort und Stelle.
Hadley
5
@ Hadley Bist du sicher? Versuchen Sie es DF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF)).
Matt Dowle
4
rbind.data.framehat eine spezielle "Hijacking" -Logik - wenn das erste Argument a ist data.table, ruft es .rbind.data.tablestattdessen auf, was ein wenig prüft und dann rbindlistintern aufruft . Wenn Sie also bereits data.tableObjekte zum Binden haben, gibt es wahrscheinlich nur einen geringen Leistungsunterschied zwischen rbindund rbindlist.
Ken Williams
6
mnel, dieser Beitrag muss möglicherweise bearbeitet werden, da er nun rbindlistnach Namen ( use.names=TRUE) übereinstimmen und auch fehlende Spalten ( fill=TRUE) füllen kann . Ich habe dies , dies und diesen Beitrag aktualisiert . Stört es Sie, dieses zu bearbeiten, oder ist es in Ordnung, wenn ich es mache? So oder so ist gut für mich.
Arun
1
dplyr::rbind_listist auch ziemlich ähnlich
Hadley
48

Durch v1.9.2, rbindlisthatte ziemlich viel entwickelt, Implementierung viele Funktionen , einschließlich:

  • Auswählen der höchsten SEXPTYPESpalte während der Bindung - implementiert beim v1.9.2Schließen von FR # 2456 und Bug # 4981 .
  • Ordnungsgemäßer Umgang mit factorSpalten - zuerst beim v1.8.10Schließen von Bug # 2650 implementiert und erweitert, um auch geordnete Faktoren sorgfältig zu binden v1.9.2, wobei FR # 4856 und Bug # 5019 geschlossen werden .

Darüber hinaus in v1.9.2, rbind.data.tablegewinnt auch ein fillArgument, dass durch das Ausfüllen fehlende Spalt zu binden kann, implementiert in R.

Jetzt v1.9.3gibt es noch weitere Verbesserungen an diesen vorhandenen Funktionen:

  • rbindlisterhält ein Argument use.names, das standardmäßig der FALSEAbwärtskompatibilität dient.
  • rbindlisterhält auch ein Argument fill, das standardmäßig auch der FALSEAbwärtskompatibilität dient.
  • Diese Funktionen sind alle in C implementiert und sorgfältig geschrieben, um beim Hinzufügen von Funktionen keine Kompromisse bei der Geschwindigkeit einzugehen.
  • Da rbindlistjetzt nach Namen übereinstimmen und fehlende Spalten füllen können, rbind.data.tableruft rbindlistjetzt einfach auf . Der einzige Unterschied besteht darin, dass aus Gründen der Abwärtskompatibilität use.names=TRUEstandardmäßig rbind.data.table.

rbind.data.frameverlangsamt sich ziemlich stark, hauptsächlich aufgrund von Kopien (auf die @mnel ebenfalls hinweist), die vermieden werden könnten (indem man zu C wechselt). Ich denke, das ist nicht der einzige Grund. Die Implementierung zum Einchecken / Abgleichen von Spaltennamen in rbind.data.framekann auch langsamer werden, wenn viele Spalten pro data.frame vorhanden sind und viele solcher data.frames gebunden werden müssen (wie im folgenden Benchmark gezeigt).

Das rbindlistFehlen (ed) bestimmter Funktionen (wie das Überprüfen von Faktorstufen oder übereinstimmenden Namen) hat jedoch ein sehr geringes (oder gar kein) Gewicht dafür, dass es schneller als ist rbind.data.frame. Dies liegt daran, dass sie sorgfältig in C implementiert und auf Geschwindigkeit und Speicher optimiert wurden.

Hier ist ein Maßstab, unterstreicht die effiziente Bindung , während sie durch die Spaltennamen passend als auch unter Verwendung von rbindlist‚s - use.namesFunktion aus v1.9.3. Der Datensatz besteht aus 10000 Datenrahmen mit einer Größe von jeweils 10 * 500.

NB: Dieser Benchmark wurde aktualisiert und enthält nun einen Vergleich mit dplyr'sbind_rows

library(data.table) # 1.11.5, 2018-06-02 00:09:06 UTC
library(dplyr) # 0.7.5.9000, 2018-06-12 01:41:40 UTC
set.seed(1L)
names = paste0("V", 1:500)
cols = 500L
foo <- function() {
    data = as.data.frame(setDT(lapply(1:cols, function(x) sample(10))))
    setnames(data, sample(names))
}
n = 10e3L
ll = vector("list", n)
for (i in 1:n) {
    .Call("Csetlistelt", ll, i, foo())
}

system.time(ans1 <- rbindlist(ll))
#  user  system elapsed 
# 1.226   0.070   1.296 

system.time(ans2 <- rbindlist(ll, use.names=TRUE))
#  user  system elapsed 
# 2.635   0.129   2.772 

system.time(ans3 <- do.call("rbind", ll))
#   user  system elapsed 
# 36.932   1.628  38.594 

system.time(ans4 <- bind_rows(ll))
#   user  system elapsed 
# 48.754   0.384  49.224 

identical(ans2, setDT(ans3)) 
# [1] TRUE
identical(ans2, setDT(ans4))
# [1] TRUE

Das Binden von Spalten als solche ohne Überprüfung auf Namen dauerte nur 1,3, während das Überprüfen auf Spaltennamen und das entsprechende Binden nur 1,5 Sekunden länger dauerte. Im Vergleich zur Basislösung ist dies 14x schneller und 18x schneller als dplyrdie Version.

Arun
quelle