Що означає .SD позначає у data.table в R


173

.SDвиглядає корисною, але я не дуже знаю, що з цим роблю. Що це означає? Чому існує попередній період (повна зупинка). Що відбувається, коли я ним користуюся?

Я читаю: .SDце data.tableмістить підмножину xданих s для кожної групи, за винятком стовпців (груп). Він може бути використаний при групуванні i, при групуванні, за допомогою byклавіш byта _ad hoc_by

Чи означає це, що дочка data.tableзберігається в пам'яті для наступної операції?


30
?data.tableбуло покращено в v1.7.10, завдяки цьому питанню. Тепер це пояснює назву .SDвідповідно до прийнятої відповіді.
Метт Даул

Відповіді:


205

.SDозначає щось на зразок " Subset of Data.table". Немає значення для початкового ".", за винятком того, що це робить ще більш малоймовірним виникнення зіткнення з визначеним користувачем іменем стовпця.

Якщо це ваш data.table:

DT = data.table(x=rep(c("a","b","c"),each=2), y=c(1,3), v=1:6)
setkey(DT, y)
DT
#    x y v
# 1: a 1 1
# 2: b 1 3
# 3: c 1 5
# 4: a 3 2
# 5: b 3 4
# 6: c 3 6

Це може допомогти вам зрозуміти, що .SDтаке:

DT[ , .SD[ , paste(x, v, sep="", collapse="_")], by=y]
#    y       V1
# 1: 1 a1_b3_c5
# 2: 3 a2_b4_c6

В основному, by=yоператор розбиває вихідний таблицю даних на ці два під-data.tables

DT[ , print(.SD), by=y]
# <1st sub-data.table, called '.SD' while it's being operated on>
#    x v
# 1: a 1
# 2: b 3
# 3: c 5
# <2nd sub-data.table, ALSO called '.SD' while it's being operated on>
#    x v
# 1: a 2
# 2: b 4
# 3: c 6
# <final output, since print() doesn't return anything>
# Empty data.table (0 rows) of 1 col: y

і діє на них по черзі.

Поки він працює на будь-якому з них, він дозволяє вам звертатися до поточного суб- data.table, використовуючи прізвисько / ручку / символ .SD. Це дуже зручно, оскільки ви можете отримувати доступ до та керувати стовпцями так само, як ніби ви сиділи в командному рядку, працюючи з єдиним таблицею даних, що називається .SD... крім цього тут, data.tableвиконуватимуть ці операції на кожному під- data.tableвизначеному комбінації клавіш, "склеювання" їх разом і повернення результатів в єдине data.table!


12
Так, саме. Ще один спосіб бачити .SDце DT[,print(.SD),by=y].
Метт Даул

6
@MatthewDowle - поки ти тут, питання до тебе. Виконання DT[,print(.SD[,y]),by=y]вказує на те, що я маю доступ до значення y, хоча воно не є частиною .SD. Звідки значення yмасштабу? Чи доступно це / б це поточне значення by?
Джош О'Браєн

8
@Josh Nice. Так. Це .SD[,y]регулярний data.tableпідмножина, тому що yне є його стовпцем .SDу середовищі, яке його викликало, що в цьому випадку є jсередовищем ( DTзапиту), де доступні byзмінні. Якщо його там не знайти, він виглядає в батьківському, його батьківському і так далі звичайним способом R. (Ну, і через приєднану спадкову область, яка також не використовується в цих прикладах, оскільки немає is).
Метт Даул

1
@Josh FAQ 2.8 має про це більше, можливо, знадобиться вдосконалення (пропозиції вітаються).
Метт Dowle

3
@Josh Оскільки змінні групи доступні у, теж, і довжина 1. by=list(x,y,z)означатиме x, yі zдоступні j. Для загального доступу вони також загорнуті .BY. Поширені запитання 2.10 мають певну історію, але до неї можна додати ясність ?data.table. Чудово, доку допомога буде дуже вітається. Ще краще, якщо ви хочете приєднатися до проекту та змінитись безпосередньо.
Метт Даул

98

Редагувати:

З огляду на те, наскільки добре отримана ця відповідь, я перетворив її на віньєтку пакету, доступну тут


Зважаючи на те, як часто це трапляється, я думаю, що це заслуговує на трохи більше викладів, окрім корисної відповіді, яку Дав Джош О'Брайен вище.

У доповненні до 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 інтерпретувати це як назви стовпців, а не намагатися присвоїти ім'я 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 моделі

Варіантна характеристика моделі є основною ознакою надійного статистичного аналізу. Давайте спробуємо передбачити ЕРА пітчера (зароблений середній запуск, показник продуктивності), використовуючи невеликий набір коваріатів, наявний у 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)

підходить коефіцієнт OLS на Вт, різні технічні характеристики

Коефіцієнт завжди має очікувану ознаку (кращі глечики, як правило, мають більше виграшів і дозволено менше пробігів), але величина може суттєво відрізнятися залежно від того, що ми ще контролюємо.

Умовні приєднання

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!


5
Гарне пояснення. Лише один коментар: Замість Teams [, .SD [that.max (R)], by = teamID], ви можете скористатися функцією швидкого замовлення data.table: через Teams [order (teamID, -R), .SD [ 1L], keyby = teamID], це має бути швидше.
bartleby

@bartleby дякую, це дійсно правильно, але загальний підхід все-таки корисно знати - наприклад, якщо нам потрібно замовити кількість, залежне від групи. можливо, я поміняю приклад.
MichaelChirico

3

Я зробив відео про це після розмови з Меттом Даулем про .SD, ви можете побачити це на YouTube: https://www.youtube.com/watch?v=DwEzQuYfMsI


2
На жаль, переповнення стека зазвичай не вітає відповідей, які по суті складаються із зовнішніх посилань. Можливо, ви хочете відредагувати більше тексту та інформації про код у ньому? Фій Метт додав ваше відео до вікі: github.com/Rdatatable/data.table/wiki/Презентації
Франк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.