Редагувати:
З огляду на те, наскільки добре отримана ця відповідь, я перетворив її на віньєтку пакету, доступну тут
Зважаючи на те, як часто це трапляється, я думаю, що це заслуговує на трохи більше викладів, окрім корисної відповіді, яку Дав Джош О'Брайен вище.
У доповненні до 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
Ключовим моментом для розуміння цього синтаксису є нагадування про те, що data.table
(а також а data.frame
) можна розглядати як а, list
де кожен елемент є стовпцем - таким чином, sapply
/ lapply
застосовується FUN
до кожного стовпця і повертає результат так, як sapply
/ lapply
зазвичай було б (тут FUN == is.character
повертається a 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 моделі
Варіантна характеристика моделі є основною ознакою надійного статистичного аналізу. Давайте спробуємо передбачити ЕРА пітчера (зароблений середній запуск, показник продуктивності), використовуючи невеликий набір коваріатів, наявний у 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
, A join
виконується (в простому вигляді, використовуючи key
й 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]
замість цього, щоб отримати перше спостереження для кожної групи.
Група Optima
Припустимо, ми хотіли повернути найкращий рік для кожної команди, вимірюючи їх загальною кількістю пробіг ( R
; ми, звичайно, можемо легко скорегувати це, щоб посилатися на інші показники). Замість того, щоб брати фіксований елемент з кожного під- data.table
, тепер динамічно визначаємо бажаний індекс так:
Teams[ , .SD[which.max(R)], by = teamID]
Зауважте, що цей підхід, звичайно, може поєднуватися з .SDcols
поверненням лише частин data.table
для кожного .SD
(із застереженням, який .SDcols
слід фіксувати в різних підмножинах)
Примітка : .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
відповідно до прийнятої відповіді.