Редактировать:
Учитывая, насколько хорошо был получен этот ответ, я преобразовал его в виньетку пакета, доступную здесь
Учитывая, как часто это происходит, я думаю, что это заслуживает немного большего изложения, помимо полезного ответа Джоша О'Брайена выше.
В дополнении к 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 интерпретировать это как имена столбцов, вместо того, чтобы пытаться присвоить имя fkt
RHS.
Гибкость .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 (в простой форме, используя key
s 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
согласно принятому ответу.