Bearbeiten:
Angesichts der guten Resonanz dieser Antwort habe ich sie in eine Paketvignette umgewandelt, die jetzt hier verfügbar ist
Angesichts der Häufigkeit, mit der dies geschieht, ist dies meiner Meinung nach eine etwas ausführlichere Darstellung erforderlich, die über die hilfreiche Antwort von Josh O'Brien hinausgeht.
Neben dem S ubset der D ata Akronym in der Regel von Josh zitiert / erstellt, ich denke , es ist auch hilfreich , um die „S“ zu prüfen , für „selbigen“ oder „Selbstreferenz“ stehen - .SD
ist in seiner einfachsten Gestalt ein reflexiver Verweis auf sich data.table
selbst - wie wir in den folgenden Beispielen sehen werden, ist dies besonders hilfreich, um "Abfragen" (Extraktionen / Teilmengen / usw. mit [
) zu verketten . Dies bedeutet insbesondere auch, dass .SD
es sich selbst um eine handeltdata.table
(mit der Einschränkung, dass keine Zuordnung zulässig ist :=
).
Die einfachere Verwendung von .SD
ist für die Spaltenuntermenge (dh wenn .SDcols
angegeben); Ich denke, diese Version ist viel einfacher zu verstehen, deshalb werden wir das zuerst unten behandeln. Die Interpretation von .SD
Gruppierungsszenarien (dh wann by =
oder keyby =
angegeben) in ihrer zweiten Verwendung ist konzeptionell etwas anders (obwohl es im Kern dieselbe ist, da eine nicht gruppierte Operation schließlich ein Randfall der Gruppierung mit just ist Eine Gruppe).
Hier sind einige veranschaulichende Beispiele und einige andere Beispiele für Verwendungen, die ich selbst oft implementiere:
Laden von Lahman-Daten
Um dies realistischer zu gestalten, anstatt Daten zu erstellen, laden wir einige Datensätze über Baseball von Lahman
:
library(data.table)
library(magrittr) # some piping can be beautiful
library(Lahman)
Teams = as.data.table(Teams)
# *I'm selectively suppressing the printed output of tables here*
Teams
Pitching = as.data.table(Pitching)
# subset for conciseness
Pitching = Pitching[ , .(playerID, yearID, teamID, W, L, G, ERA)]
Pitching
Nackt .SD
Um zu veranschaulichen, was ich über die Reflexivität von meine .SD
, betrachten Sie die banalste Verwendung:
Pitching[ , .SD]
# playerID yearID teamID W L G ERA
# 1: bechtge01 1871 PH1 1 2 3 7.96
# 2: brainas01 1871 WS3 12 15 30 4.50
# 3: fergubo01 1871 NY2 0 0 1 27.00
# 4: fishech01 1871 RC1 4 16 24 4.35
# 5: fleetfr01 1871 NY2 0 1 1 10.00
# ---
# 44959: zastrro01 2016 CHN 1 0 8 1.13
# 44960: zieglbr01 2016 ARI 2 3 36 2.82
# 44961: zieglbr01 2016 BOS 2 4 33 1.52
# 44962: zimmejo02 2016 DET 9 7 19 4.87
# 44963: zychto01 2016 SEA 1 0 12 3.29
Das heißt, wir sind gerade zurückgekehrt Pitching
, dh dies war eine übermäßig ausführliche Schreibweise Pitching
oder Pitching[]
:
identical(Pitching, Pitching[ , .SD])
# [1] TRUE
In Bezug auf die Teilmenge .SD
ist es immer noch eine Teilmenge der Daten, es ist nur eine triviale (die Menge selbst).
Spaltenuntermenge: .SDcols
Die erste Möglichkeit, Auswirkungen auf das zu haben, .SD
besteht darin, die in der Verwendung des Arguments enthaltenen Spalten auf Folgendes zu beschränken :.SD
.SDcols
[
Pitching[ , .SD, .SDcols = c('W', 'L', 'G')]
# W L G
# 1: 1 2 3
# 2: 12 15 30
# 3: 0 0 1
# 4: 4 16 24
# 5: 0 1 1
# ---
# 44959: 1 0 8
# 44960: 2 3 36
# 44961: 2 4 33
# 44962: 9 7 19
# 44963: 1 0 12
Dies ist nur zur Veranschaulichung und war ziemlich langweilig. Aber selbst diese einfache Verwendung eignet sich für eine Vielzahl von äußerst nützlichen / allgegenwärtigen Datenmanipulationsvorgängen:
Spaltentypkonvertierung
Spalte Typumwandlung ist eine Tatsache des Lebens für die Daten munging - als dies geschrieben wurde , fwrite
kann nicht automatisch gelesen Date
oder POSIXct
Spalten und Conversions zurück und her unter character
/ factor
/ numeric
sind üblich. Wir können Gruppen solcher Spalten verwenden .SD
und .SDcols
stapelweise konvertieren.
Wir stellen fest, dass die folgenden Spalten wie character
im Teams
Datensatz gespeichert sind :
# see ?Teams for explanation; these are various IDs
# used to identify the multitude of teams from
# across the long history of baseball
fkt = c('teamIDBR', 'teamIDlahman45', 'teamIDretro')
# confirm that they're stored as `character`
Teams[ , sapply(.SD, is.character), .SDcols = fkt]
# teamIDBR teamIDlahman45 teamIDretro
# TRUE TRUE TRUE
Wenn Sie durch die Verwendung von sapply
hier verwirrt sind, beachten Sie, dass es dasselbe ist wie für Basis R data.frames
:
setDF(Teams) # convert to data.frame for illustration
sapply(Teams[ , fkt], is.character)
# teamIDBR teamIDlahman45 teamIDretro
# TRUE TRUE TRUE
setDT(Teams) # convert back to data.table
Der Schlüssel zum Verständnis dieser Syntax zu erinnern ist , dass ein data.table
(sowie data.frame
) als ein in Betracht gezogen werden , list
wobei jedes Element eine Spalte - also sapply
/ lapply
gilt FUN
für jede Spalte und gibt das Ergebnis als sapply
/ lapply
in der Regel würde (hier FUN == is.character
kehrt eine logical
von Länge 1, sapply
gibt also einen Vektor zurück).
Die Syntax zum Konvertieren dieser Spalten factor
ist sehr ähnlich - fügen Sie einfach den :=
Zuweisungsoperator hinzu
Teams[ , (fkt) := lapply(.SD, factor), .SDcols = fkt]
Beachten Sie, dass wir fkt
in Klammern setzen müssen ()
, um R zu zwingen, dies als Spaltennamen zu interpretieren, anstatt zu versuchen, den Namen fkt
der RHS zuzuweisen .
Die Flexibilität von .SDcols
(und :=
), einen character
Vektor oder einen integer
Vektor von Spaltenpositionen zu akzeptieren, kann auch für die musterbasierte Konvertierung von Spaltennamen * nützlich sein. Wir könnten alle factor
Spalten konvertieren in character
:
fkt_idx = which(sapply(Teams, is.factor))
Teams[ , (fkt_idx) := lapply(.SD, as.character), .SDcols = fkt_idx]
Und konvertieren Sie dann alle Spalten, die zurück enthalten, team
in factor
:
team_idx = grep('team', names(Teams), value = TRUE)
Teams[ , (team_idx) := lapply(.SD, factor), .SDcols = team_idx]
** Die explizite Verwendung von Spaltennummern (wie DT[ , (1) := rnorm(.N)]
) ist eine schlechte Praxis und kann im Laufe der Zeit zu unbemerkt beschädigtem Code führen, wenn sich die Spaltenpositionen ändern. Selbst die implizite Verwendung von Zahlen kann gefährlich sein, wenn wir nicht genau kontrollieren, wann wir den nummerierten Index erstellen und wann wir ihn verwenden.
Steuerung der RHS eines Modells
Unterschiedliche Modellspezifikationen sind ein Kernmerkmal einer robusten statistischen Analyse. Versuchen wir, die ERA eines Pitchers (Earned Runs Average, ein Maß für die Leistung) anhand der in der Pitching
Tabelle verfügbaren kleinen Menge von Kovariaten vorherzusagen . Wie variiert die (lineare) Beziehung zwischen W
(gewinnt) und ERA
hängt davon ab, welche anderen Kovariaten in der Spezifikation enthalten sind?
Hier ist ein kurzes Skript, das die Kraft .SD
dieser Frage nutzt :
# this generates a list of the 2^k possible extra variables
# for models of the form ERA ~ G + (...)
extra_var = c('yearID', 'teamID', 'G', 'L')
models =
lapply(0L:length(extra_var), combn, x = extra_var, simplify = FALSE) %>%
unlist(recursive = FALSE)
# here are 16 visually distinct colors, taken from the list of 20 here:
# https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/
col16 = c('#e6194b', '#3cb44b', '#ffe119', '#0082c8', '#f58231', '#911eb4',
'#46f0f0', '#f032e6', '#d2f53c', '#fabebe', '#008080', '#e6beff',
'#aa6e28', '#fffac8', '#800000', '#aaffc3')
par(oma = c(2, 0, 0, 0))
sapply(models, function(rhs) {
# using ERA ~ . and data = .SD, then varying which
# columns are included in .SD allows us to perform this
# iteration over 16 models succinctly.
# coef(.)['W'] extracts the W coefficient from each model fit
Pitching[ , coef(lm(ERA ~ ., data = .SD))['W'], .SDcols = c('W', rhs)]
}) %>% barplot(names.arg = sapply(models, paste, collapse = '/'),
main = 'Wins Coefficient with Various Covariates',
col = col16, las = 2L, cex.names = .8)
Der Koeffizient hat immer das erwartete Vorzeichen (bessere Pitcher haben tendenziell mehr Siege und weniger erlaubte Läufe), aber die Größe kann erheblich variieren, je nachdem, was wir sonst noch kontrollieren.
Bedingte Verknüpfungen
data.table
Die Syntax ist schön für ihre Einfachheit und Robustheit. Die Syntax x[i]
verarbeitet flexibel zwei gemeinsame Ansätze zu subsetting - wenn i
a logical
vector, x[i]
kehrt die Zeilen der dem x
entspricht, wo i
ist TRUE
; Wenn i
es sich um ein anderes handeltdata.table
, join
wird a ausgeführt (in der einfachen Form unter Verwendung des key
s von x
und i
andernfalls, wenn on =
angegeben, unter Verwendung der Übereinstimmungen dieser Spalten).
Dies ist im Allgemeinen großartig, wird jedoch nicht ausgeführt, wenn eine bedingte Verknüpfung durchgeführt werden soll , bei der die genaue Art der Beziehung zwischen Tabellen von einigen Merkmalen der Zeilen in einer oder mehreren Spalten abhängt.
Dieses Beispiel ist ein bisschen erfunden, veranschaulicht aber die Idee; siehe hier ( 1 , 2 ) für mehr.
Ziel ist es team_performance
, der Pitching
Tabelle eine Spalte hinzuzufügen, in der die Leistung (Rang) des Teams als bester Pitcher in jedem Team aufgezeichnet wird (gemessen an der niedrigsten ERA unter Pitchern mit mindestens 6 aufgezeichneten Spielen).
# to exclude pitchers with exceptional performance in a few games,
# subset first; then define rank of pitchers within their team each year
# (in general, we should put more care into the 'ties.method'
Pitching[G > 5, rank_in_team := frank(ERA), by = .(teamID, yearID)]
Pitching[rank_in_team == 1, team_performance :=
# this should work without needing copy();
# that it doesn't appears to be a bug:
# https://github.com/Rdatatable/data.table/issues/1926
Teams[copy(.SD), Rank, .(teamID, yearID)]]
Beachten Sie, dass die x[y]
Syntax nrow(y)
Werte zurückgibt , weshalb sie .SD
rechts in steht Teams[.SD]
(da die RHS von :=
in diesem Fall nrow(Pitching[rank_in_team == 1])
Werte erfordert .
Gruppierte .SD
Operationen
Oft möchten wir unsere Daten auf Gruppenebene bearbeiten . Wenn wir angeben by =
(oder keyby =
), besteht das mentale Modell für das, was bei data.table
Prozessen passiert , j
darin, sich vorzustellen, dass Sie data.table
in viele Komponenten unterteilt sind data.table
, von denen jede einem einzelnen Wert Ihrer by
Variablen entspricht:
In diesem Fall .SD
ist es, mehrere in der Natur - bezieht er sich auf jede dieser sub- data.table
s, one-at-a-time (etwas genauer, der Umfang der .SD
ein einzelner sub- data.table
). Auf diese Weise können wir eine Operation, die wir für jedes Subdata.table
ausführen möchten, präzise ausdrücken, bevor das neu zusammengestellte Ergebnis an uns zurückgegeben wird.
Dies ist in einer Vielzahl von Einstellungen nützlich, von denen die häufigsten hier vorgestellt werden:
Gruppenuntermenge
Lassen Sie uns die neuesten Daten für jedes Team in den Lahman-Daten abrufen. Dies kann ganz einfach gemacht werden mit:
# the data is already sorted by year; if it weren't
# we could do Teams[order(yearID), .SD[.N], by = teamID]
Teams[ , .SD[.N], by = teamID]
Denken Sie daran, dass dies .SD
selbst ein data.table
ist und .N
sich auf die Gesamtzahl der Zeilen in einer Gruppe bezieht (dies entspricht der Anzahl nrow(.SD)
innerhalb jeder Gruppe), sodass .SD[.N]
die Gesamtheit.SD
für die letzte Zeile zurückgegeben wird, die jeder Zeile zugeordnet ist teamID
.
Eine andere übliche Version davon ist die Verwendung .SD[1L]
, um stattdessen die erste Beobachtung für jede Gruppe zu erhalten.
Gruppe Optima
Angenommen, wir wollten das beste Jahr für jedes Team zurückgeben, gemessen an der Gesamtzahl der erzielten Läufe ( R
wir könnten dies natürlich leicht anpassen, um auf andere Metriken zu verweisen). Anstatt aus jedem Unterelement ein festes Element zu nehmen data.table
, definieren wir den gewünschten Index jetzt dynamisch wie folgt:
Teams[ , .SD[which.max(R)], by = teamID]
Beachten Sie, dass dieser Ansatz natürlich kombiniert werden kann .SDcols
, um nur Teile von data.table
jedem zurückzugeben .SD
(mit der Einschränkung, .SDcols
die über die verschiedenen Teilmengen hinweg festgelegt werden sollte).
NB : .SD[1L]
wird derzeit optimiert GForce
( siehe auch ), data.table
Einbauten , die massiv die häufigsten gruppierten Operationen wie beschleunigen sum
oder mean
- siehe ?GForce
für weitere Einzelheiten und halten ein Auge auf / Sprachunterstützung für Anfragen Feature Verbesserung für Updates an dieser Front: 1 , 2 , 3 , 4 , 5 , 6
Gruppierte Regression
Zurück zur obigen Anfrage bezüglich der Beziehung zwischen ERA
und W
, nehmen wir an, dass diese Beziehung von Team zu Team unterschiedlich ist (dh es gibt für jedes Team eine andere Steigung). Wir können diese Regression leicht wiederholen, um die Heterogenität in dieser Beziehung wie folgt zu untersuchen (wobei zu beachten ist, dass die Standardfehler dieses Ansatzes im Allgemeinen falsch sind - die Spezifikation ERA ~ W*teamID
wird besser sein - dieser Ansatz ist leichter zu lesen und die Koeffizienten sind in Ordnung). ::
# use the .N > 20 filter to exclude teams with few observations
Pitching[ , if (.N > 20) .(w_coef = coef(lm(ERA ~ W))['W']), by = teamID
][ , hist(w_coef, 20, xlab = 'Fitted Coefficient on W',
ylab = 'Number of Teams', col = 'darkgreen',
main = 'Distribution of Team-Level Win Coefficients on ERA')]
Während es ein gutes Maß an Heterogenität gibt, gibt es eine deutliche Konzentration um den beobachteten Gesamtwert
Hoffentlich hat dies die Fähigkeit aufgeklärt, .SD
schönen, effizienten Code zu ermöglichen data.table
!
?data.table
wurde dank dieser Frage in Version 1.7.10 verbessert. Es erklärt nun den Namen.SD
gemäß der akzeptierten Antwort.