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

Far*_*rel 159 r data.table

.SD看起来很有用,但我真的不知道我在做什么.它代表什么?为什么会有前一段时间(句号).我用它时发生了什么?

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

这是否意味着女儿data.table被留在内存中进行下一次操作?

Jos*_*ien 192

.SD代表" Suaset of Data.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
Run Code Online (Sandbox Code Playgroud)

这样做可能会帮助您了解以下内容.SD:

DT[ , .SD[ , paste(x, v, sep="", collapse="_")], by=y]
#    y       V1
# 1: 1 a1_b3_c5
# 2: 3 a2_b4_c6
Run Code Online (Sandbox Code Playgroud)

基本上,该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
Run Code Online (Sandbox Code Playgroud)

并依次对它们进行操作.

当它在任何一个上运行时,它允许您data.table使用昵称/句柄/符号来引用当前子.SD.这非常方便,因为您可以访问和操作列,就像您在命令行中使用单个data.table一样调用.SD...除了在这里,data.table将在每个子data.table定义的子列表上执行密钥的组合,将它们"粘贴"在一起并将结果返回到单个data.table!

  • 对,就是这样.另一种看到`.SD`的方法是`DT [,print(.SD),by = y]`. (11认同)
  • @Josh很好.是.`.SD [,y]`是一个常规的`data.table`子集,因为`y`不是`.SD`的列,它在调用它的环境中查找,在这种情况下是`j `环境(`DT`查询)``by`变量可用.如果在那里找不到它,它会以通常的R方式查找父项及其父项等.(好吧,通过连接继承范围,也没有在这些例子中使用,因为没有`i`s). (7认同)
  • @MatthewDowle - 当你在这里时,给你一个问题.执行`DT [,print(.SD [,y]),by = y]`表示我可以访问`y`的值,即使它不是`.SD`的一部分."y"的范围从哪里开始?它是否可用b/c它是`by`的当前值? (6认同)
  • @Josh因为组变量在j中也是可用的,并且长度为1.`by = list(x,y,z)`表示`x`,`y`和`z`可用于`j`.对于通用访问,它们也包含在`.BY`中.常见问题2.10有一些历史,但可以在`?data.table`中添加一些清晰度.伟大的,文档帮助将是非常受欢迎的.如果你想加入项目并直接改变,那就更好了. (3认同)

Mic*_*ico 83

考虑到这种情况经常发生,我认为除了Josh O'Brien给出的有用答案之外,还需要更多的阐述.

除了通常由Josh引用/创建的D ata首字母缩略词的S ubset 之外,我认为将"S"代表"Selfsame"或"Self-reference"也是有帮助的 - 这是最基本的幌子自反引用到自己-我们将在下面的例子中看到,这是一个链接"查询"(提取/亚/使用等一起特别有用).特别是,这也意味着是本身(需要提醒的是它不允许与分配)..SDdata.table[.SDdata.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
Run Code Online (Sandbox Code Playgroud)

.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
Run Code Online (Sandbox Code Playgroud)

也就是说,我们刚刚回来Pitching,也就是说,这是一种过于冗长的写作方式,Pitching或者Pitching[]:

identical(Pitching, Pitching[ , .SD])
# [1] TRUE
Run Code Online (Sandbox Code Playgroud)

在子集方面,.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
Run Code Online (Sandbox Code Playgroud)

这只是为了说明而且非常无聊.但即便如此,这种简单的用法也适用于各种高度有益/无处不在的数据操作操作:

列类型转换

列类型转换是数据重写的生命现实 - 在撰写本文时,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 
Run Code Online (Sandbox Code Playgroud)

如果您对使用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
Run Code Online (Sandbox Code Playgroud)

理解这种语法的关键是回忆一下data.table(以及a data.frame)可以被认为是list每个元素都是列的位置 - 因此,sapply/ lapply适用FUN于每一并返回结果sapply/ lapply通常会(在这里FUN == is.character返回一个logical长度为1,因此sapply返回一个向量).

将这些列转换factor为非常相似的语法- 只需添加:=赋值运算符即可

Teams[ , (fkt) := lapply(.SD, factor), .SDcols = fkt]
Run Code Online (Sandbox Code Playgroud)

请注意,我们必须用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]
Run Code Online (Sandbox Code Playgroud)

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

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

**明确使用列号(例如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)
Run Code Online (Sandbox Code Playgroud)

适用于W的OLS系数,各种规格

系数始终具有预期的符号(更好的投手倾向于拥有更多的胜利和更少的跑步),但是幅度可以根据我们控制的其他内容而有很大差异.

有条件的加入

data.table语法因其简单性和健壮性而美观.语法x[i]灵活处理到子集两种常见的方法-当i是一个logical向量,x[i]将返回的那些行x对应于其中iTRUE; 如果i另一个data.table,join则执行a(以普通形式,使用keys xi,否则,何时on =指定,使用这些列的匹配).

这通常是很好的,但是当我们希望执行条件连接时不足,其中表之间的关系的确切性质取决于一列或多列中的行的一些特性.

这个例子有点人为,但说明了这个想法; 看到这里(1,2)为多.

目标是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)]]
Run Code Online (Sandbox Code Playgroud)

请注意,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]
Run Code Online (Sandbox Code Playgroud)

回想一下,.SD它本身就是一个data.table,它.N指的是一个组中的总行数(它等于nrow(.SD)每个组中的行),因此.SD[.N]返回与每个行相关的最后一行的全部内容.SDteamID.

另一个常见的版本是使用.SD[1L]相反来获得每个组的第一个观察.

Group Optima

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

Teams[ , .SD[which.max(R)], by = teamID]
Run Code Online (Sandbox Code Playgroud)

请注意,这种方法当然可以结合使用.SDcols,只返回data.table每个部分.SD(具有.SDcols应该在各个子集上修复的警告)

:.SD[1L]目前由优化GForce(另见),data.table内部大量其加快最常见的组合操作,如summean-看到?GForce更多细节,并保持眼睛/语音支持的在这方面的更新功能改进请求:1,2,3,4,5,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')]
Run Code Online (Sandbox Code Playgroud)

拟合系数的分布

虽然存在相当多的异质性,但在观察到的整体价值方面存在明显的集中

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

  • 很好的解释.只需一条评论:代替团队[,.SD [which.max(R)],by = teamID],您可以利用快速data.table订单功能:通过团队[order(teamID,-R),. SD [ 1L],keyby = teamID],这应该更快. (5认同)

Sha*_*ron 5

在与 Matt Dowle 谈论 .SD 之后,我做了一个关于此的视频,你可以在 YouTube 上看到它:https : //www.youtube.com/watch?v= DwEzQuYfMsI

  • 不幸的是,Stack Overflow 通常不欢迎主要由外部链接组成的答案。也许您想在其中编辑更多文本和代码信息?Fyi Matt 已将您的视频添加到 wiki:https://github.com/Rdatatable/data.table/wiki/Presentations (2认同)