911 Stimmen

Data.table vs dplyr: Kann das eine etwas gut tun, das das andere nicht kann oder schlecht macht?

Übersicht

Ich bin relativ vertraut mit data.table, nicht so sehr mit dplyr. Ich habe einige dplyr Vignetten und Beispiele durchgelesen, die auf SO aufgetaucht sind, und meine bisherigen Schlussfolgerungen sind folgende:

  1. data.table und dplyr sind in Bezug auf Geschwindigkeit vergleichbar, außer bei vielen (d.h. >10-100K) Gruppen und in einigen anderen Umständen (siehe Benchmarks unten)
  2. dplyr hat eine zugänglichere Syntax
  3. dplyr abstrahiert potenzielle DB-Interaktionen (oder wird es tun)
  4. Es gibt einige geringfügige funktionale Unterschiede (siehe "Beispiele/Verwendung" unten)

In meinen Augen hat Punkt 2. nicht viel Gewicht, weil ich ziemlich vertraut mit data.table bin, obwohl ich verstehe, dass es für Benutzer, die mit beiden neu sind, ein großer Faktor sein wird. Ich würde gerne eine Diskussion darüber vermeiden, welches intuitiver ist, da das für meine spezifische Frage aus der Perspektive von jemandem, der bereits mit data.table vertraut ist, irrelevant ist. Ich möchte auch eine Diskussion darüber vermeiden, wie "intuitiver" zu einer schnelleren Analyse führt (sicherlich wahr, aber auch das ist nicht das, worauf ich hier am meisten interessiert bin).

Frage

Was ich wissen möchte ist:

  1. Gibt es analytische Aufgaben, die für Personen, die mit den Paketen vertraut sind, mit einem Paket einfacher zu codieren sind als mit dem anderen (d.h. irgendeine Kombination von erforderlichen Tastenkombinationen gegen erforderlichem Maß an Esoterik, wobei weniger von beidem eine gute Sache ist).
  2. Gibt es analytische Aufgaben, die mit einem Paket gegenüber einem anderen wesentlich effizienter durchgeführt werden (d.h. mehr als 2x).

Eine neue SO-Frage hat mich dazu etwas mehr nachdenken lassen, weil ich bis zu diesem Zeitpunkt nicht gedacht habe, dass dplyr viel bieten würde, was ich nicht bereits in data.table machen könnte. Hier ist die dplyr Lösung (Daten am Ende der Frage):

dat %.%
  group_by(name, job) %.%
  filter(job != "Boss" | year == min(year)) %.%
  mutate(cumu_job2 = cumsum(job2))

Was viel besser war als mein Versuch einer data.table Lösung. Das gesagt, gute data.table Lösungen sind auch sehr gut (vielen Dank an Jean-Robert, Arun, und hier sei angemerkt, dass ich hier eine einzelne Anweisung der streng optimalen Lösung vorgezogen habe):

setDT(dat)[,
  .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], 
  by=list(id, job)
]

Die Syntax für letzteres mag sehr esoterisch erscheinen, ist aber eigentlich ziemlich einfach, wenn man mit data.table vertraut ist (d.h. es werden keine der esoterischeren Tricks verwendet).

Im Idealfall würde ich gerne einige gute Beispiele sehen, bei denen die dplyr oder data.table Methode wesentlich prägnanter ist oder deutlich besser abschneidet.

Beispiele

Verwendung

  • dplyr erlaubt keine gruppierten Operationen, die eine beliebige Anzahl von Zeilen zurückgeben (aus eddi's Frage, Anmerkung: anscheinend wird dies in dplyr 0.5 implementiert, auch zeigt @beginneR in der Antwort auf eddi's Frage einen potenziellen Workaround unter Verwendung von do).
  • data.table unterstützt rollende Joins (danke @dholstius) sowie Überlappungsjoins
  • data.table optimiert intern Ausdrücke der Form DT[col == value] oder DT[col %in% values] für Geschwindigkeit durch automatische Indizierung, die binäre Suche verwendet, während sie die gleiche Basis-R-Syntax verwendet. Hier finden Sie einige weitere Details und einen kleinen Benchmark.
  • dplyr bietet Standardbewertungsversionen von Funktionen (z.B. regroup, summarize_each_), die die programmatische Verwendung von dplyr erleichtern können (Anmerkung: die programmatische Verwendung von data.table ist definitiv möglich, erfordert jedoch etwas Überlegung, Substitution/Quoting, etc, zumindest nach meinem Wissen)

Benchmarks

Daten

Dies gilt für das erste Beispiel, das ich im Fragenteil gezeigt habe.

dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))

443voto

hadley Punkte 97925

