Ich werde versuchen, meine besten Anleitungen zu geben, aber es ist nicht einfach, weil man mit allen {data.table}, {dplyr}, {dtplyr} und auch {base R} vertraut sein muss. Ich benutze {data.table} und viele {tidy-world} -Pakete (außer {dplyr}). Ich liebe beide, obwohl ich die Syntax von data.table gegenüber dplyr's bevorzuge. Ich hoffe, dass alle Tidy-World-Pakete {dtplyr} oder {data.table} als Backend verwenden, wann immer dies erforderlich ist.
Wie bei jeder anderen Übersetzung (denken Sie an dplyr-to-sparkly / SQL) gibt es Dinge, die zumindest vorerst übersetzt werden können oder nicht. Ich meine, vielleicht kann {dtplyr} es eines Tages zu 100% übersetzen, wer weiß. Die folgende Liste ist weder vollständig noch zu 100% korrekt, da ich mein Bestes geben werde, um auf der Grundlage meines Wissens zu verwandten Themen / Paketen / Problemen / etc. Zu antworten.
Für die Antworten, die nicht ganz korrekt sind, hoffe ich, dass es Ihnen einige Anleitungen gibt, auf welche Aspekte von {data.table} Sie achten sollten, und vergleichen Sie sie mit {dtplyr} und finden Sie die Antworten selbst heraus. Nehmen Sie diese Antworten nicht als selbstverständlich an.
Und ich hoffe, dieser Beitrag kann als eine der Ressourcen für alle {dplyr}, {data.table} oder {dtplyr} Benutzer / Ersteller für Diskussionen und Kooperationen verwendet werden und #RStats noch besser machen.
{data.table} wird nicht nur für schnelle und speichereffiziente Operationen verwendet. Es gibt viele Leute, einschließlich mir, die die elegante Syntax von {data.table} bevorzugen. Es enthält auch andere schnelle Operationen wie Zeitreihenfunktionen wie frollapply
die in C geschriebene Rolling-Family (dh ). Es kann mit allen Funktionen verwendet werden, einschließlich Tidyverse. Ich benutze {data.table} + {purrr} viel!
Komplexität der Operationen
Dies kann leicht übersetzt werden
library(data.table)
library(dplyr)
library(flights)
data <- data.table(diamonds)
# dplyr
diamonds %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(
avg_price = mean(price),
median_price = as.numeric(median(price)),
count = n()
) %>%
arrange(desc(count))
# data.table
data [
][cut != 'Fair', by = cut, .(
avg_price = mean(price),
median_price = as.numeric(median(price)),
count = .N
)
][order( - count)]
{data.table} ist sehr schnell und speichereffizient, da (fast?) alles von Grund auf von C mit den Schlüsselkonzepten Update-by-Reference erstellt wird , Key (Think SQL) und deren unermüdlicher Optimierung überall im Paket erstellt wird (dh fifelse
, fread/fread
Radix - Sortierreihenfolge von Basis R angenommen), wobei er darauf achtete die Syntax ist präzise und konsistent, das ist , warum ich denke , es ist elegant ist.
Von der Einführung in data.table werden die wichtigsten Datenmanipulationsvorgänge wie Teilmenge, Gruppe, Aktualisierung, Verknüpfung usw. für zusammengehalten
prägnante und konsistente Syntax ...
flüssige Analyse ohne die kognitive Belastung, jede Operation abbilden zu müssen ...
Automatische und sehr effektive Optimierung von Vorgängen durch genaue Kenntnis der für jeden Vorgang erforderlichen Daten, was zu einem sehr schnellen und speichereffizienten Code führt
Der letzte Punkt als Beispiel:
# Calculate the average arrival and departure delay for all flights with “JFK” as the origin airport in the month of June.
flights[origin == 'JFK' & month == 6L,
.(m_arr = mean(arr_delay), m_dep = mean(dep_delay))]
Wir haben zuerst eine Teilmenge in i, um übereinstimmende Zeilenindizes zu finden, bei denen der Ursprungsflughafen gleich "JFK" und der Monat gleich 6L ist. Wir setzen noch nicht die gesamte Datentabelle, die diesen Zeilen entspricht, unter.
Nun schauen wir uns j an und stellen fest, dass es nur zwei Spalten verwendet. Und was wir tun müssen, ist ihren Mittelwert zu berechnen (). Daher setzen wir nur die Spalten unter, die den übereinstimmenden Zeilen entsprechen, und berechnen ihren Mittelwert ().
Da sich die drei Hauptkomponenten der Abfrage (i, j und by) zusammen in [...] befinden , kann data.table alle drei anzeigen und die Abfrage vor der Auswertung insgesamt optimieren, nicht jeweils einzeln . Wir sind daher in der Lage, die gesamte Teilmenge (dh die Teilmenge der Spalten neben arr_delay und dep_delay) sowohl für die Geschwindigkeit als auch für die Speichereffizienz zu vermeiden.
Um die Vorteile von {data.table} nutzen zu können, muss die Übersetzung von {dtplr} in dieser Hinsicht korrekt sein. Je komplexer die Operationen sind, desto schwieriger sind die Übersetzungen. Für einfache Operationen wie oben kann es sicherlich leicht übersetzt werden. Für komplexe oder solche, die nicht von {dtplyr} unterstützt werden, müssen Sie sich wie oben erwähnt selbst herausfinden, die übersetzte Syntax und den Benchmark vergleichen und vertraute verwandte Pakete kennen.
Für komplexe oder nicht unterstützte Operationen kann ich im Folgenden einige Beispiele nennen. Wieder versuche ich nur mein Bestes. Sei sanft zu mir.
Update-by-Reference
Ich werde nicht auf das Intro / die Details eingehen, aber hier sind einige Links
Hauptressource: Referenzsemantik
Weitere Details: Genau verstehen, wann eine data.table auf eine andere data.table verweist (im Vergleich zu einer Kopie davon)
Update-by-Reference ist meiner Meinung nach das wichtigste Merkmal von {data.table} und das macht es so schnell und speichereffizient. dplyr::mutate
unterstützt es standardmäßig nicht. Da ich mit {dtplyr} nicht vertraut bin, bin ich mir nicht sicher, wie viel und welche Vorgänge von {dtplyr} unterstützt werden können oder nicht. Wie oben erwähnt, hängt dies auch von der Komplexität der Operationen ab, die sich wiederum auf die Übersetzungen auswirken.
Es gibt zwei Möglichkeiten, Update-by-Reference in {data.table} zu verwenden
Zuweisungsoperator von {data.table} :=
set
-Familie: set
, setnames
, setcolorder
, setkey
, setDT
, fsetdiff
, und viele mehr
:=
wird im Vergleich zu häufiger verwendet set
. Bei komplexen und großen Datenmengen durch Referenz aktualisieren der Schlüssel, um Höchstgeschwindigkeit und Speichereffizienz zu erzielen. Die einfache Denkweise (nicht 100% genau, da die Details viel komplizierter sind, da es sich um Hard- / Flachkopien und viele andere Faktoren handelt) besagt, dass es sich um einen großen Datensatz von 10 GB mit 10 Spalten und jeweils 1 GB handelt . Um eine Spalte zu bearbeiten, müssen Sie nur 1 GB verarbeiten.
Der entscheidende Punkt ist, dass Sie mit Update-by-Reference nur die erforderlichen Daten verarbeiten müssen. Aus diesem Grund verwenden wir bei der Verwendung von {data.table}, insbesondere bei großen Datenmengen, nach Möglichkeit immer die Aktualisierung per Referenz . Beispiel: Bearbeiten eines großen Modellierungsdatensatzes
# Manipulating list columns
df <- purrr::map_dfr(1:1e5, ~ iris)
dt <- data.table(df)
# data.table
dt [,
by = Species, .(data = .( .SD )) ][, # `.(` shorthand for `list`
model := map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = . )) ][,
summary := map(model, summary) ][,
plot := map(data, ~ ggplot( . , aes(Sepal.Length, Sepal.Width)) +
geom_point())]
# dplyr
df %>%
group_by(Species) %>%
nest() %>%
mutate(
model = map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = . )),
summary = map(model, summary),
plot = map(data, ~ ggplot( . , aes(Sepal.Length, Sepal.Width)) +
geom_point())
)
Der Verschachtelungsvorgang wird list(.SD)
möglicherweise von {dtlyr} nicht unterstützt, wenn tidyverse Benutzer tidyr::nest
? Ich bin mir also nicht sicher, ob die nachfolgenden Operationen so übersetzt werden können, dass {data.table} schneller und weniger Speicherplatz ist.
HINWEIS: Das Ergebnis von data.table ist in "Millisekunde", dplyr in "Minute".
df <- purrr::map_dfr(1:1e5, ~ iris)
dt <- copy(data.table(df))
bench::mark(
check = FALSE,
dt[, by = Species, .(data = list(.SD))],
df %>% group_by(Species) %>% nest()
)
# # A tibble: 2 x 13
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc
# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl>
# 1 dt[, by = Species, .(data = list(.SD))] 361.94ms 402.04ms 2.49 705.8MB 1.24 2 1
# 2 df %>% group_by(Species) %>% nest() 6.85m 6.85m 0.00243 1.4GB 2.28 1 937
# # ... with 5 more variables: total_time <bch:tm>, result <list>, memory <list>, time <list>,
# # gc <list>
Es gibt viele Anwendungsfälle von Update-by-Reference und selbst {data.table} -Benutzer verwenden die erweiterte Version nicht immer, da mehr Codes erforderlich sind. Ob {dtplyr} diese Out-of-the-Box unterstützt, müssen Sie selbst herausfinden.
Mehrere Referenzaktualisierungen für dieselben Funktionen
Hauptressource : Elegantes Zuweisen mehrerer Spalten in data.table mit lapply ()
Dies betrifft entweder die am häufigsten verwendete :=
oder set
.
dt <- data.table( matrix(runif(10000), nrow = 100) )
# A few variants
for (col in paste0('V', 20:100))
set(dt, j = col, value = sqrt(get(col)))
for (col in paste0('V', 20:100))
dt[, (col) := sqrt(get(col))]
# I prefer `purrr::map` to `for`
library(purrr)
map(paste0('V', 20:100), ~ dt[, (.) := sqrt(get(.))])
Laut Ersteller von {data.table} Matt Dowle
(Beachten Sie, dass es möglicherweise üblicher ist, eine Schleife über eine große Anzahl von Zeilen als über eine große Anzahl von Spalten zu erstellen.)
Join + Setkey + Update-by-Reference
Ich brauchte in letzter Zeit eine schnelle Verknüpfung mit relativ großen Datenmengen und ähnlichen Verknüpfungsmustern, daher verwende ich anstelle der normalen Verknüpfungen die Möglichkeit, durch Referenz zu aktualisieren . Da sie mehr Codes benötigen, verpacke ich sie in ein privates Paket mit einer nicht standardmäßigen Bewertung für Wiederverwendbarkeit und Lesbarkeit, wo ich es nennesetjoin
.
Ich habe hier einen Benchmark durchgeführt: data.table join + update-by-reference + setkey
Zusammenfassung
# For brevity, only the codes for join-operation are shown here. Please refer to the link for details
# Normal_join
x <- y[x, on = 'a']
# update_by_reference
x_2[y_2, on = 'a', c := c]
# setkey_n_update
setkey(x_3, a) [ setkey(y_3, a), on = 'a', c := c ]
HINWEIS: dplyr::left_join
wurde ebenfalls getestet und ist mit ~ 9.000 ms am langsamsten. Verwenden Sie mehr Speicher als beide {data.table} update_by_reference
und setkey_n_update
, aber weniger Speicher als den normalen_join von {data.table}. Es verbrauchte ungefähr ~ 2,0 GB Speicher. Ich habe es nicht aufgenommen, da ich mich ausschließlich auf {data.table} konzentrieren möchte.
Wichtigste Ergebnisse
setkey + update
und update
sind ~ 11 und ~ 6,5 - mal schneller als normal join
jeweils
- Beim ersten Join
setkey + update
ähnelt die Performance von dem update
Overhead, der setkey
die eigenen Performancegewinne weitgehend ausgleicht
- bei zweiten und nachfolgenden Verknüpfungen ist, wie
setkey
nicht erforderlich, setkey + update
schneller als das update
~ 1,8-fache (oder schneller als das normal join
~ 11-fache)
Beispiele
Verwenden Sie für performante und speichereffiziente Verknüpfungen entweder update
oder setkey + update
, wenn letzteres auf Kosten von mehr Codes schneller ist.
Der Kürze halber sehen wir uns einige Pseudocodes an . Die Logik ist die gleiche.
Für eine oder mehrere Spalten
a <- data.table(x = ..., y = ..., z = ..., ...)
b <- data.table(x = ..., y = ..., z = ..., ...)
# `update`
a[b, on = .(x), y := y]
a[b, on = .(x), `:=` (y = y, z = z, ...)]
# `setkey + update`
setkey(a, x) [ setkey(b, x), on = .(x), y := y ]
setkey(a, x) [ setkey(b, x), on = .(x), `:=` (y = y, z = z, ...) ]
Für viele Spalten
cols <- c('x', 'y', ...)
# `update`
a[b, on = .(x), (cols) := mget( paste0('i.', cols) )]
# `setkey + update`
setkey(a, x) [ setkey(b, x), on = .(x), (cols) := mget( paste0('i.', cols) ) ]
Wrapper für schnelle und speichereffiziente Verknüpfungen ... viele von ihnen ... mit ähnlichem Verknüpfungsmuster, wickeln Sie sie wie setjoin
oben - mit update
- mit oder ohnesetkey
setjoin(a, b, on = ...) # join all columns
setjoin(a, b, on = ..., select = c('columns_to_be_included', ...))
setjoin(a, b, on = ..., drop = c('columns_to_be_excluded', ...))
# With that, you can even use it with `magrittr` pipe
a %>%
setjoin(...) %>%
setjoin(...)
Mit setkey
Argumenton
weggelassen werden. Es kann auch zur besseren Lesbarkeit enthalten sein, insbesondere für die Zusammenarbeit mit anderen.
Große Reihenoperation
- wie oben erwähnt verwenden
set
- Füllen Sie Ihre Tabelle vorab aus und verwenden Sie Update-by-Reference- Techniken
- Teilmenge mit Schlüssel (dh
setkey
)
Verwandte Ressource: Fügen Sie am Ende eines data.table-Objekts eine Zeile als Referenz hinzu
Zusammenfassung der Aktualisierung durch Referenz
Dies sind nur einige Anwendungsfälle von Update-by-Reference . Es gibt viele mehr.
Wie Sie sehen können, gibt es für die erweiterte Verwendung des Umgangs mit großen Datenmengen viele Anwendungsfälle und -techniken, bei denen Update-by-Reference verwendet wird für große Datenmengen verwendet wird. Es ist nicht so einfach in {data.table} zu verwenden und ob {dtplyr} es unterstützt, können Sie selbst herausfinden.
Ich konzentriere mich auf Update-by-Reference in diesem Beitrag da ich denke, dass dies die leistungsstärkste Funktion von {data.table} für schnelle und speichereffiziente Vorgänge ist. Das heißt, es gibt viele, viele andere Aspekte, die es auch so effizient machen, und ich denke, die von {dtplyr} nicht nativ unterstützt werden.
Andere Schlüsselaspekte
Was unterstützt wird / nicht, hängt auch von der Komplexität der Vorgänge ab und davon, ob es sich um die native Funktion von data.table handelt, z. B. Update-by-Reference oder setkey
. Und ob der übersetzte Code der effizientere ist (einer, den Benutzer von data.table schreiben würden), ist auch ein weiterer Faktor (dh der Code wird übersetzt, aber ist es die effiziente Version?). Viele Dinge sind miteinander verbunden.
setkey
. Siehe Schlüssel und schnelle Teilmenge der binären Suche
- Sekundärindizes und automatische Indizierung
- Verwenden von .SD für die Datenanalyse
- Zeitreihenfunktionen: Denken
frollapply
. Walzfunktionen, Walzaggregate, Schiebefenster, gleitender Durchschnitt
- Rolling Join , Non-Equi Join , (einige) "Cross" Join
- {data.table} hat die Grundlage für Geschwindigkeit und Speichereffizienz geschaffen. In Zukunft kann es viele Funktionen umfassen (z. B. die Implementierung der oben genannten Zeitreihenfunktionen).
- im Allgemeinen, die komplexere Operationen auf data.table der
i
, j
oder by
Operationen (können Sie fast alle Ausdrücke dort verwenden), ich denke , die die Übersetzungen härter, vor allem wenn es mit kombinieren Update-by-reference , setkey
und anderen einheimischen data.table funktioniert wiefrollapply
- Ein weiterer Punkt bezieht sich auf die Verwendung von Basis R oder Tidyverse. Ich benutze beide data.table + tidyverse (außer dplyr / readr / tidyr). Bei großen Operationen vergleiche ich häufig die
stringr::str_*
Funktionen von Familie und Basis R, und ich finde, dass Basis R bis zu einem gewissen Grad schneller ist, und verwende diese. Der Punkt ist, halten Sie sich nicht nur an tidyverse oder data.table oder ..., sondern erkunden Sie andere Optionen, um die Arbeit zu erledigen.
Viele dieser Aspekte hängen mit den oben genannten Punkten zusammen
Sie können herausfinden, ob {dtplyr} diese Vorgänge unterstützt, insbesondere wenn sie kombiniert werden.
Ein weiterer nützlicher Trick beim Umgang mit kleinen oder großen Datenmengen während einer interaktiven Sitzung: {data.table} erfüllt wirklich das Versprechen, die Programmier- und Rechenzeit enorm zu reduzieren .
Einstellschlüssel für wiederholt verwendete Variablen für Geschwindigkeit und 'aufgeladene Rownamen' (Teilmenge ohne Angabe des Variablennamens).
dt <- data.table(iris)
setkey(dt, Species)
dt['setosa', do_something(...), ...]
dt['virginica', do_another(...), ...]
dt['setosa', more(...), ...]
# `by` argument can also be omitted, particularly useful during interactive session
# this ultimately becomes what I call 'naked' syntax, just type what you want to do, without any placeholders.
# It's simply elegant
dt['setosa', do_something(...), Species, ...]
Wenn Ihre Operationen nur einfache wie im ersten Beispiel umfassen, kann {dtplyr} die Aufgabe erledigen. Für komplexe / nicht unterstützte können Sie dieses Handbuch verwenden, um die übersetzten von {dtplyr} damit zu vergleichen, wie erfahrene data.table-Benutzer mit der eleganten Syntax von data.table schnell und speichereffizient codieren würden. Die Übersetzung bedeutet nicht, dass dies der effizienteste Weg ist, da es möglicherweise unterschiedliche Techniken gibt, um mit unterschiedlichen Fällen großer Datenmengen umzugehen. Für noch größere Datenmengen können Sie {data.table} mit {disk.frame} , {fst} und {drake} und anderen fantastischen Paketen kombinieren , um das Beste daraus zu machen. Es gibt auch eine {big.data.table} , die derzeit jedoch inaktiv ist.
Ich hoffe es hilft allen. Einen schönen Tag noch ☺☺
dplyr
dem du gut abschneiden kannst und in dem du nicht gut abschneiden kannstdata.table
? Wenn nicht, ist der Wechseldata.table
zu besser alsdtplyr
.