.SD
看起来很有用,但我真的不知道我在用它做什么。它代表什么?为什么会有前一段(句号)。当我使用它时会发生什么?
我读到:
.SD
是一个data.table
包含x
每个组的数据子集,不包括组列。可用于分组i
时、分组时by
、键控by
和 _ad hoc_by
这是否意味着女儿data.table
s 被保存在内存中以供下一次操作使用?
.SD
看起来很有用,但我真的不知道我在用它做什么。它代表什么?为什么会有前一段(句号)。当我使用它时会发生什么?
我读到:
.SD
是一个data.table
包含x
每个组的数据子集,不包括组列。可用于分组i
时、分组时by
、键控by
和 _ad hoc_by
这是否意味着女儿data.table
s 被保存在内存中以供下一次操作使用?
.SD
代表“ S
ata.table 的D
ubset”之类的东西。初始 没有任何意义"."
,只是它更不可能与用户定义的列名发生冲突。
如果这是您的 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
并依次对它们进行操作。
当它在任何一个上运行时,它允许您data.table
通过使用 nick-name/handle/symbol 来引用当前子.SD
。这非常方便,因为您可以访问和操作列,就像您坐在命令行上使用名为.SD
... 的单个 data.table 一样,除了这里,data.table
将对每个子data.table
定义的子执行这些操作键的组合,将它们“粘贴”在一起并将结果返回一个data.table
!
鉴于此答案广受欢迎,我已将其转换为现在可在此处获得的包小插图
考虑到这种情况出现的频率,我认为除了上面 Josh O'Brien 给出的有用答案之外,还需要进行更多说明。
除了通常由 Josh 引用/创建的Data首字母缩略词的子集之外,我认为考虑用“S”代表“Selfsame”或“Self-reference”也很有帮助——它最基本.SD
的形式是对自身的反身引用data.table
——正如我们将在下面的示例中看到的那样,这对于将“查询”链接在一起(使用 提取/子集/等[
)特别有用。特别是,这也意味着它.SD
本身就是 adata.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
此处的使用感到困惑,请注意它与 base 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
返回 alogical
长度为 1,因此sapply
返回一个向量)。
将这些列转换为的语法factor
非常相似——只需添加:=
赋值运算符
Teams[ , (fkt) := lapply(.SD, factor), .SDcols = fkt]
请注意,我们必须用fkt
括号括()
起来以强制 R 将其解释为列名,而不是尝试将名称分配给fkt
RHS。
.SDcols
(and :=
) 接受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)]
)是不好的做法,如果列位置发生变化,可能会随着时间的推移导致代码静默损坏。如果我们不对创建编号索引和使用它的顺序进行智能/严格控制,即使隐式使用数字也会很危险。
变化的模型规格是稳健统计分析的核心特征。让我们尝试使用表中可用的一小组协变量来预测投手的 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
向量时,将返回与 where is对应的x[i]
那些行;when是another,执行 a (以普通形式,使用 and 的s ,否则,使用这些列的匹配项指定 when )。x
i
TRUE
i
data.table
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
它本身就是 a data.table
,它.N
指的是组中的总行数(它等于nrow(.SD)
每个组中的行数),因此.SD[.N]
返回与 each 关联的最后一行的整体.SD
teamID
。
另一个常见的版本是改为使用.SD[1L]
来获取每个组的第一个观察结果。
假设我们想要返回每支球队最好的一年,以他们的总得分R
来衡量(当然,我们可以很容易地调整它以参考其他指标)。我们现在动态定义所需的索引,而不是从每个 sub-中获取固定元素,如下所示:data.table
Teams[ , .SD[which.max(R)], by = teamID]
请注意,这种方法当然可以结合使用.SDcols
,只返回data.table
for each.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
!
在与 Matt Dowle 讨论 .SD 后,我制作了一个视频,您可以在 YouTube 上看到它:https ://www.youtube.com/watch?v=DwEzQuYfMsI