在data.table中对每个组随机抽取样本行

41

你如何使用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
5个回答

63

可能是这样的吗?

> 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进行以下的纠正。)


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

8
我认为 joran 的答案可以进一步概括。细节在这里(如何在 data.table 中对组进行采样并带有附加条件)但我认为这个解决方案考虑了没有“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

4
这个问题的答案有两个微妙的考虑因素,Josh O'Brien和Valentin在评论中提到了这些。第一个是通过.SD 进行子集划分非常低效,最好直接采样.I (请参见下面的基准测试)。
第二个考虑因素是,如果我们从.I进行采样,那么当 .I > 1length(.I) = 1时,调用 sample(.I, size = 1)会导致意外的行为。在这种情况下,sample() 的行为就像我们调用了 sample(1:.I, size = 1),这肯定不是我们想要的。正如Valentin所指出的,最好在这种情况下使用结构.I[sample(.N, size = 1)]
作为一个基准测试,我们构建了一个简单的1,000 x 1数据表,并随机对每个组进行了抽样。即使对于如此小的数据表,.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 包(版本为 v0.3.0)于 2020 年 12 月 02 日创建。


2

受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


如何保持超过2列的布局? - Union find
@Unionfind 你能提一个新问题并举个例子说明你想要做什么吗? - SymbolixAU
https://dev59.com/a2wUookBPGXWitrZChrx?noredirect=1#comment135135888_76634150 - Union find

0

分层抽样 > 过采样

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)]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接