Hier ist mein Versuch einer umfassenden Antwort aus der dplyr-Perspektive, folgend der groben Gliederung von Aruns Antwort (aber etwas umgestellt basierend auf unterschiedlichen Prioritäten).

Syntax

Es gibt eine gewisse Subjektivität hinsichtlich der Syntax, aber ich stehe zu meiner Aussage, dass die Kürze von data.table es schwieriger macht zu lernen und schwerer zu lesen. Das liegt teilweise daran, dass dplyr ein viel einfacheres Problem löst!

Eine wirklich wichtige Sache, die dplyr für dich tut, ist, dass es deine Optionen einschränkt. Ich behaupte, dass die meisten Probleme mit einer einzigen Tabelle nur mit fünf Schlüsselverben filtern, wählen, mutieren, anordnen und zusammenfassen, zusammen mit einem "nach Gruppe" Adverb gelöst werden können. Diese Einschränkung ist eine große Hilfe beim Erlernen der Datenmanipulation, weil sie dabei hilft, dein Denken über das Problem zu ordnen. In dplyr wird jedes dieser Verben auf eine einzige Funktion abgebildet. Jede Funktion erfüllt eine Aufgabe und ist isoliert leicht zu verstehen.

Indem du diese einfachen Operationen mit %>% verknüpfst, schaffst du Komplexität. Hier ist ein Beispiel aus einem der Beiträge, auf den Arun verlinkt hat hierzu:

diamonds %>%
  filter(cut != "Fair") %>%
  group_by(cut) %>%
  summarize(
    AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = n()
  ) %>%
  arrange(desc(Count))

