【问题标题】:Sample random rows within each group in a data.table在 data.table 中的每个组中随机抽取行
【发布时间】:2013-04-23 17:47:16
【问题描述】:

您将如何使用 data.table 有效地对数据框中的每个组中的行进行采样?

DT = data.table(a = sample(1:2), b = sample(1:1000,20))
DT
    a   b
 1: 2 562
 2: 1 183
 3: 2 180
 4: 1 874
 5: 2 533
 6: 1  21
 7: 2  57
 8: 1  20
 9: 2  39
10: 1 948
11: 2 799
12: 1 893
13: 2 993
14: 1  69
15: 2 906
16: 1 347
17: 2 969
18: 1 130
19: 2 118
20: 1 732

我在想类似的东西:DT[ , sample(??, 3), by = a] 将为每个“a”返回三行样本(返回行的顺序不重要):

    a   b
 1: 2 180
 2: 2  57
 3: 2 799
 4: 1  69
 5: 1 347
 6: 1 732

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    有两个微妙的考虑因素会影响对这个问题的回答,Josh O'Brien 和 Valentin 在 cmets 中提到了这些因素。首先是通过.SD进行子集化效率非常低,最好直接对.I进行采样(见下面的benchmark)。

    第二个考虑因素,如果我们.I 采样,调用sample(.I, size = 1) 会导致.I > 1length(.I) = 1 出现意外行为。在这种情况下,sample() 的行为就像我们调用了sample(1:.I, size = 1),这肯定不是我们想要的。正如 Valentin 所说,在这种情况下,最好使用构造 .I[sample(.N, size = 1)]

    作为基准,我们构建了一个简单的 1,000 x 1 数据表,并在每组中随机抽样。即使使用这么小的 data.table,.I 方法也快大约 20 倍。

    library(microbenchmark)
    library(data.table)
    
    set.seed(1L)
    DT <- data.table(id = sample(1e3, 1e3, replace = TRUE))
    
    microbenchmark(
      `.I` = DT[DT[, .I[sample(.N, 1)], by = id][[2]]],
      `.SD` = DT[, .SD[sample(.N, 1)], by = id]
    )
    #> Unit: milliseconds
    #>  expr       min        lq     mean    median        uq       max neval
    #>    .I  2.396166  2.588275  3.22504  2.794152  3.118135  19.73236   100
    #>   .SD 55.798177 59.152000 63.72131 61.213650 64.205399 102.26781   100
    

    reprex package 创建于 2020-12-02 (v0.3.0)

    【讨论】:

      【解决方案2】:

      也许是这样的?

      > DT[,.SD[sample(.N, min(3,.N))],by = a]
         a   b
      1: 1 744
      2: 1 497
      3: 1 167
      4: 2 888
      5: 2 950
      6: 2 343
      

      (感谢 Josh 的更正,如下。)

      【讨论】:

      • 在这种情况下是相同的:DT[, sample(b, 3), by=a](名称除外)
      • @ChristopherManning -- 我只是在猜测,但是(如果它真的很重要),在某些情况下这可能会更快:DT[DT[, sample(.I, 3), by=a][[2]],]。 (sample(.I,3) 采样相对于DT 的行号)。此调用的优点是它不需要在处理调用时完全填充每个子集 .SD
      • 感谢您的帮助!我还发现可以使用if 语句根据a 的值有条件地更改每个组返回的样本数:DT[,.SD[sample(.N, if(a == 1) 2 else 3)],by = a]
      • @JoshO'Brien ,我正在尝试了解您的解决方案的某些行为。我认为在 .I 上应用 sample 时,分组只产生一行可能会产生意想不到的结果。在这种情况下,.I 仅包含一个整数(DT 中的行位置),sample 的行为与输入向量时不同。 @akrun 提议的 here 解决方案 DT[DT[ , .I[sample(.N,3)] , by = a]$V1] 可能会修复它。
      • @Valentine 好点。您的评论还让我注意到原始答案存在缺陷,对于少于三行的任何组都将失败。 (试试sample(2,3) 看看我的意思。)我现在已经修复了它,使用的修复可能也应该应用于您提出的解决方案。感谢您的评论!
      【解决方案3】:

      Stratified sampling > 过采样

      size=don[y==1,.(strata=length(iden)),by=.(y,x)] # count of iden by strata   
      table(don$x,don$y) 
      
      don<-merge(don,size[,.(y,strata)],by="x") #merge strata values  
      don_strata=don[,.SD[sample(.N,strata)],by=.(y,x)]
      

      【讨论】:

        【解决方案4】:

        this answer by David Arenburg 的启发,另一种避免.SD 分配的方法是对组进行抽样,然后使用.EACHI 加入原始数据

        DT[ DT[, sample(.N, 3), by=a], b[i.V1], on="a", by=.EACHI]
        
        #    a  V1
        # 1: 2  42
        # 2: 2 498
        # 3: 2 179
        # 4: 1 469
        # 5: 1  93
        # 6: 1 898
        

        DT[, sample(.N, 3), by=a] 行为我们提供了每个组的样本

        #         a V1
        # 1:      1  9
        # 2:      1  3
        # 3:      1  2
        # 4:      2  4
        # 5:      2  9
        # ---          
        

        所以我们可以使用V1 给我们它对应的b

        【讨论】:

          【解决方案5】:

          我相信 joran 的回答可以进一步概括。详细信息在这里 (How do you sample groups in a data.table with a caveat) 但我相信这个解决方案可以解决没有“3”行可供采样的情况。

          当前解决方案在尝试从公共值小于“x”的行中采样“x”次时会出错。在以下情况下,x=3。它考虑到了这一警告。 (由 nrussell 完成的解决方案)

          set.seed(123)
          ##
          DT <- data.table(
            a=c(1,1,1,1:15,1,1), 
            b=sample(1:1000,20))
          ##
          R> DT[,.SD[sample(.N,min(.N,3))],by = a]
               a   b
           1:  1 288
           2:  1 881
           3:  1 409
           4:  2 937
           5:  3  46
           6:  4 525
           7:  5 887
           8:  6 548
           9:  7 453
          10:  8 948
          11:  9 449
          12: 10 670
          13: 11 566
          14: 12 102
          15: 13 993
          16: 14 243
          17: 15  42
          

          【讨论】:

            猜你喜欢
            • 2019-12-28
            • 1970-01-01
            • 2022-01-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-09-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多