首页 文章

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

提问于
浏览
136

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

我读到: .SDdata.table ,其中包含每组的 x 数据的子集,不包括组列 . 当按 i 分组时,可以使用它,当按 by 分组,键入 by 和_ad hoc_ by

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

2 回答

  • 163

    .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
    

    这样做可能对你有帮助 see 是什么 .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
    

    并依次对它们进行操作 .

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

  • 51

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

    除了通常由Josh引用/创建的 D ata首字母缩略词的 S ubset之外,我认为将"S"代表"Selfsame"或"Self-reference" - .SD 也是有帮助的,其最基本的形式是对 data.table 本身的反身性引用 - - 正如我们在下面的示例中看到的,这对于将"queries"(使用 [ 的提取/子集/等)链接在一起特别有用 . 特别是,这也意味着 .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
    

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

    列类型转换

    列类型转换对于数据修改是生命中的事实 - 在撰写本文时,fwrite cannot automatically read Date or POSIXct columns,并且在 character / 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 应用于每一列并将结果返回为 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 表中提供的一小组协变量,让's try and predict a pitcher' s ERA(获得的运行平均值,衡量性能) . W (wins)和 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

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

    有条件的连接

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

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

    这个例子有点人为,但说明了这个想法;请参阅此处(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 ,每个组件对应于 by 变量的单个值(s ):

    grouping illustrated

    在这种情况下, .SD 本质上是多个 - 它指的是这些子中的每一个,一次一个(稍微更准确地说, .SD 的范围是单个子 data.table ) . 这使我们能够在重新组装结果返回给我们之前简明地表达我们想要在每个子_724502上执行的操作 .

    这在各种设置中都很有用,其中最常见的是:

    组子集

    让我们在拉赫曼数据中获得每个团队最新的数据季节 . 这可以通过以下方式完成:

    # 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] 代替获得每个组的第一个观察 .

    Group Optima

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

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

    请注意,这种方法当然可以与 .SDcols 结合使用,以便仅为每个 .SD 返回部分 data.table (需要注意的是 .SDcols 应该在各个子集中修复)

    注意: .SD[1L] 目前由GForcesee also), data.table 内部优化,大大加快了最常见的分组操作,如 summean - 有关详细信息,请参阅 ?GForce 并密切关注/语音支持功能改进请求更新此问题前:123456

    分组回归

    回到上面关于 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')]
    

    distribution of fitted coefficients

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

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

相关问题