Selbst wenn du noch nie zuvor dplyr gesehen hast (oder sogar R!), kannst du immer noch verstehen, was passiert, weil die Funktionen alle englische Verben sind. Der Nachteil von englischen Verben ist, dass sie mehr Tipparbeit erfordern als [, aber ich denke, dass dies weitgehend durch eine bessere Autovervollständigung gemildert werden kann.

Hier ist der entsprechende data.table-Code:

diamondsDT <- data.table(diamonds)
diamondsDT[
  cut != "Fair", 
  .(AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = .N
  ), 
  by = cut
][ 
  order(-Count) 
]

Es ist schwieriger, diesen Code zu verfolgen, es sei denn, du bist bereits mit data.table vertraut. (Ich konnte auch nicht herausfinden, wie ich das wiederholte [ einrücken sollte, damit es meinen Augen gefällt). Persönlich, wenn ich mir Code anschaue, den ich vor 6 Monaten geschrieben habe, ist es, als würde ich einen von einem Fremden geschriebenen Code ansehen, daher ziehe ich klaren und wenn auch ausführlichen Code vor.

Zwei weitere kleine Faktoren, die ich denke, die die Lesbarkeit leicht verringern:

  • Da fast jede Datenoperation [ verwendet, benötigst du zusätzliche Kontext, um herauszufinden, was passiert. Ist zum Beispiel x[y] der Zusammenführung von zwei Datentabellen oder dem Extrahieren von Spalten aus einem Datenrahmen? Dies ist nur ein kleines Problem, denn in gut geschriebenem Code sollten die Variablennamen nahelegen, was passiert.

  • Ich mag, dass group_by() in dplyr eine separate Operation ist. Es ändert die Berechnung grundlegend, daher denke ich, dass es offensichtlich sein sollte, wenn du den Code überfliegst, und es ist einfacher, group_by() zu entdecken als das by Argument für [.data.table.

Ich mag auch, dass der Pipe nicht auf nur ein Paket beschränkt ist. Du kannst damit beginnen, deine Daten mit tidyr zu bereinigen und dann mit einem Plot in ggvis abschließen. Und du bist nicht auf die Pakete beschränkt, die ich schreibe - jeder kann eine Funktion schreiben, die nahtlos Teil einer Datenmanipulations-Pipeline bildet. Tatsächlich ziehe ich persönlich den zuvor verwendeten data.table-Code, umgeschrieben mit %>%, vor:

diamonds %>% 
  data.table() %>% 
  .[cut != "Fair", 
    .(AvgPrice = mean(price),
      MedianPrice = as.numeric(median(price)),
      Count = .N
    ), 
    by = cut
  ] %>% 
  .[order(-Count)]

Und die Idee des Pipings mit %>% ist nicht nur auf Datenrahmen beschränkt und lässt sich leicht auf andere Kontexte verallgemeinern: interaktive Webgrafiken, Web-Scraping, Gists, Laufzeitverträge, ...)

Speicher und Leistung

Ich habe diese zusammengefasst, weil sie für mich nicht so wichtig sind. Die meisten R-Benutzer arbeiten mit weit unter 1 Million Zeilen Daten und dplyr ist für diese Datengröße bereits schnell genug, dass du die Verarbeitungszeit nicht bemerkst. Wir optimieren dplyr für Ausdruckskraft bei mittleren Datenmengen; nutze gerne data.table für rohe Geschwindigkeit bei größeren Daten.

Die Flexibilität von dplyr bedeutet auch, dass du die Leistungsmerkmale leicht anpassen kannst mit derselben Syntax. Wenn die Leistung von dplyr mit dem Datenrahmen-Backend nicht gut genug für dich ist, kannst du das data.table-Backend verwenden (wenn auch mit einem etwas eingeschränkten Funktionsumfang). Wenn die Daten, mit denen du arbeitest, nicht in den Speicher passen, kannst du ein Datenbank-Backend verwenden.

All das gesagt, wird die Leistung von dplyr langfristig besser werden. Wir werden auf jeden Fall einige der großartigen Ideen von data.table implementieren, wie z.B. radix Bestellen und die Verwendung des gleichen Index für Verknüpfungen und Filter. Wir arbeiten auch an Parallelisierung, damit wir von mehreren Kernen profitieren können.

Funktionen

Ein paar Dinge, an denen wir 2015 arbeiten wollen:

  • das readr Paket, um es einfach zu machen, Dateien von der Festplatte zu holen und in in den Speicher, analog zu fread().

  • Mehr flexible Verknüpfungen, einschließlich Unterstützung für nicht-äquivalente Verknüpfungen.

  • Mehr flexible Gruppierungen wie Bootstrap-Stichproben, Zusammenfassungen und mehr

Ich investiere auch Zeit in die Verbesserung der R Datenbank-Connectoren, die Fähigkeit, mit Web-APIs zu kommunizieren, und es einfacher zu HTML-Seiten zu scrapen.

80voto

Thell Punkte 5795

In direkter Antwort auf den Fragetitel...

dplyr tut sicherlich Dinge, die data.table nicht kann.

Dein Punkt #3

dplyr abstrahiert (oder wird) potenzielle DB-Interaktionen

ist eine direkte Antwort auf deine eigene Frage, aber wird nicht auf ein hohes genug Niveau gehoben. dplyr ist wirklich eine erweiterbare Front-End zu mehreren Datenspeichermechanismen, wohingegen data.table eine Erweiterung zu einem einzelnen ist.

Betrachte dplyr als eine Backend-agnostische Schnittstelle, bei der alle Ziele die gleiche Grammatik verwenden und bei der du die Ziele und Handhaber nach Belieben erweitern kannst. data.table ist aus der Sicht von dplyr eines dieser Ziele.

Es wird hoffentlich nie einen Tag geben, an dem data.table versucht, deine Abfragen zu übersetzen, um SQL-Statements zu erstellen, die mit Datenspeichern auf Festplatte oder Netzwerkbetrieb operieren.

dplyr kann möglicherweise Dinge tun, die data.table nicht oder vielleicht nicht so gut kann.

Aufgrund der Arbeit im Arbeitsspeicher könnte data.table eine viel schwierigere Zeit haben, sich in die parallele Verarbeitung von Abfragen zu erweitern als dplyr.


In Antwort auf die im Text gestellten Fragen...

Verwendung

Gibt es analytische Aufgaben, die für Personen, die mit den Paketen vertraut sind, mit einem Paket einfacher zu codieren sind als mit dem anderen (d.h., eine Kombination von benötigten Tastenanschlägen vs. erforderlichem Grad an Esoterik, wobei weniger von beiden eine gute Sache ist).

Das mag wie eine Ausweichung erscheinen, aber die wirkliche Antwort ist nein. Personen, die mit den Tools vertraut sind, scheinen entweder das Paket zu verwenden, das ihnen am vertrautesten ist, oder dasjenige, das tatsächlich das Richtige für die jeweilige Aufgabe ist. Mit anderen Worten, manchmal möchtest du eine bestimmte Lesbarkeit präsentieren, manchmal eine bestimmte Leistung, und wenn du einen ausreichend hohen Level von beiden benötigst, brauchst du möglicherweise ein weiteres Tool, um zusammen mit dem bereits vorhandenen klarere Abstraktionen zu machen.

Leistung

Gibt es analytische Aufgaben, die in einem Paket wesentlich (d.h. um mehr als das 2-fache) effizienter durchgeführt werden als in einem anderen.

Auch hier nein. data.table glänzt darin, in allem, was es tut, effizient zu sein, während dplyr die Last trägt, in mancher Hinsicht auf den zugrunde liegenden Datenspeicher und registrierte Handhaber beschränkt zu sein.

Dies bedeutet, wenn du auf ein Leistungsproblem mit data.table stößt, kannst du ziemlich sicher sein, dass es in deiner Abfragefunktion liegt, und wenn es tatsächlich ein Engpass mit data.table ist, hast du dir die Freude gemacht, einen Bericht einzureichen. Das gilt auch dann, wenn dplyr data.table als das Back-End verwendet; du könntest einige Overheads von dplyr sehen, aber die Chancen sind, dass es an deiner Abfrage liegt.

Wenn dplyr Leistungsprobleme mit den Back-Ends hat, kannst du sie umgehen, indem du eine Funktion für hybride Auswertung registrierst oder (im Falle von Datenbanken) die generierte Abfrage vor der Ausführung manipulierst.

Siehe auch die akzeptierte Antwort zu wann ist plyr besser als data.table?

19voto

Iyar Lin Punkte 366

Beim Lesen der Antworten von Hadley und Arun könnte man den Eindruck bekommen, dass diejenigen, die die Syntax von dplyr bevorzugen, in einigen Fällen auf data.table umsteigen oder lange Laufzeiten in Kauf nehmen müssten.

Aber wie bereits erwähnt wurde, kann dplyr data.table als Backend verwenden. Dies wird mit dem dtplyr-Paket erreicht, das kürzlich Version 1.0.0 veröffentlicht hat. Das Erlernen von dtplyr erfordert praktisch keinen zusätzlichen Aufwand.

Bei der Verwendung von dtplyr verwendet man die Funktion lazy_dt(), um eine verzögerte data.table zu deklarieren, nach der standardmäßig dplyr-Syntax verwendet wird, um Operationen darauf festzulegen. Das würde in etwa so aussehen:

new_table <- mtcars2 %>% 
  lazy_dt() %>%
  filter(wt < 5) %>% 
  mutate(l100k = 235.21 / mpg) %>% # Liter / 100 km
  group_by(cyl) %>% 
  summarise(l100k = mean(l100k))

  new_table

#> Source: local data table [?? x 2]
#> Call:   `_DT1`[wt < 5][, `:=`(l100k = 235.21/mpg)][, .(l100k = mean(l100k)), 
#>     keyby = .(cyl)]
#> 
#>     cyl l100k
#>    
#> 1     4  9.05
#> 2     6 12.0 
#> 3     8 14.9 
#> 
#> # Verwenden Sie as.data.table()/as.data.frame()/as_tibble(), um auf die Ergebnisse zuzugreifen

Das Objekt new_table wird nicht ausgewertet, bevor man as.data.table()/as.data.frame()/as_tibble() darauf anwendet, zu diesem Zeitpunkt wird die zugrunde liegende data.table-Operation ausgeführt.

Ich habe eine Benchmark-Analyse wiederaufbereitet, die vom Autor von data.table, Matt Dowle, im Dezember 2018 durchgeführt wurde und den Fall von Operationen über eine große Anzahl von Gruppen abdeckt. Ich habe festgestellt, dass dtplyr tatsächlich größtenteils denen, die die dplyr-Syntax bevorzugen, ermöglicht, sie weiterhin zu verwenden und dabei die Geschwindigkeit von data.table zu nutzen.

-1voto

Yetta Jager Punkte 71

Ich habe mit data.table angefangen, benutze aber dplyr, um dem Arbeits-Team entgegenzukommen (und weil mir die Syntax besser gefällt). Beide sind aufgrund der Verkettung schwer zu debuggen. Eine wichtige Einschränkung ist, dass beide Probleme mit Berechnungen haben, die Informationen aus mehr als einer Zeile oder Spalte erfordern. Für data.table können Funktionen von mehr als einer Spalte mit mapply, map oder einfach zusätzlichen Argumenten mit lapply durchgeführt werden. Ich bin in dplyr nicht weiter darauf eingegangen, aber ich denke, es ist möglich. Allerdings finde ich keinen dplyr-Weg, um direkt auf Informationen in einer anderen Zeile zuzugreifen. Dies kann beispielsweise in data.table folgendermaßen gemacht werden: DT[, DDspawn := DD - .SD[jday==Jday.spawn, 'DD'], by=bys]. Ich glaube, dass diese Fähigkeit in der Datenbankterminologie als 'direkter Zugriff' bezeichnet wird, aber ich bin kein Experte. Gibt es einen dplyr-Weg? Es gibt Lag-Funktionen, also muss es möglich sein, aber ich finde nicht die Syntax, dies in dplyr zu tun.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X