1. Oktober 2016

Was ist ein data.table?

Das R-Paket data.table ist eine verbesserte und erweitere Form des data.frame.

Matt Dowle Arun Srinivasan

Workshop

  • Hands-On Einführung in data.table
  • Wichtige Konzepte und Befehle
  • Schwerpunkte:
    • Daten zusammenfassen
    • Daten ändern
  • Leider keine Schwerpunkte
    • Joins von verschiedenen Datenquellen
    • Reshaping

Warum data.table?

  • fread() ist die schnellste, einfachste und anpassbarste Lesefunktion für Daten aus externen Tabellen.
  • schnell
  • sparsam (Arbeitsspeicher)
  • Eine Syntax für alles

Daten einlesen

Einlesen von Eyetracking-Daten. 20 Spalten, 10287016 Zeilen, etwa 800 MB

system.time(df <- read.table("Zyklop4_Freq.txt", header = TRUE))
##    user  system elapsed 
## 146.485  11.211 165.623
library(data.table)
system.time(dt <- fread("Zyklop4_Freq.txt", showProgress = FALSE))
##    user  system elapsed 
##  12.103   1.058  13.781

data.table-Syntax

DT[ i , j , by]

Was passiert an den drei Stellen?

  • i: Auswahl eines Subsets von Zeilen
  • j: Auswahl von Spalten (Variablen)
  • by: Gruppierung anhand von Faktoren

Was für einen Input benötigen die Stellen?

  • i: Logischer Ausdruck / Key
  • j: Variablennamen als Liste / Zuweisungsoperator
  • by: Variablennamen als Liste

Übungsdatenset

  • Inspiriert von self-paced reading oder speeded accuracy tradeoff
  • subject: Teilnehmer-ID
  • phase: Erste oder zweite Teilnahme
  • trial: Durchlauf des Experiments
  • cond: Experimentelle Bedingung (ambig, eindeutig, filler)
  • rt1, rt2, rt3: Reaktionszeiten an drei Stellen im Trial

Einlesen der Daten mit fread()

dt <- fread("test_data.csv")

Ein kurzer Überblick

dt
##       subject phase_abc      cond       rt1       rt2      rt3 trial_abc
##    1:       1         1 eindeutig 155.89676 106.64248 241.2878         1
##    2:       1         1 eindeutig 191.58098 142.92113 163.0465         2
##    3:       1         1 eindeutig 113.09026 148.53180 208.3144         3
##    4:       1         1 eindeutig 121.86306 176.85986 184.8779         4
##    5:       1         1     ambig 386.83068 138.11622 194.9553         5
##   ---                                                                   
## 3596:      20         2 eindeutig  75.78100  88.82252 215.6548        86
## 3597:      20         2 eindeutig  84.12431 138.70172 146.4208        87
## 3598:      20         2 eindeutig 100.85870 104.03544 142.2829        88
## 3599:      20         2     ambig 214.77696 108.95919 205.5869        89
## 3600:      20         2     ambig 369.47361 306.05633 202.1959        90

Ein kurzer Überblick

str(dt)
## Classes 'data.table' and 'data.frame':   3600 obs. of  7 variables:
##  $ subject  : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ phase_abc: int  1 1 1 1 1 1 1 1 1 1 ...
##  $ cond     : chr  "eindeutig" "eindeutig" "eindeutig" "eindeutig" ...
##  $ rt1      : num  156 192 113 122 387 ...
##  $ rt2      : num  107 143 149 177 138 ...
##  $ rt3      : num  241 163 208 185 195 ...
##  $ trial_abc: int  1 2 3 4 5 6 7 8 9 10 ...
##  - attr(*, ".internal.selfref")=<externalptr>
c(is.data.table(dt), is.data.frame(dt))
## [1] TRUE TRUE

Spaltennamen bearbeiten

names(dt)
## [1] "subject"   "phase_abc" "cond"      "rt1"       "rt2"       "rt3"      
## [7] "trial_abc"

Es gibt im Datenset zwei Spaltennamen mit _abc als Suffix, was unnötig ist.

setnames(dt, c("trial_abc", "phase_abc"), c("trial", "phase"))
names(dt)
## [1] "subject" "phase"   "cond"    "rt1"     "rt2"     "rt3"     "trial"

Man kann auch alle Spaltennamen auf einmal ändern, indem man einen Namensvektor verwendet, der so lang ist wie die Spaltenanzahl

setnames(dt, c("subject", "phase", "cond", "rt1", "rt2", "rt3", "trial") )     

Zeilen-Subsets: i

Zeile 12-15

