Kurs
Dieses data.table-R-Tutorial erklärt die Grundlagen des DT[i, j, by]-Befehls, der das Herzstück des data.table-Pakets ist. Wenn du mehr über das data.table-Paket lernen möchtest, bietet DataCamp einen interaktiven R-Kurs zum data.table-Paket an. Der Kurs enthält mehr als 35 interaktive R-Übungen – alle bequem im Browser – und mehrere Videos mit Matt Dowle, dem Hauptautor des data.table-Pakets, und Arun Srinivasan, einem wichtigen Mitwirkenden. Probiere es kostenlos aus.
Wenn du bereits mit großen Datensätzen im RAM gearbeitet hast (1 bis über 100 GB), weißt du, dass ein data.frame an Grenzen stößt: Bestimmte Operationen dauern einfach zu lange. Data.table löst dieses Problem, indem es die Rechenzeit reduziert. Außerdem kannst du mit weniger Tippen mehr erreichen. Sobald du die data.table-Syntax aus diesem R-Tutorial verinnerlicht hast, wird dich die Leichtigkeit komplexer Operationen überraschen. Du sparst also nicht nur Rechen-, sondern auch Programmierzeit.
Der Befehl DT[i,j,by] hat drei Teile: i, j und by. In SQL-Begriffen entspricht i dem WHERE, j dem SELECT und by dem GROUP BY. Man kann den Befehl so lesen: „Nimm DT, filtere die Zeilen mit ‚i‘ und berechne dann ‚j‘ gruppiert nach ‚by‘.“ Ein einfaches Beispiel mit dem hflights-Datensatz (damit du alles nachbauen kannst) ergibt:
library(hflights)
library(data.table)
DT <- as.data.table(hflights)
DT[Month==10,mean(na.omit(AirTime)), by=UniqueCarrier]
UniqueCarrier V1
AA 68.76471
AS 255.29032
B6 176.93548
CO 141.52861
...
Hier haben wir die Tabelle auf den 10. Monat des Jahres eingeschränkt, die durchschnittliche AirTime der tatsächlich geflogenen Flüge berechnet (daher na.omit(); stornierte Flüge haben keinen AirTime-Wert) und die Ergebnisse nach Carrier gruppiert. Man sieht zum Beispiel, dass AA (American Airlines) im Schnitt deutlich kürzere Flugzeiten hat als AS (Alaska Airlines). Ist dir außerdem aufgefallen, dass Basisfunktionen aus R im j-Teil verwendet werden können? Darauf kommen wir gleich zurück.
Der i-Teil
Der ‚i‘-Teil dient zum Zeilen-Subsetting, genau wie bei einem data frame.
DT[2:5]
#selects the second to the fifth row of DT
Year Month DayofMonth DayOfWeek DepTime ArrTime UniqueCarrier FlightNum TailNum ActualElapsedTime AirTime
2011 1 2 7 1401 1501 AA 428 N557AA 60 45
2011 1 3 1 1352 1502 AA 428 N541AA 70 48
2011 1 4 2 1403 1513 AA 428 N403AA 70 39
2011 1 5 3 1405 1507 AA 428 N492AA 62 44
ArrDelay DepDelay Origin Dest Distance TaxiIn TaxiOut Cancelled CancellationCode Diverted
-9 1 IAH DFW 224 6 9 0 0
-8 -8 IAH DFW 224 5 17 0 0
3 3 IAH DFW 224 9 22 0 0
-3 5 IAH DFW 224 9 9 0 0
Du kannst auch Spaltennamen verwenden, da sie im Scope von DT ausgewertet werden.
DT[UniqueCarrier=="AA"]
#Returns all those rows where the Carrier is American Airlines
Year Month DayofMonth DayOfWeek DepTime ArrTime UniqueCarrier FlightNum TailNum ActualElapsedTime
2011 1 1 6 1400 1500 AA 428 N576AA 60
2011 1 2 7 1401 1501 AA 428 N557AA 60
2011 1 3 1 1352 1502 AA 428 N541AA 70
2011 1 4 2 1403 1513 AA 428 N403AA 70
2011 1 5 3 1405 1507 AA 428 N492AA 62
---
2011 12 27 2 1021 1333 AA 2234 N3ETAA 132
2011 12 28 3 1015 1329 AA 2234 N3FJAA 134
2011 12 29 4 1023 1335 AA 2234 N3GSAA 132
2011 12 30 5 1024 1334 AA 2234 N3BAAA 130
2011 12 31 6 1024 1343 AA 2234 N3HNAA 139
AirTime ArrDelay DepDelay Origin Dest Distance TaxiIn TaxiOut Cancelled CancellationCode Diverted
40 -10 0 IAH DFW 224 7 13 0 0
45 -9 1 IAH DFW 224 6 9 0 0
48 -8 -8 IAH DFW 224 5 17 0 0
39 3 3 IAH DFW 224 9 22 0 0
44 -3 5 IAH DFW 224 9 9 0 0
---
112 -12 1 IAH MIA 964 8 12 0 0
112 -16 -5 IAH MIA 964 9 13 0 0
110 -10 3 IAH MIA 964 12 10 0 0
110 -11 4 IAH MIA 964 9 11 0 0
119 -2 4 IAH MIA 964 8 12 0 0
Beachte, dass du kein Komma zum Zeilensubsetting in einer data.table brauchst. In einem data.frame würde DF[2:5] alle Zeilen der 2. bis 5. Spalte zurückgeben. Stattdessen müssen wir (wie allen hier klar ist) DF[2:5,] angeben. Beachte auch, dass DT[,2:5] bei data.tables nichts bedeutet; das wird in der ersten Frage der FAQs des data.table-Pakets erklärt.
Ungewöhnlich und nützlich: Beim Zeilensubsetting kannst du auch das Symbol .N im Befehl DT[…] verwenden; es steht für die Zeilenanzahl bzw. die letzte Zeile. Damit kannst du die letzte Zeile oder einen Versatz davon auswählen.
DT[.N-1]
#Returns the penultimate row of DT
Year Month DayofMonth DayOfWeek DepTime ArrTime UniqueCarrier FlightNum TailNum ActualElapsedTime AirTime
2011 12 6 2 656 812 WN 621 N727SW 76 64
ArrDelay DepDelay Origin Dest Distance TaxiIn TaxiOut Cancelled CancellationCode Diverted
-13 -4 HOU TUL 453 3 9 0 0
Der j-Teil
Der ‚j‘-Teil dient dazu, Spalten auszuwählen und etwas damit zu machen. Und etwas kann wirklich alles Mögliche sein. Alle Arten von Funktionen lassen sich verwenden – ein echtes Plus von data.table.
DT[, mean(na.omit(ArrDelay))]
[1] 7.094334
Beachte, dass der ‚i‘-Teil leer ist und das erste Zeichen in der Klammer ein Komma ist. Das wirkt anfangs etwas kontraintuitiv. Es bedeutet schlicht, dass wir keine Zeilen einschränken – also alle Zeilen verwenden. Im ‚j‘-Teil wird dann die durchschnittliche Verspätung bei der Ankunft aller Flüge berechnet. Offenbar hatten die Flüge im hflights-Datensatz im Schnitt mehr als 7 Minuten Verspätung. Plane das bei deinem nächsten Flug ein!
Wenn du mehrere Spalten auswählst und im ‚j‘-Teil etwas damit machst, musst du die ‚.()‘-Notation verwenden. Diese ist lediglich ein Alias für ‚list()‘. Sie liefert eine data.table zurück, während ohne ‚.()‘ – wie oben gezeigt – nur ein Vektor zurückkommt.
DT[, .(mean(na.omit(DepDelay)), mean(na.omit(ArrDelay)))]
V1 V2
9.444951 7.094334
Eine weitere nützliche Funktion, die die ‚.()‘-Notation erfordert, erlaubt es dir, Spalten innerhalb des DT[…]-Befehls umzubenennen.
DT[, .(Avg_ArrDelay =
mean(na.omit(ArrDelay)))]
Avg_ArrDelay
7.094334
DT[, .(Avg_DepDelay = mean(na.omit(DepDelay)),
avg_ArrDelay = mean(na.omit(ArrDelay)))]
Avg_DepDelay Avg_ArrDelay
9.444951 7.094334
Neue Spaltennamen sind natürlich optional.
Die obigen Punkte zu ‚i‘ und ‚j‘ kombiniert ergeben:
DT[UniqueCarrier=="AA", .(Avg_DepDelay =
mean(na.omit(DepDelay)),
Avg_ArrDelay = mean(na.omit(ArrDelay)),
plot(DepTime,DepDelay,ylim=c(-15,200)),
abline(h=0))]
Avg_DepDelay Avg_ArrDelay V3 V4
6.390144 0.8917558 NULL NULL
Hier haben wir im ‚i‘-Teil alle Zeilen mit Carrier AA ausgewählt, im ‚j‘-Teil die durchschnittliche Abflug- und Ankunftsverspätung berechnet und außerdem Abflugzeit gegen Abflugverspätung geplottet.
Noch einmal zusammengefasst: Der ‚j‘-Teil dient dazu, Berechnungen auf den dort angegebenen Spalten auszuführen. Da die Spalten einer data.table wie Variablen behandelt werden und die Bestandteile von ‚j‘ als Ausdrücke ausgewertet werden, ist im ‚j‘-Teil praktisch alles möglich. Das verkürzt deine Programmierzeit deutlich.
Der by-Teil
Der letzte Abschnitt dieses data.table-R-Tutorials widmet sich dem ‚by‘-Teil. Den ‚by‘-Teil verwendest du, wenn du den ‚j‘-Ausdruck gruppiert nach einer bestimmten Variable (oder einer Transformation davon) berechnen willst. Du wirst sehen, dass der ‚j‘-Ausdruck für jede ‚by‘-Gruppe wiederholt wird. Die Nutzung ist simpel: Gib im ‚by‘-Argument einfach die Spalte an, nach der du gruppieren möchtest.
DT[,mean(na.omit(DepDelay)),by=Origin]
Origin V1
IAH 8.436951
HOU 12.837873
Hier haben wir die durchschnittliche Abflugverspätung berechnet, allerdings gruppiert nach dem Startflughafen. Offenbar sind Flüge ab HOU im Schnitt stärker verspätet als jene ab IAH.
Wie beim ‚j‘-Teil kannst du auch im ‚by‘-Teil eine Menge machen. Funktionen lassen sich im ‚by‘-Teil einsetzen, sodass die Ergebnisse aus dem ‚j‘-Teil nach etwas gruppiert werden, das wir innerhalb des DT[…]-Befehls festlegen. Funktionen direkt in DT[…] machen diese eine Zeile sehr mächtig. Wenn du mehrere Spalten im ‚by‘-Teil nutzt, brauchst du ebenfalls die ‚.()‘-Notation.
DT[,.(Avg_DepDelay_byWeekdays = mean(na.omit(DepDelay))), by=.(Origin,Weekdays = DayOfWeek<6)]
Origin Weekdays Avg_DepDelay_byWeekdays
IAH FALSE 8.286543
IAH TRUE 8.492484
HOU FALSE 10.965384
HOU TRUE 13.433994
Hier wurde zunächst die durchschnittliche Abflugverspätung aller Flüge berechnet (kein Subsetting im ‚i‘-Teil, also alle Zeilen), anschließend gruppiert – zuerst nach Startflughafen, dann nach Wochentag. Weekdays ist an Wochenenden False. Es zeigt sich, dass die Abflugverspätung ab HOU größer war als ab IAH, und überraschenderweise an Wochenenden geringer.
Alles zusammen ergibt ein typischer DT[i,j,by]-Befehl:
DT[UniqueCarrier=="DL", .(Avg_DepDelay =
mean(na.omit(DepDelay)),
Avg_ArrDelay = mean(na.omit(ArrDelay)),
Compensation = mean(na.omit(ArrDelay - DepDelay))), by = .(Origin, Weekdays = DayOfWeek&lt;6)]
Origin Weekdays Avg_DepDelay Avg_ArrDelay Compensation
IAH FALSE 8.979730 4.116751 -4.825719
HOU FALSE 7.120000 2.656566 -4.555556
IAH TRUE 9.270948 6.281941 -2.836609
HOU TRUE 11.631387 10.406593 -1.278388
Hier wurde die Teilmenge der von Delta Air Lines geflogenen Flüge (ausgewählt in ‚i‘) nach Startflughafen und Weekdays gruppiert (in ‚by‘). Außerdem wurde die in der Luft „kompensierte“ Zeit berechnet (in ‚j‘). Es zeigt sich, dass an Wochenenden – unabhängig davon, ob der Flug von IAH oder HOU startete – die in der Luft aufgeholte Zeit größer ist.
Es gibt noch viel mehr im data.table-Paket zu entdecken, aber dieser Beitrag hat den grundlegenden DT[i,j,by]-Befehl veranschaulicht. Der DataCamp-Kurs erklärt das gesamte Paket ausführlich. Du kannst die Übungen in deinem eigenen Tempo im Browser bearbeiten, bekommst Tipps und Feedback und kannst Videos und Folien beliebig oft wiederholen. Dieses interaktive Lernen vermittelt dir fundiertes Wissen und praktische Erfahrung mit data.tables. Probiere es kostenlos aus. Sieh dir auch DataCamps data.table R Package Cheat Sheet an.
Hoffentlich verstehst du dank dieses data.table-R-Tutorials nun die grundlegende Syntax von data.table und bist bereit, selbst zu experimentieren. Wenn du Fragen zum data.table-Paket hast, schau hier vorbei. Matt und Arun sind sehr aktiv. Einer der nächsten Blogposts zum data.table-Paket wird technischer und zeigt die vielen Möglichkeiten von data.tables im Detail. Bleib dran!

