Что означает .SD в data.table в R - PullRequest
       39

Что означает .SD в data.table в R

154 голосов
/ 14 декабря 2011

.SD выглядит полезным, но я не совсем понимаю, что я с ним делаю.Что это значит?Почему существует предыдущий период (полная остановка).Что происходит, когда я его использую?

Я прочитал: .SD - это data.table, содержащий подмножество данных x для каждой группы, исключая столбцы столбцов группы.Его можно использовать при группировке по i, при группировке по by, с ключом by и _ad hoc_ by

Означает ли это, что дочерняя запись data.table s удерживаетсяв памяти для следующей операции?

Ответы [ 2 ]

185 голосов
/ 14 декабря 2011

.SD означает что-то вроде "S ubset of D ata.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, используя псевдоним / handle / symbol .SD. Это очень удобно, поскольку вы можете обращаться к столбцам и работать с ними так же, как если бы вы сидели в командной строке и работали с одной таблицей данных с именем .SD ... за исключением того, что здесь data.table будет выполнять эти операции над каждый субэлемент data.table, определенный комбинациями клавиш, "склеивает" их вместе и возвращает результаты в одном data.table!

74 голосов
/ 21 ноября 2017

Учитывая, как часто это происходит, я думаю, что это заслуживает немного большего изложения, помимо полезного ответа, данного Джошем О'Брайеном выше.

В дополнение к S убсетуаббревиатура D ata, обычно цитируемая / созданная Джошем, я думаю, что также полезно рассмотреть букву «S», обозначающую «Само собой» или «Самоссылка» - .SD в самом основномПриведите рефлексивную ссылку к самому data.table - как мы увидим в примерах ниже, это особенно полезно для объединения воедино «запросов» (извлечения / подмножества / и т.д., используя [).В частности, это также означает, что .SD сам по себе является data.table (с оговоркой, что он не позволяет присваивать с :=).

Упрощенное использование .SD для поднабора столбцов (т. е. когда указано .SDcols);Я думаю, что эту версию гораздо проще понять, поэтому сначала мы рассмотрим ее ниже.Интерпретация .SD во втором его использовании, сценарии группирования (т. Е. Когда задано by = или keyby =), концептуально немного отличается (хотя по сути это то же самое, поскольку, в конце концов, не группируетсяоперация - крайний случай группировки только с одной группой).


Вот несколько иллюстративных примеров и некоторые другие примеры использования, которые я сам часто использую:

Загрузка данных Лахмана

Чтобы придать этому более реалистичный видВместо того, чтобы составлять данные, давайте загрузим некоторые наборы данных о бейсболе из 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 возвращает logical длины 1, поэтому sapply возвращает вектор).

Синтаксис для преобразования этихстолбцы к factor очень похожи - просто добавьте оператор присваивания :=

Teams[ , (fkt) := lapply(.SD, factor), .SDcols = fkt]

Обратите внимание, что мы должны заключить fkt в скобки (), чтобы заставить R интерпретировать это как имена столбцов,вместо того, чтобы пытаться присвоить RHS имя fkt.

Гибкость .SDcols:=) для принятия character vector или и 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 питчера (Earned Runs Average, показатель производительности), используя небольшой набор ковариат, доступных в таблице 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)

fit OLS coefficient on W, various specifications

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

Условные объединения * Синтаксис

data.table прекрасен своей простотой и надежностью.Синтаксис x[i] гибко обрабатывает два распространенных подхода к подмножеству - когда i является вектором logical, x[i] возвращает те строки x, которые соответствуют где i равно TRUE;когда i равен , другой data.table, выполняется join (в простом виде, используя key s 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 переменной (переменных):

grouping illustrated

В этом случае .SD является множественным по природе - оно относится к каждому из этих под data.table с, по одному (слегкаТочнее, область действия .SD - это единица (data.table).Это позволяет нам кратко выразить операцию, которую мы хотели бы выполнить для каждого подпункта data.table до того, как нам будет возвращен пересобранный результат.

Это полезно вРазнообразие настроек, наиболее распространенные из которых представлены здесь:

Групповое поднаборы

Давайте получим самые последние данные сезона для каждой команды в данных Лахмана.Это можно сделать довольно просто с помощью:

# 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 относится к общему количеству строк в группе (оно равно * 1237).* внутри каждой группы), поэтому .SD[.N] возвращает полное значение .SD для последней строки, связанной с каждым teamID.

Другой распространенной версией этого является использование .SD[1L] вместо этого, чтобы получить первое наблюдение для каждой группы.

Группа Optima

Предположим, что мы хотим вернуть лучший год для каждой команды, измеренный по их общему количеству забитых запусков (R; конечно, мы могли бы легко откорректировать это для ссылки на другие метрики).Вместо того чтобы брать элемент fixed из каждого подэлемента 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')]

distribution of fitted coefficients

Несмотря на то, что существует значительная степень неоднородности, существует четкая концентрация вокруг наблюдаемогообщая ценность

Надеюсь, это прояснило силу .SD в создании красивого и эффективного кода в data.table!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...