Редактировать:
Учитывая, насколько хорошо был получен этот ответ, я преобразовал его в виньетку пакета, доступную здесь
Учитывая, как часто это происходит, я думаю, что это заслуживает немного большего изложения, помимо полезного ответа Джоша О'Брайена выше.
В дополнении к S ubset от D ату аббревиатуры обычно цитируемой / создатель Джош, я думаю , что это также полезно рассмотреть «S» стоять « тот же самый» или «Self-эталонным» - .SDнаходится в самом базовом обличию а рефлексивная ссылка на data.tableсаму себя - как мы увидим в примерах ниже, это особенно полезно для объединения «запросов» (извлечения / подмножества / и т. д. [). В частности, это означает , что .SDэто само по себеdata.table (с оговоркой, что оно не позволяет присваивать :=).
Более простое использование .SDдля поднабора столбцов (т. Е. Когда .SDcolsуказано); Я думаю, что эту версию гораздо проще понять, поэтому мы рассмотрим ее сначала ниже. Интерпретация при .SDего втором использовании групповых сценариев (т. Е. Когда by =илиkeyby = указано), концептуально немного отличается (хотя по сути это то же самое, поскольку, в конце концов, несгруппированная операция - это крайний случай группировки с одна группа).
Вот несколько иллюстративных примеров и некоторые другие примеры использования, которые я сам часто использую:
Загрузка данных Lahman
Чтобы придать этому более реалистичный вид, а не составлять данные, давайте загрузим некоторые наборы данных о бейсболе из 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
обнаженный .SD
Чтобы проиллюстрировать, что я имею в виду относительно рефлексивной природы .SD, рассмотрим ее наиболее банальное использование:
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
То есть, мы только что вернулись Pitching, то есть это был слишком многословный способ написания Pitchingили Pitching[]:
identical(Pitching, Pitching[ , .SD])
# [1] TRUE
С точки зрения подмножества, .SDэто все еще подмножество данных, это просто тривиальное (сам набор).
Подмножество столбцов: .SDcols
Первый способ воздействия , что .SDэто является ограничение столбцов , содержащихся в .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
Это только для иллюстрации и было довольно скучно. Но даже это простое использование поддается широкому кругу очень полезных / вездесущих операций с данными:
Преобразование типов столбцов
Преобразование типа столбца является фактом существования для извлечения данных - на момент написания этой статьи fwriteневозможно автоматически читать столбцы Dateили POSIXctстолбцы , и преобразования между и character/ factor/ numericвстречаются часто. Мы можем использовать .SDи.SDcols для пакетного преобразования групп таких столбцов.
Мы заметили, что следующие столбцы хранятся как characterв Teamsнаборе данных:
# 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
Если вас смущает использование sapplyздесь, обратите внимание, что это то же самое, что и для базы 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
Ключом к пониманию этого синтаксиса является напоминание о том, что a data.table(а также a data.frame) можно рассматривать как a, listгде каждый элемент является столбцом - таким образом, sapply/ lapplyприменяется FUNк каждому столбцу и возвращает результат, как sapply/ lapplyобычно будет (здесь FUN == is.characterвозвращается logicalдлиной 1, такsapply возвращает вектор).
Синтаксис для преобразования этих столбцов factorочень похож - просто добавьте :=оператор присваивания
Teams[ , (fkt) := lapply(.SD, factor), .SDcols = fkt]
Обратите внимание, что мы должны заключить fktв скобки, ()чтобы заставить R интерпретировать это как имена столбцов, вместо того, чтобы пытаться присвоить имя fktRHS.
Гибкость .SDcols(и :=) принять characterвектор или в integerвектор позиций столбцов также может пригодиться для картины на основе преобразования имен столбцов *. Мы могли бы преобразовать все factorстолбцы в character:
fkt_idx = which(sapply(Teams, is.factor))
Teams[ , (fkt_idx) := lapply(.SD, as.character), .SDcols = fkt_idx]
А затем преобразовать все столбцы, которые содержат teamобратно в factor:
team_idx = grep('team', names(Teams), value = TRUE)
Teams[ , (team_idx) := lapply(.SD, factor), .SDcols = team_idx]
** Явное использование номеров столбцов (например DT[ , (1) := rnorm(.N)]) является плохой практикой и может привести к незаметно искаженному коду с течением времени, если позиции столбцов изменятся. Даже неявное использование чисел может быть опасным, если мы не сохраняем умный / строгий контроль над порядком, когда мы создаем нумерованный индекс и когда мы его используем.
Управление RHS модели
Различная спецификация модели является основной характеристикой надежного статистического анализа. Давайте попробуем спрогнозировать ERA (среднее значение заработанных ходов, показатель производительности) питчера, используя небольшой набор ковариат, представленных в Pitchingтаблице. Как (линейная) зависимость между W(выигрывает) и ERAизменяется в зависимости от того, какие другие ковариаты включены в спецификацию?
Вот краткий сценарий, использующий возможности .SDэтого вопроса:
# 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)

Коэффициент всегда имеет ожидаемый знак (у лучших кувшинов, как правило, больше побед и меньше разрешенных пробегов), но величина может существенно варьироваться в зависимости от того, что еще мы контролируем.
Условные объединения
data.tableсинтаксис прекрасен своей простотой и надежностью. Синтаксис x[i]гибко обрабатывает два общие подходы к Подменят - когда iэто logicalвектор, x[i]будет возвращать эти строки , xсоответствующие где iнаходится TRUE; когда iявляется другимdata.table , joinвыполняется a (в простой форме, используя keys of xи i, в противном случае, когда on =указано, используя совпадения этих столбцов).
В целом это хорошо, но не получается, когда мы хотим выполнить условное объединение , в котором точный характер взаимосвязи между таблицами зависит от некоторых характеристик строк в одном или нескольких столбцах.
Этот пример немного придуман, но иллюстрирует идею; смотри здесь ( 1 , 2 ) для получения дополнительной информации.
Цель состоит в том, чтобы добавить team_performanceв Pitchingтаблицу столбец, в котором записывается результативность команды (ранг) лучшего питчера в каждой команде (по наименьшему показателю ERA среди питчеров с минимум 6 зарегистрированными играми).
# 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)]]
Обратите внимание, что x[y]синтаксис возвращает nrow(y)значения, поэтому он .SDнаходится справа Teams[.SD](поскольку RHS :=в этом случае требует nrow(Pitching[rank_in_team == 1])значений.
Сгруппированные .SDоперации
Часто мы хотели бы выполнить некоторые операции с нашими данными на уровне группы . Когда мы указываем by =(или keyby =), ментальная модель того, что происходит, когда data.tableпроцессы jдолжны думать о том, что вы data.tableразделены на множество компонентных подпрограмм data.table, каждая из которых соответствует одному значению вашей byпеременной (переменных):

В этом случае, .SDэто несколько по своей природе - это относится к каждому из этих подпунктов data.table, по одному (чуть точнее, объем .SDявляется одним подпунктом data.table). Это позволяет нам кратко выразить операцию, которую мы хотели бы выполнить на каждой подпрограмме,data.table прежде чем нам будет возвращен пересобранный результат.
Это полезно в различных настройках, наиболее распространенные из которых представлены здесь:
Подмножество групп
Давайте получим самый последний сезон данных для каждой команды в данных Lahman. Это можно сделать довольно просто:
# 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]
Вспомните, что .SDоно само по себе data.tableи .Nотносится к общему количеству строк в группе (оно равно nrow(.SD)каждой группе), поэтому .SD[.N]возвращает полное значение.SD для последней строки, связанной с каждой teamID.
Другой распространенной версией этого является использование .SD[1L]вместо этого, чтобы получить первое наблюдение для каждой группы.
Группа Оптима
Предположим, что мы хотим вернуть лучший год для каждой команды, измеренный по их общему количеству забитых запусков ( Rразумеется, мы могли бы легко откорректировать это для ссылки на другие показатели). Вместо того, чтобы брать фиксированный элемент из каждого подпункта data.table, мы теперь динамически определяем желаемый индекс следующим образом:
Teams[ , .SD[which.max(R)], by = teamID]
Обратите внимание, что этот подход, конечно, можно комбинировать с .SDcolsвозвратом только части data.tableдля каждого .SD(с оговоркой, которая .SDcolsдолжна быть зафиксирована в различных подмножествах)
NB : .SD[1L]в настоящее время оптимизируется GForce( см. Также ) data.tableвнутренними компонентами, которые значительно ускоряют наиболее распространенные сгруппированные операции, такие как sumили mean- см. ?GForceДополнительную информацию и следите за / голосовой поддержкой запросов на улучшение функций для обновлений в этой области: 1 , 2 , 3 , 4 , 5 , 6
Сгруппированная регрессия
Возвращаясь к приведенному выше запросу относительно отношений между ERAи W, предположим, мы ожидаем, что эти отношения будут различаться в зависимости от команды (т. Е. Для каждой команды существует разный наклон). Мы можем легко перезапустить эту регрессию, чтобы исследовать неоднородность в этом отношении следующим образом (отмечая, что стандартные ошибки этого подхода, как правило, неверны - спецификация ERA ~ W*teamIDбудет лучше - этот подход легче читать, а коэффициенты в порядке) :
# 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')]

Хотя существует значительная степень неоднородности, существует четкая концентрация вокруг наблюдаемой общей стоимости
Надеюсь, это прояснило возможности создания .SDкрасивого и эффективного кода data.table!
?data.tableбыл улучшен в v1.7.10, благодаря этому вопросу. Это теперь объясняет имя.SDсогласно принятому ответу.