【发布时间】:2019-10-25 11:38:25
【问题描述】:
我习惯将类似的任务集中到一行中。例如,如果我需要对数据表中的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))
接下来,我定义我的方法。第一种方法将过滤器链接在一起。第二个 AND 将过滤器组合在一起。
# 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)]
}
在这里,我检查它们给出了相同的结果。
# Check both give same result
identical(chain_filter(), and_filter())
#> [1] TRUE
最后,我对它们进行基准测试。
# 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
由reprex package (v0.3.0) 于 2019 年 10 月 25 日创建
在这种情况下,链接可以减少大约 70% 的运行时间。为什么会这样?我的意思是,数据表的幕后发生了什么?我没有看到任何反对使用& 的警告,所以我很惊讶差异如此之大。在这两种情况下,他们评估相同的条件,所以这不应该是一个区别。在 AND 情况下,& 是一个快速运算符,然后它只需过滤一次数据表(即使用 AND 产生的逻辑向量),而不是在链接情况下过滤三次。
奖金问题
这个原则是否适用于一般的数据表操作?模块化任务总是更好的策略吗?
【问题讨论】:
-
我对这个观察结果也有同感,也有同样的疑惑。根据我的经验,在一般操作中可以观察到链接速度的提升。
-
虽然 data.tavle 确实对这种情况进行了 一些 优化(仅此一项就是一项壮举,并且与基础 R 相比有了很大的改进!),通常是 A & B & C & D 将在组合结果和过滤之前评估 所有 N 个逻辑条件时间。而链接第二个第三个和第四个逻辑调用仅评估 n 次(其中 n
-
@MichaelChirico 哇。这太令人惊讶了!我不知道为什么,但我只是假设它会像 C++ 短路一样工作
-
跟进@MichaelChirico 的评论,您可以通过执行以下操作对向量进行类似的
base观察:chain_vec <- function() { x <- which(a < .001); x[which(b[x] > .999)] }和and_vec <- function() { which(a < .001 & b > .999) }。 (其中a和b是与runif相同长度的向量 - 我使用n = 1e7进行这些截止)。 -
@MichaelChirico 啊,我明白了。那么,最大的区别在于,在链的每个步骤中,数据表要小得多,因此可以更快地评估条件和过滤?这就说得通了。感谢您的见解!
标签: data.table r data.table