Wenn ich dplyr Syntax auf eine Datentabelle , erhalte ich alle die Geschwindigkeitsvorteile der Datentabelle , während immer noch die Syntax von dplyr verwenden? Mit anderen Worten, verwende ich die Datentabelle falsch, wenn ich sie mit der Dplyr-Syntax abfrage? Oder muss ich eine reine datierbare Syntax verwenden, um die gesamte Leistung zu nutzen?
Vielen Dank im Voraus für jeden Rat. Codebeispiel:
library(data.table)
library(dplyr)
diamondsDT <- data.table(ggplot2::diamonds)
setkey(diamondsDT, cut)
diamondsDT %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()) %>%
arrange(desc(Count))
Ergebnisse:
# cut AvgPrice MedianPrice Count
# 1 Ideal 3457.542 1810.0 21551
# 2 Premium 4584.258 3185.0 13791
# 3 Very Good 3981.760 2648.0 12082
# 4 Good 3928.864 3050.5 4906
Hier ist die datierbare Äquivalenz, die ich mir ausgedacht habe. Ich bin mir nicht sicher, ob es der bewährten DT-Praxis entspricht. Aber ich frage mich, ob der Code hinter den Kulissen wirklich effizienter ist als die Dplyr-Syntax:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
r
data.table
dplyr
Polymerase
quelle
quelle
dplyr
Methoden für Datentabellen, aber die Datentabelle hat auch ihre eigenen vergleichbaren Methodendplyr
derdata.frame
s und entsprechendedata.table
s verwendet werden, finden Sie hier (und die darin enthaltenen Referenzen).Antworten:
Es gibt keine einfache Antwort, da sich die Philosophien dieser beiden Pakete in bestimmten Aspekten unterscheiden. Einige Kompromisse sind also unvermeidlich. Hier sind einige der Bedenken, die Sie möglicherweise ansprechen / berücksichtigen müssen.
Operationen mit
i
(==filter()
undslice()
in dplyr)Angenommen,
DT
mit etwa 10 Spalten. Betrachten Sie diese data.table-Ausdrücke:(1) gibt die Anzahl der Zeilen in der
DT
Spalte where ana > 1
. (2) gibtmean(b)
gruppiert nachc,d
für denselben Ausdruck ini
wie (1) zurück.Häufig verwendete
dplyr
Ausdrücke wären:Datentabellencodes sind eindeutig kürzer. Darüber hinaus sind sie auch speichereffizienter 1 . Warum? Da sowohl in (3) und (4),
filter()
kehrt Zeilen für alle 10 Spalten Zuerst wird , wenn in (3) können wir die Anzahl von Zeilen gerade benötigen, und in (4) haben wir nur Spalten müssenb, c, d
für die aufeinanderfolgenden Operationen. Um dies zu überwinden, müssen wirselect()
apriori Spalten:Beachten Sie, dass wir in (5) und (6) immer noch Spalten unterteilen,
a
die wir nicht benötigen. Aber ich bin mir nicht sicher, wie ich das vermeiden soll. Wenn diefilter()
Funktion ein Argument zum Auswählen der zurückzugebenden Spalten hätte, könnten wir dieses Problem vermeiden, aber dann führt die Funktion nicht nur eine Aufgabe aus (was auch eine dplyr-Entwurfsauswahl ist).Unterzuweisung durch Referenz
In data.table können Sie beispielsweise Folgendes tun:
welche Updates Spalte
a
unter Bezugnahme auf nur jene Zeilen, die die Bedingung erfüllen. Im Moment kopiert dplyr deep die gesamte data.table intern, um eine neue Spalte hinzuzufügen. @BrodieG hat dies bereits in seiner Antwort erwähnt.Die tiefe Kopie kann jedoch durch eine flache Kopie ersetzt werden, wenn FR # 617 implementiert ist. Ebenfalls relevant: dplyr: FR # 614 . Beachten Sie, dass die von Ihnen geänderte Spalte immer kopiert wird (daher etwas langsamer / weniger speichereffizient). Es gibt keine Möglichkeit, Spalten durch Referenz zu aktualisieren.
Andere Funktionen
In data.table können Sie während des Beitritts aggregieren. Dies ist einfacher zu verstehen und speichereffizient, da das Ergebnis der Zwischenverknüpfung niemals erreicht wird. Überprüfen Sie diesen Beitrag für ein Beispiel. Sie können dies (im Moment?) Nicht mit der data.table / data.frame-Syntax von dplyr tun.
Die Funktion für rollierende Verknüpfungen von data.table wird auch in der Syntax von dplyr nicht unterstützt.
Wir haben kürzlich Überlappungs-Joins in data.table implementiert, um über Intervallbereiche zu verbinden ( hier ein Beispiel ). Dies ist
foverlaps()
derzeit eine separate Funktion und kann daher mit den Pipe-Operatoren verwendet werden (magrittr / pipeR? - habe es nie selbst versucht).Letztendlich ist es unser Ziel, es
[.data.table
so zu integrieren , dass wir die anderen Funktionen wie Gruppieren, Aggregieren während des Beitritts usw. nutzen können, die dieselben oben beschriebenen Einschränkungen aufweisen.Seit 1.9.4 implementiert data.table die automatische Indizierung mithilfe von Sekundärschlüsseln für schnelle, auf binärer Suche basierende Teilmengen mit regulärer R-Syntax. Beispiel:
DT[x == 1]
undDT[x %in% some_vals]
erstellt beim ersten Durchlauf automatisch einen Index, der dann für aufeinanderfolgende Teilmengen aus derselben Spalte bis zur schnellen Teilmenge mithilfe der binären Suche verwendet wird. Diese Funktion wird weiterentwickelt. In dieser Übersicht finden Sie eine kurze Übersicht über diese Funktion.Aus dem Weg
filter()
, der für data.tables implementiert ist, wird diese Funktion nicht genutzt.Eine dplyr-Funktion besteht darin, dass sie auch eine Schnittstelle zu Datenbanken mit derselben Syntax bietet, die data.table derzeit nicht bietet.
Sie müssen also diese (und wahrscheinlich auch andere) Punkte abwägen und basierend darauf entscheiden, ob diese Kompromisse für Sie akzeptabel sind.
HTH
(1) Beachten Sie, dass sich die Speichereffizienz direkt auf die Geschwindigkeit auswirkt (insbesondere wenn die Daten größer werden), da der Engpass in den meisten Fällen darin besteht, die Daten aus dem Hauptspeicher in den Cache zu verschieben (und die Daten im Cache so weit wie möglich zu nutzen - reduzieren Sie die Cache-Fehler - um den Zugriff auf den Hauptspeicher zu reduzieren). Ich gehe hier nicht auf Details ein.
quelle
filter()
Plussummarise()
mit demselben Ansatz zu implementieren , den dplyr für SQL verwendet - dh einen Ausdruck aufzubauen und dann nur einmal bei Bedarf auszuführen. Es ist unwahrscheinlich, dass dies in naher Zukunft implementiert wird, da dplyr für mich schnell genug ist und die Implementierung eines Abfrageplaners / -optimierers relativ schwierig ist.Probier es einfach.
Bei diesem Problem scheint data.table mit data.table 2,4-mal schneller zu sein als dplyr:
Überarbeitet basierend auf dem Kommentar von Polymerase.
quelle
microbenchmark
Pakets stellte ich fest, dass das Ausführen des OP-dplyr
Codes auf der Originalversion (Datenrahmen) vondiamonds
eine mittlere Zeit von 0,012 Sekunden und eine mittlere Zeit von 0,024 Sekunden nach dem Konvertierendiamonds
in eine Datentabelle dauerte . Das Ausführen desdata.table
Codes von G. Grothendieck dauerte 0,013 Sekunden. Zumindest auf meinem System sieht es so ausdplyr
unddata.table
hat ungefähr die gleiche Leistung. Aber warum solltedplyr
es langsamer sein, wenn der Datenrahmen zum ersten Mal in eine Datentabelle konvertiert wird?So beantworten Sie Ihre Fragen:
data.table
data.table
SyntaxIn vielen Fällen ist dies ein akzeptabler Kompromiss für diejenigen, die die
dplyr
Syntax wünschen, obwohl sie möglicherweise langsamer ist alsdplyr
bei einfachen Datenrahmen.Ein wichtiger Faktor scheint zu sein, dass
dplyr
dasdata.table
beim Gruppieren standardmäßig kopiert wird. Beachten Sie (unter Verwendung von Mikrobenchmark):Die Filterung ist von vergleichbarer Geschwindigkeit, die Gruppierung jedoch nicht. Ich glaube, der Schuldige ist diese Zeile in
dplyr:::grouped_dt
:wo
copy
standardmäßigTRUE
(und kann nicht einfach in FALSE geändert werden, was ich sehen kann). Dies macht wahrscheinlich nicht 100% des Unterschieds aus, aber der allgemeine Overhead allein für etwas, dessen Größediamonds
höchstwahrscheinlich nicht der volle Unterschied ist.Das Problem ist, dass
dplyr
die Gruppierung in zwei Schritten erfolgt, um eine konsistente Grammatik zu erhalten . Zuerst werden Schlüssel für eine Kopie der ursprünglichen Datentabelle festgelegt, die den Gruppen entsprechen, und erst später wird eine Gruppe erstellt.data.table
Weist nur Speicher für die größte Ergebnisgruppe zu, die in diesem Fall nur eine Zeile umfasst. Dies macht also einen großen Unterschied darin, wie viel Speicher zugewiesen werden muss.Zu Ihrer Information, wenn es jemanden interessiert, habe ich dies mithilfe von
treeprof
(install_github("brodieg/treeprof")
) gefunden, einem experimentellen (und immer noch sehr Alpha) Baum-Viewer für dieRprof
Ausgabe:Beachten Sie, dass das oben genannte derzeit nur auf Macs AFAIK funktioniert. Leider werden
Rprof
Anrufe des Typs auchpackagename::funname
als anonym aufgezeichnet, sodass tatsächlich alle darin enthaltenendatatable::
Anrufegrouped_dt
verantwortlich sein können, aber nach schnellen Tests sah es so aus, als wäredatatable::copy
es der große.Sie können jedoch schnell erkennen, dass der
[.data.table
Anruf nicht so viel Aufwand verursacht , aber es gibt auch einen völlig separaten Zweig für die Gruppierung.BEARBEITEN : um das Kopieren zu bestätigen:
quelle
Sie können jetzt dtplyr verwenden , das Teil der Tidyverse ist . Sie können wie gewohnt Anweisungen im dplyr-Stil verwenden, verwenden jedoch eine verzögerte Auswertung und übersetzen Ihre Anweisungen in data.table-Code unter der Haube. Der Aufwand für die Übersetzung ist minimal, aber Sie leiten alle, wenn nicht die meisten Vorteile von data.table ab. Weitere Details beim offiziellen Git Repo hier und auf der Tidyverse- Seite .
quelle