.SD在R中的data.table中代表什么


172

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

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

这是否意味着女儿data.tables被保存在存储器中以进行下一个操作?


30
?data.table由于此问题,在v1.7.10中进行了改进。现在,它.SD按照接受的答案说明名称。
Matt Dowle 2012年

Answers:


205

.SD代表“ Sataset.ubset ”之类的D东西。初始名称没有任何意义".",除了它使与用户定义的列名称发生冲突的可能性更加小外。

如果这是您的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使用nickname / handle / symbol来引用当前子目录.SD。这非常方便,因为您可以访问列并对其进行操作,就像您坐在命令行中使用单个data.table(称为.SD...)一样,只是在这里,data.table它将对data.table由组合键,将它们“粘贴”在一起,然后一次返回结果data.table


12
对,就是这样。另一种看待方式.SDDT[,print(.SD),by=y]
马特·道尔

6
@MatthewDowle-当您在这里时,您会遇到一个问题。这样做DT[,print(.SD[,y]),by=y],表示y即使它不是的一部分,我也可以使用的值.SDy范围的价值从何而来?b / c是当前值by吗?
乔什·奥布莱恩

8
@乔什尼斯。是。本.SD[,y]是一个普通的data.table子集,所以因为y不是列的.SD,它看起来在调用它的环境,在这种情况下是j(的环境中DT查询),其中的by变量是可用的。如果未在其中找到,它将以通常的R方式查找父级及其父级,依此类推。(同样,也可以通过join继承的作用域,因为没有,所以在这些示例中未使用它i)。
马特·道尔

1
@Josh常见问题解答2.8对此有更多说明,可能需要改进(欢迎提出建议)。
马特·道尔

3
@Josh因为组变量j中可用,也和长度都为1,by=list(x,y,z)将意味着xyz可用来j。对于通用访问,它们也包含.BY在内。常见问题解答2.10具有一些历史记录,但是可以添加一些清晰度?data.table。很好,docu帮助将非常受欢迎。如果您想加入项目并直接进行更改,那就更好了。
马特·道尔

97

编辑:

鉴于此答案的受欢迎程度,我将其转换为可在此处使用的小插图包装


考虑到这种情况的发生频率,我认为除了上面乔什·奥布莱恩(Josh O'Brien)给出的有帮助的答案之外,还有其他更多的阐述。

除了小号的的ubset 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,即,这是一种过于冗长的编写方式,PitchingPitching[]

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不能自动读取DatePOSIXct,并且character/ factor/ 之间来回转换numeric是很常见的。我们可以使用.SD.SDcols批量转换此类列的组。

我们注意到,以下列的存储方式characterTeams数据集相同:

# 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将其解释为列名,而不是尝试将名称分配fkt给RHS。

的灵活性.SDcols(和:=)接受character载体integer列的位置的载体也能派上用场为列名的基于模式的转换*。我们可以将所有factor列转换为character

fkt_idx = which(sapply(Teams, is.factor))
Teams[ , (fkt_idx) := lapply(.SD, as.character), .SDcols = fkt_idx]

然后将所有包含的列转换teamfactor

team_idx = grep('team', names(Teams), value = TRUE)
Teams[ , (team_idx) := lapply(.SD, factor), .SDcols = team_idx]

** 显式使用列号(例如DT[ , (1) := rnorm(.N)])是一种不好的做法,并且如果列位置发生变化,随着时间的流逝,它可能导致代码无声地损坏。如果我们不对创建数字索引和使用数字索引的顺序进行明智/严格的控制,那么即使隐式使用数字也可能很危险。

控制模型的RHS

多样的模型规格是强大的统计分析的核心特征。让我们尝试使用Pitching表格中可用的一小组协变量来预测投手的ERA(获胜平均成绩,一种性能指标)。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)

适合W上的OLS系数,各种规格

该系数始终具有预期的符号(更好的投手往往会获得更多的胜利,而允许的奔跑次数更少),但是幅度可能会因我们控制的其他因素而有很大差异。

有条件的加入

data.table语法的简单性和鲁棒性很漂亮。该语法可以x[i]灵活地处理两种常见的子集方法-当ilogical向量时,x[i]将返回与x其中i是对应的那些行TRUE;当i另一个data.table,一个join被执行(在平原形式,使用keyxi,否则,当on =被指定时,使用的那些列的匹配)。

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

这个例子有点人为,但是说明了这个想法。看到这里(12)为多。

目标是team_performancePitching表格中添加一列,以记录每支球队中最佳投手的球队表现(排名)(以最低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执行的操作。

这在多种设置中很有用,以下是其中最常见的设置:

组子集

让我们在拉曼数据中获取每个团队的最新数据季节。这可以很简单地完成:

# 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关联的最后一行的全部.SDteamID

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

群最优

假设我们想返回每个团队的最佳年份,以他们的总得分R来衡量(当然,我们可以轻松地将其调整为引用其他指标)。现在,我们可以动态定义所需的索引,而不是从每个子元素中获取固定的元素:data.table

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

请注意,此方法当然可以与结合使用,.SDcols以仅返回的data.table每个部分.SD.SDcols需在各个子集中固定的警告)

NB.SD[1L]目前已通过内部优化GForce另请参见)进行了优化,这些data.table内部结构可极大地加快最常见的分组操作(如summean-请参阅),?GForce以了解更多详细信息,并密切关注/表示对功能改进请求的支持,请对此方面进行更新:1 23456

分组回归

回到上文关于之间的关系的调查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')]

拟合系数的分布

尽管存在相当多的异质性,但在观察到的总体价值周围仍存在明显的集中度

希望这阐明了.SD在中促进美观,高效代码的力量data.table


5
很好的解释。只需评论一下:您可以利用Teams [order(teamID,-R),.SD [ 1L],keyby = teamID],这应该更快。
bartleby

@bartleby,谢谢,的确是正确的,但是一般的方法还是很有用的-例如,如果我们需要根据与组有关的数量进行订购。也许我会改变这个例子。
MichaelChirico

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.