dt[12:15]
##    subject phase      cond       rt1      rt2      rt3 trial
## 1:       1     1 eindeutig 220.76992 130.5839 222.6751    12
## 2:       1     1 eindeutig 122.15787 186.0654 271.2614    13
## 3:       1     1    filler 183.70652 106.3440 166.0945    14
## 4:       1     1    filler  98.52713 143.6320 199.3453    15

Zeilen-Subsets: i

Alle Zeilen von Teilnehmer 10

dt[subject == 10]
##      subject phase      cond      rt1       rt2      rt3 trial
##   1:      10     1     ambig 458.2007 245.86966 219.0292     1
##   2:      10     1    filler 200.6776 217.36385 222.0075     2
##   3:      10     1 eindeutig 181.3904  63.51424 179.8608     3
##   4:      10     1 eindeutig 164.0693 119.24932 242.0558     4
##   5:      10     1 eindeutig 217.8179 132.89706 149.7172     5
##  ---                                                          
## 176:      10     2     ambig 361.9925 181.40055 132.9176    86
## 177:      10     2 eindeutig 177.7841 162.41290 147.1650    87
## 178:      10     2    filler 135.5509  73.31096 125.5691    88
## 179:      10     2 eindeutig 178.5278 144.27265 172.3546    89
## 180:      10     2 eindeutig 186.5837  90.66802 243.5949    90

Zeilen-Subsets: i

Alle Zeilen in denen rt2 kleiner als 90 ms ist

dt[rt2 < 90]
##      subject phase      cond      rt1      rt2      rt3 trial
##   1:       1     1     ambig 247.5130 40.63681 156.4901    27
##   2:       1     1     ambig 409.0629 66.51920 221.6415    29
##   3:       1     1    filler 136.8414 84.25239 210.5631    34
##   4:       1     1    filler 142.4835 41.90551 269.0570    45
##   5:       1     1     ambig 365.8766 25.03176 196.3923    47
##  ---                                                         
## 421:      20     2    filler 106.6338 41.67573 235.1373    69
## 422:      20     2 eindeutig 139.8379 37.56570 171.2089    70
## 423:      20     2 eindeutig 120.5089 40.05831 135.2224    72
## 424:      20     2 eindeutig 117.0844 77.05615 144.5680    85
## 425:      20     2 eindeutig  75.7810 88.82252 215.6548    86

Eine Spalte auswählen: j

Spalte rt1 als Vektor (Die ersten 6 Elemente)

head(dt[, rt1])
## [1] 155.8968 191.5810 113.0903 121.8631 386.8307 373.8521

Spalte rt1 als Liste / DT (Die ersten 6 Elemente)

head(dt[, .(rt1)])
##         rt1
## 1: 155.8968
## 2: 191.5810
## 3: 113.0903
## 4: 121.8631
## 5: 386.8307
## 6: 373.8521

Mehrere Spalten auswählen: j

head(dt[, .(rt1, rt2, rt3)], 3)
##         rt1      rt2      rt3
## 1: 155.8968 106.6425 241.2878
## 2: 191.5810 142.9211 163.0465
## 3: 113.0903 148.5318 208.3144
head(dt[, c("rt1", "rt2", "rt3"), with=FALSE], 3)
##         rt1      rt2      rt3
## 1: 155.8968 106.6425 241.2878
## 2: 191.5810 142.9211 163.0465
## 3: 113.0903 148.5318 208.3144
head(dt[, 4:6, with=FALSE], 3)
##         rt1      rt2      rt3
## 1: 155.8968 106.6425 241.2878
## 2: 191.5810 142.9211 163.0465
## 3: 113.0903 148.5318 208.3144

Gruppierung anhand von Faktoren: by

  • Wie erstellt man Statistiken für verschiedene Datengruppen?
  • Standard R: split() und apply()
  • data.table: Einheitliche Syntax mit by
  • Alternativen: Hadley Wickhams dplyr-Paket

Einfaches Beispiel

Mittelwert der Reaktionszeit in Abhängigkeit von Bedingung.

Standard R

with(dt, lapply(split(rt1, cond), mean, na.rm=TRUE))
## $ambig
## [1] 383.999
## 
## $eindeutig
## [1] 137.2745
## 
## $filler
## [1] 137.1714

Data.table

dt[, .(mean_rt = mean(rt1, na.rm = TRUE)), .(cond) ]
##         cond  mean_rt
## 1: eindeutig 137.2745
## 2:     ambig 383.9990
## 3:    filler 137.1714

Nicht so einfaches Beispiel mit Standard R:

