【问题标题】:Efficient way to subset data.table based on value in any of selected columns [duplicate]根据任何选定列中的值对data.table进行子集化的有效方法[重复]
【发布时间】:2019-02-28 18:02:26
【问题描述】:

假设我有一个包含 6 列的 data.table

library(data.table)
set.seed(123)
dt <- data.table( id = 1:100,
                  p1 = sample(1:10, 100, replace = TRUE ),
                  p2 = sample(1:10, 100, replace = TRUE ),
                  p3 = sample(1:10, 100, replace = TRUE ),
                  p4 = sample(1:10, 100, replace = TRUE ),
                  p5 = sample(1:10, 100, replace = TRUE ) )

现在,我想在 p1 - pn 列(这里:p1-p5)上对这个 data.table 进行子集化。我想保留 any 的 p 列包含 10 值的所有行。

对于这个小样本data.table,这可以手动完成

test1 <- dt[ p1 == 10 | p2 == 10 | p3 == 10 | p4 == 10 | p5 == 10, ]

但是我的生产数据包含几十个 p 列,所以手动将它们全部输入会很痛苦......

我目前的解决方案是首先使用我需要的列名创建一个向量:

cols <- grep( "^p", names( dt ), value = TRUE )

...然后使用apply 进行子集化:

test2 <- dt[ apply( dt[, ..cols ], 1, function(r) any( r == 10 ) ), ]

检查:

identical(test1, test2)
# TRUE

我的实际问题

上述解决方案(使用apply)对我来说已经足够快了。但我不确定它是否是最佳解决方案。我对 data.table 很陌生(与 SO 上的其他一些人相比),这(可能?)不是实现我想要的子集的最有效/有效/优雅的方式。

我是来学习的,所以有人对我的子集问题有更优雅/更好/更快的方法吗?

更新

问题已被标记为重复...但我仍会在此处发布我的答案:

我发现@Marcus 的答案是最好的(=可读的)代码,而@akrun 的答案是最快的。

基准测试

具有 1,000,000 行和 50 列感兴趣的数据表(即 p 列)

#create sample data
set.seed( 123 )
n   <- 1000000
k   <- 100
dat <- sample( 1:100, n * k, replace = TRUE )
DT  <- as.data.table( matrix( data = dat, nrow = n, ncol = k ) )
setnames( DT, names( DT ), c( paste0( "p", 1:50 ), paste( "r", 1:50 ) ) )

#vector with columns starting with "p"
cols <- grep( "^p", names( DT ), value = TRUE )

apply_method   <- DT[ apply( DT[, ..cols ], 1, function(x) any( x == 10 ) ), ]
reduce_method  <- DT[ DT[, Reduce(`|`, lapply(.SD, `==`, 10)), .SDcols = cols]]
rowsums_method <- DT[ rowSums( DT[ , ..cols ] == 10, na.rm = TRUE ) >= 1 ]

identical(  apply_method, rowsums_method )

microbenchmark::microbenchmark(
  apply   = DT[ apply( DT[ , ..cols ], 1, function(x) any( x == 10 ) ), ],
  reduce  = DT[ DT[, Reduce( `|`, lapply( .SD, `==`, 10 ) ), .SDcols = cols ] ],
  rowSums = DT[ rowSums( DT[ , ..cols ] == 10, na.rm = TRUE ) >= 1, ],
  times = 10
)

#    expr       min        lq      mean    median        uq       max neval
#   apply 3352.0640 3441.7760 3665.5004 3662.7666 3760.7553 4325.9125    10
#  reduce  408.6349  437.6806  552.8850  572.2012  657.6072  710.7699    10
# rowSums  619.2594  663.7325  784.2389  850.0963  868.2096  892.7469    10

【问题讨论】:

  • 这个test3 &lt;- dt[rowSums(dt[, ..cols ] == 10) &gt;= 1]; identical(test1, test3) 怎么样?
  • @markus,这是迄今为止的最快速度......还有:漂亮的短代码......太糟糕了,我不能接受......
  • @markus 当您不是在寻找值10,而是寻找字符串时,是否也可以调整此方法,例如cols-columns中的test
  • dt &lt;- data.table(col1 = c("test", "no_test"), col2 = 1:4); cols = c("col1", "col2"); dt[rowSums(dt[, ..cols ] == "test") &gt;= 1] ?
  • 忽略我(已删除)之前的评论。将na.rm = TRUE 添加到解决方案中就可以了!由于我的生产数据在感兴趣的列中也包含 NA!

标签: r data.table


【解决方案1】:

一种选择是在.SDcols 中指定感兴趣的“cols”,循环遍历Data.table 的子集(.SD),生成逻辑向量的listReduce 它到单个逻辑向量与 (|) 并使用它来子集行

i1 <- dt[, Reduce(`|`, lapply(.SD, `==`, 10)), .SDcols = cols]
test2 <- dt[i1]
identical(test1, test2)
#[1] TRUE

【讨论】:

  • 有趣的做法...我之前没用过Reduce...速度和原来的apply-solution差不多
  • 更新:当 data.table 变大时,reduce + lapply 是明显的速度赢家。查看有问题的基准更新
  • 很想了解这个答案,但|== 是什么?
猜你喜欢
  • 2019-11-16
  • 1970-01-01
  • 2017-12-23
  • 2016-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-12
相关资源
最近更新 更多