使用data.table
R包,我注意到运行一个简单的for
循环时会使用非常高的处理器,该循环将使用来自另一个data.table
的值来子集数据集。当我说高使用率时,我的意思是循环运行的整个过程中所有可用线程都是100%。
有趣的是,在同一过程中使用data.frame
对象对于相同的输出所花的时间要少10倍。而且只有一个核心达到100%。
这是我希望重现的示例:
chr = c(rep(1, 1000), rep(2, 1000), rep(3, 1000), rep(3,1000))
start = rep(seq(from =1, to = 100000, by=100), 4)
end = start + 100
df1 <- data.frame(chr=chr, start=start, end=end)
df2 <- rbind(df1,df1,df1,df1,df1)
dt1 <- data.table::data.table(df1)
dt2 <- data.table::data.table(df2)
test1 <- list()
test2 <- list()
#loop subsetting a data.frame
system.time(
for (i in 1:nrow(df2)) {
no.dim <- dim(df1[df1$chr == df2[i, 'chr'] & df1$start >= df2[i, 'start'] & df1$end <= df2[i, 'end'], ])[1]
test1[i] <- no.dim
})
# loop subsetting a data.table using data.table syntax
system.time(
for (i in 1:nrow(dt2)) {
no.dim <- dim(dt1[chr == dt2[i, chr] & start >= dt2[i, start] & end <= dt2[i, end], ])[1]
test2[i] <- no.dim
})
# is the output the same
identical(test1, test2)
这是输出:
> #loop subsetting a data.frame
> system.time(
+ for (i in 1:nrow(df2)) {
+ no.dim <- dim(df1[df1$chr == df2[i, 'chr'] & df1$start >= df2[i, 'start'] & df1$end <= df2[i, 'end'], ])[1]
+ test1[i] <- no.dim
+ })
user system elapsed
2.607 0.004 2.612
>
> # loop subsetting a data.table using data.table syntax
> system.time(
+ for (i in 1:nrow(dt2)) {
+ no.dim <- dim(dt1[chr == dt2[i, chr] & start >= dt2[i, start] & end <= dt2[i, end], ])[1]
+ test2[i] <- no.dim
+ })
user system elapsed
192.632 0.152 24.398
>
> # is the output the same
> identical(test1, test2)
[1] TRUE
现在,我知道执行同一任务可能有多种更好和更有效的方法,而且我可能不是data.table
方法。但是可以说,由于某种原因,您有一个使用“ data.frame”对象的脚本,并且您想快速重写该内容以使用data.table
来代替。上面采取的方法似乎很合理。
任何人都可以在速度下降和处理器使用率方面重现同样的情况吗?是否可以通过保持或多或少相同的子集过程来解决问题,还是必须将其完全重写才能在data.table
上有效使用?
PS:刚刚在Windows计算机上对其进行了测试,线程使用率正常(一个线程以100%的速度运行),但速度仍然较慢。在与我的类似的系统上进行测试,结果与上面相同。
R version 3.5.1 (2018-07-02)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.10
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.8.0
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.8.0
locale:
[1] LC_CTYPE=C LC_NUMERIC=C LC_TIME=C LC_COLLATE=C
[5] LC_MONETARY=C LC_MESSAGES=C LC_PAPER=et_EE.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C LC_MEASUREMENT=C LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] data.table_1.12.0
loaded via a namespace (and not attached):
[1] compiler_3.5.1 assertthat_0.2.0 cli_1.0.1 tools_3.5.1 pillar_1.3.1
[6] rstudioapi_0.9.0 tibble_2.0.0 crayon_1.3.4 utf8_1.1.4 fansi_0.4.0
[11] pkgconfig_2.0.2 rlang_0.3.1
编辑:
感谢大家的评论。减速问题似乎与[.data.table
的开销有关,如@Hugh所详述。 @denis指出这里efficient subsetting of data.table with greater-than, less-than using indices提到了相同的问题。
@Frank提出的修复程序虽然确实有效并且可以产生相似的输出,但是通过完全删除循环并在原始数据集中添加了可能不需要的列来更改过程的行为。
EDIT.1:
在我的第一次编辑之后,@ Frank添加了另一种方法,该方法包括使用data.table语法计算列表列。尽管它非常简洁,但我必须承认我需要一段时间才能弄清楚发生了什么。我发现它只是在子集data.table的开始和结束列上计算lm(),所以我尝试使用for循环和data.frames重现结果。时间:
> system.time({res <- dt1[dt2, on=.(chr, start >= start, end <= end), .(n = .N, my_lm = list(lm(x.start ~ x.end))), by=.EACHI][, .(n, my_lm)]; res <- as.list(res$my_lm)})
user system elapsed
11.538 0.003 11.336
>
> test_new <- list()
> system.time(
+ for (i in 1:20000) {
+ df_new <- df1[df1$chr == df2$chr[i] & df1$start >= df2$start[i] & df1$end <= df2$end[i],]
+ test_new[[i]] <- lm(df_new$start ~ df_new$end)
+ })
user system elapsed
12.377 0.048 12.425
>
只要您具有lm()的瓶颈功能,就最好使用基本的for循环(出于控制和可读性的考虑),但要使用data.frames。
答案 0 :(得分:7)
任何人都可以在速度下降和处理器使用率方面重现同样的情况吗?是否可以通过保持大致相同的子集过程以某种方式修复,还是必须对其进行完全重写才能在data.table上有效使用?
对于OP的两种方法(分别是DF和DT),我得到的时间分别为5秒和44秒,但是...
system.time(
dt2[, v := dt1[.SD, on=.(chr, start >= start, end <= end), .N, by=.EACHI]$N]
)
# user system elapsed
# 0.03 0.01 0.03
identical(dt2$v, unlist(test1))
# TRUE
但是可以说,由于某种原因,您有一个使用'data.frame'对象的脚本,并且您想快速重写该内容以使用data.table。上面采取的方法似乎很合理。
一旦您习惯了data.table语法,这将很快写出来。
如果您不想修改dt2
,只需直接获取矢量...
res <- dt1[dt2, on=.(chr, start >= start, end <= end), .N, by=.EACHI]$N
在此示例中,行计数向量是有意义的,但是如果您需要在list
中添加更复杂的输出,则可以使用list
列... >
res <- dt1[dt2, on=.(chr, start >= start, end <= end), .(
n = .N,
my_lm = list(lm(x.start ~ x.end))
), by=.EACHI][, .(n, my_lm)]
n my_lm
1: 1 <lm>
2: 1 <lm>
3: 1 <lm>
4: 1 <lm>
5: 1 <lm>
---
19996: 2 <lm>
19997: 2 <lm>
19998: 2 <lm>
19999: 2 <lm>
20000: 2 <lm>
答案 1 :(得分:5)
用户时间与经过时间之间的差是一个线索,表明在后台进行了一些并行化:
library(data.table)
chr = c(rep(1, 1000), rep(2, 1000), rep(3, 1000), rep(3,1000))
start = rep(seq(from =1, to = 100000, by=100), 4)
end = start + 100
df1 <- data.frame(chr=chr, start=start, end=end)
df2 <- rbind(df1,df1,df1,df1,df1)
dt1 <- data.table::data.table(df1)
dt2 <- data.table::data.table(df2)
print(dim(dt1))
#> [1] 4000 3
print(dim(dt2))
#> [1] 20000 3
test1 <- list()
test2 <- list()
bench::system_time({
for (i in 1:nrow(df2)) {
no.dim <- dim(df1[df1$chr == df2[i, 'chr'] &
df1$start >= df2[i, 'start'] &
df1$end <= df2[i, 'end'], ])[1]
test1[i] <- no.dim
}
})
#> process real
#> 3.547s 3.549s
print(getDTthreads())
#> [1] 12
bench::system_time({
for (i in 1:nrow(dt2)) {
no.dim <- dim(dt1[chr == dt2[i, chr] & start >= dt2[i, start] & end <= dt2[i, end], ])[1]
test2[i] <- no.dim
}
})
#> process real
#> 83.984s 52.266s
setDTthreads(1L)
bench::system_time({
for (i in 1:nrow(dt2)) {
no.dim <- dim(dt1[chr == dt2[i, chr] & start >= dt2[i, start] & end <= dt2[i, end], ])[1]
test2[i] <- no.dim
}
})
#> process real
#> 30.922s 30.920s
由reprex package(v0.2.1)于2019-01-30创建
但是您打电话给[
20,000次同样重要。考虑这种最小的用法,以证明单行表的[.data.table
开销占运行时间的主要部分:
library(data.table)
chr = c(rep(1, 1000), rep(2, 1000), rep(3, 1000), rep(3,1000))
start = rep(seq(from =1, to = 100000, by=100), 4)
end = start + 100
df1 <- data.frame(chr=chr, start=start, end=end)
df2 <- rbind(df1,df1,df1,df1,df1)
dt1 <- data.table::data.table(df1)
dt2 <- data.table::data.table(df2)
bench::system_time({
o <- integer(nrow(df2))
for (i in 1:nrow(df2)) {
o[i] <- df2[i, ][[2]]
}
})
#> process real
#> 875.000ms 879.398ms
bench::system_time({
o <- integer(nrow(dt2))
for (i in 1:nrow(dt2)) {
o[i] <- dt2[i, ][[2]]
}
})
#> process real
#> 26.219s 13.525s
由reprex package(v0.2.1)于2019-01-30创建