Mittelwert und Standardabweichung der Reaktionszeit in Abhängigkeit von Bedingung und Phase

with(dt, sapply(split(rt1, list(cond, phase)), mean, na.rm=TRUE))
##     ambig.1 eindeutig.1    filler.1     ambig.2 eindeutig.2    filler.2 
##    395.3650    148.9377    149.6491    372.7088    125.6114    124.8601
with(dt, sapply(split(rt1, list(cond, phase)), sd, na.rm=TRUE))
##     ambig.1 eindeutig.1    filler.1     ambig.2 eindeutig.2    filler.2 
##    74.78533    40.19283    42.00469    76.36567    41.86440    40.96023

Data.table: by

Data.table

dt[, .(mean_rt = mean(rt1, na.rm = TRUE),
       sd_rt = sd(rt1, na.rm = TRUE)), .(cond, phase) ]
##         cond phase  mean_rt    sd_rt
## 1: eindeutig     1 148.9377 40.19283
## 2:     ambig     1 395.3650 74.78533
## 3:    filler     1 149.6491 42.00469
## 4: eindeutig     2 125.6114 41.86440
## 5:     ambig     2 372.7088 76.36567
## 6:    filler     2 124.8601 40.96023

Kompliziertes Beispiel mit Standard R:

Mittelwert für alle Reaktionszeiten in Abhängigkeit von Bedingung und Phase

with(dt, sapply(split(rt1, list(cond, phase)), mean, na.rm=TRUE))
##     ambig.1 eindeutig.1    filler.1     ambig.2 eindeutig.2    filler.2 
##    395.3650    148.9377    149.6491    372.7088    125.6114    124.8601
with(dt, sapply(split(rt2, list(cond, phase)), mean, na.rm=TRUE))
##     ambig.1 eindeutig.1    filler.1     ambig.2 eindeutig.2    filler.2 
##    204.9784    149.3010    149.3194    179.5596    123.3646    127.0388
with(dt, sapply(split(rt3, list(cond, phase)), mean, na.rm=TRUE))
##     ambig.1 eindeutig.1    filler.1     ambig.2 eindeutig.2    filler.2 
##    202.9520    197.0960    199.4284    176.5738    172.6635    175.5177

Data.table. by und .SD

Data.table

  • .SD steht für alle Spalten auf die eine Funktion angewendet werden soll
  • .SDcols legt diese Spalten fest
dt[, lapply(.SD, mean, na.rm = TRUE), .(cond, phase), .SDcols = c("rt1", "rt2", "rt3")]
##         cond phase      rt1      rt2      rt3
## 1: eindeutig     1 148.9377 149.3010 197.0960
## 2:     ambig     1 395.3650 204.9784 202.9520
## 3:    filler     1 149.6491 149.3194 199.4284
## 4: eindeutig     2 125.6114 123.3646 172.6635
## 5:     ambig     2 372.7088 179.5596 176.5738
## 6:    filler     2 124.8601 127.0388 175.5177

Zeilen zählen mit .N

Oftmals möchte man wissen, wie viele Beobachtungen es für eine bestimmte Kombination von Variablen gibt. Hier hilft .N

dt[, .N] 
## [1] 3600
nrow(dt)
## [1] 3600
dt[, .N, .(cond)]
##         cond    N
## 1: eindeutig 1200
## 2:     ambig 1200
## 3:    filler 1200

Übung 1

Es ist Zeit für eine kurze Übung

Werte verändern - Der := - Operator

  • Werte werden in j verändert.
  • = wird bereits genutzt
  • Zum Zuweisen von Werten wird der := - Operator genutzt.
  • Dieser steht auch als Funktion zur Verfügung, wenn mehrere Spalten gleichzeitig verändert werden sollen.

Ein paar Beispiele

Eine neue Spalte erzeugen (Konstante)

dt[, konstante := 1]
head(dt, 3)
##    subject phase      cond      rt1      rt2      rt3 trial konstante
## 1:       1     1 eindeutig 155.8968 106.6425 241.2878     1         1
## 2:       1     1 eindeutig 191.5810 142.9211 163.0465     2         1
## 3:       1     1 eindeutig 113.0903 148.5318 208.3144     3         1

Eine neue Spalte erzeugen (Funktion)

dt[, centered_trial := scale(trial, scale = FALSE)]
head(dt, 3)
##    subject phase      cond      rt1      rt2      rt3 trial centered_trial
## 1:       1     1 eindeutig 155.8968 106.6425 241.2878     1          -44.5
## 2:       1     1 eindeutig 191.5810 142.9211 163.0465     2          -43.5
## 3:       1     1 eindeutig 113.0903 148.5318 208.3144     3          -42.5

