假设我有一个包含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)中 任何一个 包含值为 10
的行。
对于这个小样本数据表,可以使用以下代码手动完成此操作:
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还很新,而且这可能不是我想要的子集的最有效/最优雅的方式。
我来这里学习,所以有没有更加优雅/更好/更快速的方法来解决我的子集问题?
更新
该问题被标记为重复... 但我仍然会在这里发布我的答案:
我发现@Marcus的答案是最好(=可读性强)的代码,而@akrun的答案则是最快的。
基准测试
data.table包含100万行和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 <- dt[rowSums(dt[, ..cols ] == 10) >= 1]; identical(test1, test3)
怎么样? - markus10
而是在寻找字符串(例如cols列中的test
)时,是否也可以调整此方法? - Wimpeldt <- data.table(col1 = c("test", "no_test"), col2 = 1:4); cols = c("col1", "col2"); dt[rowSums(dt[, ..cols ] == "test") >= 1]
? - markusna.rm = TRUE
添加到解决方案中就可以了!因为我的生产数据中也包含感兴趣的列中的 NA 值! - Wimpel