如何在data.table中按组随机标记指定数量的行?
这个问题的核心是你混淆了全局行号(.I)和组内位置索引,而且当sample()返回多个值时,直接用==做逻辑判断会因为长度不匹配导致结果不符合预期。咱们一步步来解决它:
首先明确需求:每个C1分组里随机标记最多N行(如果组内行数少于N,就标记所有行)。
正确的解决方案
咱们用组内位置索引来处理,这样能保证每个分组内的逻辑判断长度完全匹配,不会出问题。
方法1:分组内直接生成标记逻辑
这是最高效的方式,直接在每个分组里生成对应行的标记:
set.seed(1) dt = data.table( C1 = c("A","A","A","B","C","C","C","D","D","D"), C2 = c(2,1,3,1,2,3,4,5,4,5) ) # 定义要标记的数量 N <- 2 dt[, C3 := seq_len(.N) %in% sample(.N, min(.N, N)), by = C1]
运行后查看结果:
dt # C1 C2 C3 # 1: A 2 TRUE # 2: A 1 FALSE # 3: A 3 TRUE # 4: B 1 TRUE # 组B只有1行,正确标记 # 5: C 2 FALSE # 6: C 3 TRUE # 7: C 4 TRUE # 8: D 5 TRUE # 9: D 4 FALSE #10: D 5 TRUE
方法2:先收集选中的全局行号再标记
如果你需要单独获取选中的行号,也可以先收集所有符合条件的全局行索引,再统一标记:
selected_rows <- dt[, .(selected = .I[sample(.N, min(.N, N))]), by = C1]$selected dt[, C3 := .I %in% selected_rows]
这个方法和方法1结果完全一致,适合需要对选中行做额外处理的场景。
为什么之前的方法失效?
咱们来拆解你之前的问题:
- 用
.I == sample(.I, ...)的问题:当sample()返回多个值时,比如组A返回c(1,3),.I是全局行号(组A的.I是1,2,3),此时1 == c(1,3)会返回c(TRUE, FALSE),2 == c(1,3)返回c(FALSE, FALSE),3 == c(1,3)返回c(FALSE, TRUE)——最终会得到一个长度为6的逻辑向量,而组A只有3行,data.table无法正确匹配,导致结果混乱。 - 组B未被标记的问题:你之前用
dt[, C3 := .I == sample(.I, 1), by = C1]时,组B的.I是4,但由于随机状态的影响(前面的分组已经消耗了随机数),sample(4,1)返回的不是4,所以4 == 非4得到FALSE。而用组内位置sample(.N, 1)时,组B的.N=1,sample(1,1)必然返回1,所以组内唯一行的seq_len(.N) == 1会得到TRUE,完美解决这个问题。
关键要点
- 优先用组内位置索引(
seq_len(.N))代替全局行号.I做分组内的逻辑判断,避免长度不匹配。 - 用
min(.N, N)确保当组内行数不足N时,自动标记所有行,不用额外写判断逻辑。
内容的提问来源于stack exchange,提问作者Serhii
相关产品推荐
相关产品推荐