Ein paar Beispiele

Eine Spalte verändern

dt[, phase := factor(phase, labels = c("First", "Second"))]
head(dt, 3)
##    subject phase      cond      rt1      rt2      rt3 trial centered_trial
## 1:       1 First eindeutig 155.8968 106.6425 241.2878     1          -44.5
## 2:       1 First eindeutig 191.5810 142.9211 163.0465     2          -43.5
## 3:       1 First eindeutig 113.0903 148.5318 208.3144     3          -42.5

Eine Spalte löschen

dt[, centered_trial := NULL]
head(dt, 3)
##    subject phase      cond      rt1      rt2      rt3 trial
## 1:       1 First eindeutig 155.8968 106.6425 241.2878     1
## 2:       1 First eindeutig 191.5810 142.9211 163.0465     2
## 3:       1 First eindeutig 113.0903 148.5318 208.3144     3

Mehrere Werte zuweisen

Eventuell möchte man sich gerne die logarithmierten RTs ansehen und diese Werte als neue Spalten in den data.table einfügen

dt[, `:=`(log_rt1 = log(rt1), 
          log_rt2 = log(rt2),
          log_rt3 = log(rt3))]

head(dt, 3)
##    subject phase      cond      rt1      rt2      rt3 trial  log_rt1
## 1:       1 First eindeutig 155.8968 106.6425 241.2878     1 5.049194
## 2:       1 First eindeutig 191.5810 142.9211 163.0465     2 5.255311
## 3:       1 First eindeutig 113.0903 148.5318 208.3144     3 4.728186
##     log_rt2  log_rt3
## 1: 4.669482 5.485990
## 2: 4.962293 5.094035
## 3: 5.000799 5.339049

Werte in Abhängigkeit von Gruppen

:= und by

Beispiel: Normalisierte RT pro Proband

dt[, rt1_scaled := scale(rt1), .(subject)]

Verändern der Werte für ein Subset

Addieren einer Konstante zur SubjectID um diese nachher besser ausschließen zu können.

dt[subject == 1, subject := subject + 900L ]
head(dt,3)
##    subject phase      cond      rt1      rt2      rt3 trial
## 1:     901 First eindeutig 155.8968 106.6425 241.2878     1
## 2:     901 First eindeutig 191.5810 142.9211 163.0465     2
## 3:     901 First eindeutig 113.0903 148.5318 208.3144     3

Übung 2

Es ist Zeit für eine kurze Übung

Keys

  • Keys sind Zeilennamen mit Superkräften
  • Ermöglichen das schnelle Subsetten bei sehr großen Datentabellen

  • Keys für Spalten werden mit setkey() gesetzt
  • Der data.table wird nach dieser Spalte sortiert

Ein key-Beispiel

dt[1:6]
##    subject phase      cond      rt1      rt2      rt3 trial
## 1:     901 First eindeutig 155.8968 106.6425 241.2878     1
## 2:     901 First eindeutig 191.5810 142.9211 163.0465     2
## 3:     901 First eindeutig 113.0903 148.5318 208.3144     3
## 4:     901 First eindeutig 121.8631 176.8599 184.8779     4
## 5:     901 First     ambig 386.8307 138.1162 194.9553     5
## 6:     901 First     ambig 373.8521 205.6733 203.0555     6
setkey(dt, cond)

dt[1:6]
##    subject phase  cond      rt1       rt2      rt3 trial
## 1:     901 First ambig 386.8307 138.11622 194.9553     5
## 2:     901 First ambig 373.8521 205.67333 203.0555     6
## 3:     901 First ambig 409.9648 117.80012 139.7785    16
## 4:     901 First ambig 383.0919 312.00074 178.9307    18
## 5:     901 First ambig 247.5130  40.63681 156.4901    27
## 6:     901 First ambig 498.1183 254.65926 169.5743    28

Schnelles Subsetten - Ein Wert

dt["ambig"]
##       subject  phase  cond      rt1       rt2      rt3 trial
##    1:     901  First ambig 386.8307 138.11622 194.9553     5
##    2:     901  First ambig 373.8521 205.67333 203.0555     6
##    3:     901  First ambig 409.9648 117.80012 139.7785    16
##    4:     901  First ambig 383.0919 312.00074 178.9307    18
##    5:     901  First ambig 247.5130  40.63681 156.4901    27
##   ---                                                       
## 1196:      20 Second ambig 348.5359 387.14549 180.7765    76
## 1197:      20 Second ambig 299.1182 278.76603 192.4280    79
## 1198:      20 Second ambig 345.1959 179.99092 229.9224    80
## 1199:      20 Second ambig 214.7770 108.95919 205.5869    89
## 1200:      20 Second ambig 369.4736 306.05633 202.1959    90

