.SD
看起来很有用,但我真的不知道我在做什么。它代表什么?为什么会有前一段时间(句号)。我使用它时发生了什么?
我看了:
.SD
是data.table
,其中包含每个组x
的数据子集,不包括组列。在按i
分组时,按by
分组,键入by
和_ad hoc_ by
这是否意味着女儿data.table
被保留在内存中以进行下一次操作?
答案 0 :(得分:179)
.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.table分解为这两个子{ - 1}}
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
来引用当前子{ - 1}}。这非常方便,因为您可以访问和操作列,就像您在命令行中使用一个名为data.table
的data.table一样...除了在这里,.SD
将携带在由键组合定义的每个子.SD
上执行这些操作,将它们“粘贴”在一起并将结果返回到单个data.table
!
答案 1 :(得分:64)
考虑到这种情况发生的频率,我认为除了Josh O&amp; Brien上面给出的有用答案之外,还需要更多的阐述。
除了Josh通常引用/创建的 D ata首字母缩略词的 S ubset之外,我认为考虑&#34也有帮助; S&#34;代表&#34; Selfsame&#34;或&#34;自我参考&#34; - .SD
最基本的形式是{{1>}本身的自反参考 - 正如我们在下面的示例中看到的,这对于链接特别有用在一起&#34;查询&#34; (使用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
中包含的列:< / p>
[
这只是为了说明而且非常无聊。但即便如此,这种简单的用法也适用于各种高度有益/无处不在的数据操作操作:
列类型转换是数据变换的终身事实 - 截至撰写本文时,fwrite
cannot automatically read Date
or POSIXct
columns以及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
/ 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
感到困惑,请注意它与基础R sapply
相同:
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
返回结果(此处为{{ 1}}返回长度为1的lapply
,因此FUN == is.character
会返回一个向量)。
将这些列转换为logical
的语法非常相似 - 只需添加sapply
赋值运算符
factor
请注意,我们必须在括号:=
中包含Teams[ , (fkt) := lapply(.SD, factor), .SDcols = fkt]
以强制R将其解释为列名,而不是尝试将名称fkt
分配给RHS。
()
(和fkt
)接受.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]
表中可用的一小组协变量来预测投手的ERA(获得的运行平均值,性能指标)。 DT[ , (1) := rnorm(.N)]
(胜利)和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 {的x[i]
行{1}}是x
;当i
另一个TRUE
时,会执行i
(使用data.table
的{{1}}以简单形式执行join
,否则,在指定key
时,使用这些列的匹配项。)
这一般来说很好,但是当我们希望执行条件连接时,它就会失败,其中表之间关系的确切性质取决于一列或多列中行的某些特征。
这个例子有点人为,但说明了这个想法;请参阅此处(1,2)了解更多信息。
目标是在x
表格中添加一列i
,记录每支球队最佳投手的球队表现(排名)(以最低的ERA衡量,在至少有6个记录游戏的投手中。)
on =
请注意,team_performance
语法会返回Pitching
个值,这就是# 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)
的RHS案例需要.SD
个值。
Teams[.SD]
次操作通常,我们希望对群组级别的 执行某些操作。当我们指定:=
(或nrow(Pitching[rank_in_team == 1])
)时,.SD
处理by =
时所发生情况的心理模型是将您的keyby =
视为分裂为多个component sub - data.table
s,每个都对应j
个变量的单个值:
在这种情况下,data.table
本质上是多重的 - 它指的是每个子data.table
s,一次一个(略多一些)准确地说,by
的范围是单个子{ - 1}}。这使我们能够在重新组合结果返回给我们之前简明地表达我们想要在每个子.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]
衡量);我们可以轻松调整此项以引用其他指标,课程)。我们现在定义所需的索引动态,而不是从每个子.SD
中获取固定的元素,而不是如下:
teamID
请注意,此方法当然可以与.SD[1L]
结合使用,以便仅为每个R
返回data.table
的部分内容(注意Teams[ , .SD[which.max(R)], by = teamID]
应该修复各种子集)
NB :.SDcols
目前由GForce
(see also),data.table
内部进行了优化,大大加快了最常见的分组操作,如.SD
或.SDcols
- 有关详细信息,请参阅.SD[1L]
,并密切关注/语音支持此前端更新的功能改进请求:1,{{3} },2,3,4,5
回到上面关于data.table
和sum
之间关系的调查,假设我们希望这种关系因团队而异(即,每个团队的坡度不同)。我们可以轻松地重新运行这种回归来探索这种关系中的异质性如下(注意这种方法的标准误差通常是不正确的 - 规范mean
会更好 - 这种方法更容易阅读和系数是好的):
?GForce
虽然存在相当多的异质性,但在观察到的整体价值方面存在明显的集中度
希望这已经阐明了ERA
在W
中促进美观,高效的代码的力量!
答案 2 :(得分:1)
与Matt Dowle讨论过.SD之后,我制作了一个视频,可以在YouTube上观看:https://www.youtube.com/watch?v=DwEzQuYfMsI