Lyn*_*akr 11 r data.table
我习惯于将相似的任务集中到一行中。例如,如果我需要过滤a,b以及c在数据表中,我把它们放在一起在一个[]与AND运算。昨天,我注意到在我的特定情况下,它的运行速度非常慢,并且经过了测试,却没有测试链接过滤器。我在下面提供了一个示例。
首先,我为随机数生成器添加种子,加载data.table,并创建一个虚拟数据集。
# Set RNG seed
set.seed(-1)
# Load libraries
library(data.table)
# Create data table
dt <- data.table(a = sample(1:1000, 1e7, replace = TRUE),
b = sample(1:1000, 1e7, replace = TRUE),
c = sample(1:1000, 1e7, replace = TRUE),
d = runif(1e7))
Run Code Online (Sandbox Code Playgroud)
接下来,我定义我的方法。第一种方法将过滤器链接在一起。第二个将过滤器与在一起。
# Chaining method
chain_filter <- function(){
dt[a %between% c(1, 10)
][b %between% c(100, 110)
][c %between% c(750, 760)]
}
# Anding method
and_filter <- function(){
dt[a %between% c(1, 10) & b %between% c(100, 110) & c %between% c(750, 760)]
}
Run Code Online (Sandbox Code Playgroud)
在这里,我检查它们是否给出相同的结果。
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
Run Code Online (Sandbox Code Playgroud)
最后,我对它们进行基准测试。
# Benchmark
microbenchmark::microbenchmark(chain_filter(), and_filter())
#> Unit: milliseconds
#> expr min lq mean median uq max
#> chain_filter() 25.17734 31.24489 39.44092 37.53919 43.51588 78.12492
#> and_filter() 92.66411 112.06136 130.92834 127.64009 149.17320 206.61777
#> neval cld
#> 100 a
#> 100 b
Run Code Online (Sandbox Code Playgroud)
由reprex软件包(v0.3.0)创建于2019-10-25
在这种情况下,链接可将运行时间减少约70%。为什么会这样呢?我的意思是,数据表的底层是什么?我没有看到任何关于禁止使用的警告&,所以我惊讶地发现两者之间的差异如此之大。在这两种情况下,他们评估的条件相同,因此应该没有区别。在AND情况下,它&是一个快速运算符,然后只需过滤一次数据表(即,使用AND产生的逻辑矢量),而不是在链接情况下过滤三次。
该原则通常适用于数据表操作吗?模块化任务始终是更好的策略吗?
多数情况下,答案是在无铅评论中给出的:data.table在这种情况下,“链接方法” 比“ anding方法”更快,因为链接会依次运行条件。随着每个步骤的减小data.table,对下一个步骤的评估就更少了。“ Anding”每次都会评估完整尺寸数据的条件。
我们可以用一个例子来证明这一点:当各个步骤不减小的大小时data.table(即,两个方法要检查的条件都相同):
chain_filter <- function(){
dt[a %between% c(1, 1000) # runs evaluation but does not filter out cases
][b %between% c(1, 1000)
][c %between% c(750, 760)]
}
# Anding method
and_filter <- function(){
dt[a %between% c(1, 1000) & b %between% c(1, 1000) & c %between% c(750, 760)]
}
Run Code Online (Sandbox Code Playgroud)
使用与bench软件包相同的数据,该软件包会自动检查结果是否相同:
res <- bench::mark(
chain = chain_filter(),
and = and_filter()
)
summary(res)
#> # A tibble: 2 x 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
#> 1 chain 299ms 307ms 3.26 691MB 9.78
#> 2 and 123ms 142ms 7.18 231MB 5.39
summary(res, relative = TRUE)
#> # A tibble: 2 x 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 chain 2.43 2.16 1 2.99 1.82
#> 2 and 1 1 2.20 1 1
Run Code Online (Sandbox Code Playgroud)
如您所见,在这种情况下,“与” 运算的速度快了2.43倍。这意味着链接实际上会增加一些开销,这表明通常ANDing应该更快。如果条件正在data.table逐步缩小,则除外。从理论上讲,链接方法甚至可能更慢(甚至不考虑开销),即条件是否会增加数据的大小。但是实际上我认为这是不可能的,因为不允许使用逻辑向量的循环data.table。我认为这可以回答您的奖金问题。
为了进行比较,我机器上的原始功能包括bench:
res <- bench::mark(
chain = chain_filter_original(),
and = and_filter_original()
)
summary(res)
#> # A tibble: 2 x 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
#> 1 chain 29.6ms 30.2ms 28.5 79.5MB 7.60
#> 2 and 125.5ms 136.7ms 7.32 228.9MB 7.32
summary(res, relative = TRUE)
#> # A tibble: 2 x 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#> <bch:expr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 chain 1 1 3.89 1 1.04
#> 2 and 4.25 4.52 1 2.88 1
Run Code Online (Sandbox Code Playgroud)