Schnelles Subsetten - Mehrere Werte

dt[c("ambig", "filler")]
##       subject  phase   cond       rt1       rt2      rt3 trial
##    1:     901  First  ambig 386.83068 138.11622 194.9553     5
##    2:     901  First  ambig 373.85208 205.67333 203.0555     6
##    3:     901  First  ambig 409.96483 117.80012 139.7785    16
##    4:     901  First  ambig 383.09186 312.00074 178.9307    18
##    5:     901  First  ambig 247.51295  40.63681 156.4901    27
##   ---                                                         
## 2396:      20 Second filler 131.03536 134.15808 183.0228    77
## 2397:      20 Second filler 123.71196  96.21135 197.6485    81
## 2398:      20 Second filler  95.48136 133.85050 138.7520    82
## 2399:      20 Second filler  96.85457 120.42536 151.4046    83
## 2400:      20 Second filler 107.90459  91.51640 191.3651    84

Ein Key für mehrere Spalten

setkeyv(dt, c("cond", "phase"))

dt[.("ambig", "First")]
##      subject phase  cond      rt1       rt2      rt3 trial
##   1:     901 First ambig 386.8307 138.11622 194.9553     5
##   2:     901 First ambig 373.8521 205.67333 203.0555     6
##   3:     901 First ambig 409.9648 117.80012 139.7785    16
##   4:     901 First ambig 383.0919 312.00074 178.9307    18
##   5:     901 First ambig 247.5130  40.63681 156.4901    27
##  ---                                                      
## 596:      20 First ambig 477.0779 143.11265 189.7405    75
## 597:      20 First ambig 512.2024 169.43233 189.2390    78
## 598:      20 First ambig 292.3956 172.60287 195.5032    79
## 599:      20 First ambig 431.0391 185.41375 185.0999    83
## 600:      20 First ambig 188.3254 224.47595 226.3348    89

Ein paar Beispiele für den Einsatz von Keys

setkey(dt, cond)
dt["ambig", max(rt1, na.rm = TRUE)]
## [1] 629.4806
dt["eindeutig", min(rt2, na.rm = TRUE), .(subject)][1:10]
##     subject        V1
##  1:     901 50.786401
##  2:       2 38.733796
##  3:       3  9.035472
##  4:       4 54.337898
##  5:       5  8.481713
##  6:       6 61.104929
##  7:       7 46.394125
##  8:       8 58.040648
##  9:       9 42.494815
## 10:      10 49.977858

Ein paar Beispiele für den Einsatz von Keys

setkeyv(dt, c("cond", "phase"))
dt[.("ambig", "First"), max(rt3, na.rm = TRUE) , .(subject)][1:10]
##     subject       V1
##  1:     901 254.5915
##  2:       2 260.4426
##  3:       3 246.9001
##  4:       4 270.9320
##  5:       5 272.0470
##  6:       6 252.4148
##  7:       7 247.1512
##  8:       8 260.1442
##  9:       9 261.3048
## 10:      10 266.6644

Verketten von Befehlen

  • Es ist möglich, mehrere data.table-Operationen zu verketten.
  • Aus Gründen der Nachvollziehbarkeit sollte damit aber nicht übertrieben werden.
  • Nützlich um neu erstellte Spalten zusammen zu fassen oder Subsets anzuzeigen.
  • Zum Verketten einfach eine weitere Klammer anfügen dt[ i , j , by][ i , j , by]

Beispiel: Verkettung

  • Log von rt1 erstellen
  • Maximum von log_rt1 pro Proband finden
  • Nur die Werte jedes zweiten Probanden anzeigen
dt[, log_rt1 := log(rt1)][, max(log_rt1, na.rm = TRUE), .(subject)][subject %in% seq(1,20, 2)]
##    subject       V1
## 1:       3 6.251110
## 2:       5 6.295617
## 3:       7 6.410401
## 4:       9 6.312228
## 5:      11 6.278011
## 6:      13 6.363427
## 7:      15 6.371696
## 8:      17 6.297096
## 9:      19 6.444895

Übung 3

Es ist Zeit für eine letzte Übung

Weitere Quellen zu data.table

vignette("datatable-intro-vignette")
vignette("datatable-reference-semantics")
vignette("datatable-keys-fast-subset")
vignette("datatable-reshape")

Vielen Dank