SD 在 data.table 中代表什么

.SD看起来很有用,但是我真的不知道我在用它做什么。它代表什么?为什么有一个前期(句号)。当我使用它时会发生什么?

我读到: .SD是一个包含每个组的 x数据子集的 data.table,不包括组列。它可以在按 i分组时使用,在按 by、键控 by和 _ ad hoc _ by分组时使用

这是否意味着女儿 data.tables 被保存在内存中,以备下一次手术使用?

76004 次浏览

.SD代表类似于“ Data.table 的 Subset”的东西。初始 "."没有什么意义,只是它使得与用户定义的列名冲突的可能性更小。

如果这是你的 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.table 分解为这两个子 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

然后轮流给他们做手术。

当它在其中一个上运行时,它允许您通过使用 nick-name/handle/符号 .SD来引用当前的子 data.table。这非常方便,因为你可以访问和操作这些列,就像你坐在命令行处理一个名为 .SD的单个 data.table 一样... ... 除了这里,data.table将在每个由键的组合定义的子 data.table上执行这些操作,“粘贴”它们回来,并返回一个单独的 data.table中的结果!

编辑:

考虑到这个答案是多么受欢迎,我已经将它转换成了一个现在可用的 给你包小插图


考虑到这种情况出现的频率,我认为除了上面 Josh O’Brien 给出的有用的答案之外,还需要进行更多的阐述。

除了通常由 Josh 引用/创造的 Data 缩略语的 是的子集,我认为考虑用“ S”代表“ Selfsame”或“ Self-reference”也是有帮助的—— .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,也就是说,这是一种过于冗长的写 PitchingPitching[]的方式:

identical(Pitching, Pitching[ , .SD])
# [1] TRUE

就子集而言,.SD仍然是数据的子集,它只是一个微不足道的子集(集本身)。

列子设置: .SDcols

影响 .SD的第一个方法是使用 .SDcols参数将 .SD中包含的 柱子限制为 [:

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

这只是为了说明,而且很无聊。但是,即使是这种简单的使用方式,也适用于各种高度有益/无处不在的数据操纵操作:

列类型转换

列类型转换是数据挖掘的现实——在本文撰写之时,ABC0不能自动读取 ABC1或 POSIXctcharacter/factor/numeric之间的来回转换是常见的。我们可以使用 .SD.SDcols批量转换这些列的组。

我们注意到,在 Teams数据集中,以下列被存储为 character:

# 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/lapplyFUN应用于每个 data.frame1,并返回 sapply/lapply通常会返回的结果(在这里,FUN == is.character返回长度为1的 logical,所以 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表中可用的一小组协变量来预测投手的 ERA (Earned Runs Average,一种绩效指标)。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, depicted as bars with distinct colors.

该系数总是有预期的迹象(更好的投手往往有更多的胜利和更少的运行允许) ,但幅度可以在很大程度上取决于我们控制的其他什么。

有条件连结

data.table语法因其简单性和健壮性而优美。语法 x[i]灵活地处理两种常见的子设置方法——当 ilogical向量时,x[i]将返回对应于 iTRUE的那些 x行; 当 ix[i]5时,执行 x[i]0(以普通形式,使用 xix[i]1s,否则,当指定 x[i]4时,使用这些列的匹配)。

这在一般情况下是很好的,但是在我们希望执行 条件连接时就不够了,在 条件连接中,表之间关系的确切性质取决于一列或多列中行的某些特征。

这个示例有点做作,但是说明了这个想法; 请参阅这里(12)了解更多信息。

我们的目标是在 Pitching表格中添加一列 team_performance,记录每支球队中最佳投手的表现(排名)(以最低 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)值,这就是为什么 .SDTeams[.SD]的右边(因为在这种情况下,:=的 RHS 需要 nrow(Pitching[rank_in_team == 1])值)。

分组 .SD操作

通常,我们希望对数据 在团队层面执行一些操作。当我们指定 by =(或者 keyby = )时,当 data.table处理 j时会发生什么的心理模型是想象你的 data.table被分成许多组件的子 data.table,每个子 data.table对应于你的 by变量的一个单一值:

A visual depiction of how grouping works. on the left is a grid. The first column is titled "ID COLUMN" with values the capital letters A through G, and the rest of the data is unlabelled, but is in a darker color and simply has "Data" written to indicate that's arbitrary. A right arrow shows how this data is split into groups. Each capital letter A through G has a grid on the right-hand side; the grid on the left has been subdivided to create that on the right.

在这种情况下,.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是指一个组中的总行数(它等于每个组中的 nrow(.SD)) ,所以 .SD[.N]返回与每个 teamID关联的最后一行的 整个 .SD

另一个常见的版本是使用 .SD[1L]来代替每个组的 第一观察。

最佳组合

假设我们希望返回每个团队的 最好的年,这是通过他们的总得分数来衡量的(R; 当然,我们可以很容易地调整这个值以参考其他指标)。现在,我们不再从每个子 data.table获取一个 修好了元素,而是按照以下方式定义所需的索引 动态的:

Teams[ , .SD[which.max(R)], by = teamID]

请注意,这种方法当然可以与 .SDcols结合起来,为每个 .SD只返回 data.table的一部分(但需要注意的是,.SDcols应该固定在各个子集之间)

注意: .SD[1L]目前由 < em > GForce (参见)、 data.table内部结构进行了优化,这些结构大大加快了 summean等最常见的分组操作的速度——更多细节请参见 ?GForce,关注/语音支持功能改进请求,以便在这方面进行更新: 12GForce0、 GForce1、 GForce2,“ a href = “ https://github.com/Rdattable/data.table/questions/1414”rel = “ noReferrer”> 6

分组回归

回到上面关于 ERAW之间关系的问题,假设我们预计这种关系会因团队而不同(即,每个团队有不同的斜率)。我们可以很容易地重新运行这个回归来探索这种关系中的异质性,如下所示(注意,这种方法的标准错误通常是不正确的——规范 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')]

A histogram depicting the distribution of fitted coefficients. It is vaguely bell-shaped and concentrated around -.2

虽然存在相当数量的异质性,但在观察到的总体价值周围有一个明显的集中

希望这已经阐明了 .SD在促进美丽,高效的代码在 data.table的力量!

在和马特 · 道尔讨论.SD 之后,我做了一个关于这个的视频,你可以在 YouTube 上看到: https://www.youtube.com/watch?v=DwEzQuYfMsI