假设我们有一个vector
(或data.frame
),如下所示:
set.seed(1)
x <- sample(10, 1e6, TRUE)
有人希望得到x
的所有值x > 4
,说:
a1 <- x[x > 4] # (or)
a2 <- x[which(x > 4)]
identical(a1, a2) # TRUE
我认为大多数人会更喜欢x[x > 4]
。但令人惊讶的是(至少对我而言),使用which
进行子集化的速度更快!
require(microbenchmark)
microbenchmark(x[x > 4], x[which(x > 4)], times = 100)
Unit: milliseconds
expr min lq median uq max neval
x[x > 4] 56.59467 57.70877 58.54111 59.94623 104.51472 100
x[which(x > 4)] 26.62217 27.64490 28.31413 29.97908 99.68973 100
它比我快2.1倍。
我认为,差异的一种可能性可能是由于which
不考虑NA
而>
也会回复它们。但是逻辑运算本身应该是造成这种差异的原因,而不是的情况(显然)。那就是:
microbenchmark(x > 4, which(x > 4), times = 100)
Unit: milliseconds
expr min lq median uq max neval
x > 4 8.182576 10.06163 12.68847 14.64203 60.83536 100
which(x > 4) 18.579746 19.94923 21.43004 23.75860 64.20152 100
在子集化之前使用which
大约慢1.7倍。但是which
似乎在子集化过程中大幅度地追赶。
似乎无法使用我常用的选择武器debugonce
(thanks to @GavinSimpson)作为which
来电.Internal(which(x))
,而==
来电.Primitive("==")
因此,我的问题是[
类型numeric
导致which
类型>
比{{1}}生成的逻辑向量更快?有什么想法吗?
答案 0 :(得分:4)
我想我应该退出评论并添加答案。这是我的预感,建立在其他人已经回答和讨论的内容上。 (我确信在subset_dflt的C源代码中存在真正的答案。)
我有一个向量x
和一个逻辑向量x > 0
,我可以通过两种方式在x
上对x > 0
进行分组。我可以使用which
或者我可以直接使用向量x > 0
作为索引。但是,我们必须注意,这两者并不相同,因为x[x > 0]
将保留NA
,而x[which(x > 0)]
则不会。{/ p>
但是,在任何一种方法中,我都需要检查向量x > 0
的每个元素。在显式的which
调用中,我将只检查元素的布尔状态,而在直接子设置操作中,我将不得不检查每个元素的缺失和布尔状态。
@flodel带来了一个有趣的观察。由于[
,is.na
,which
和|
都是原语或内部例程,因此我们假设没有非常的开销并执行此实验:
microbenchmark(which(x > 0), x[which(x > 0)], x > 0 | is.na(x), x[x > 0],
unit="us", times=1000)
Unit: microseconds
expr min lq median uq max neval
which(x > 0) 1219.274 1238.693 1261.439 1900.871 23085.57 1000
x[which(x > 0)] 1554.857 1592.543 1974.370 2339.238 23816.99 1000
x > 0 | is.na(x) 3439.191 3459.296 3770.260 4194.474 25234.70 1000
x[x > 0] 3838.455 3876.816 4267.261 4621.544 25734.53 1000
考虑到中值,我们可以看到,假设x > 0 | is.na(x)
是我所说的在逻辑子设置中发生的粗略模型,那么'子集'中的实际时间是~500 us。而'子集'所用的时间约为700 us。这两个数字都是可比较的,并且表明它不是“子集”本身,这在一种方法或另一种方法中是昂贵的。相反,在which
方法中计算所需的子集的工作正在进行中。
答案 1 :(得分:3)
这似乎是因为逻辑矢量的子集比数值索引的子集慢。
> ii <- x > 4
> ij <- which(x > 4)
>
> head(ii)
[1] FALSE FALSE TRUE TRUE FALSE TRUE
> head(ij)
[1] 3 4 6 7 8 9
>
> microbenchmark(x[ii], x[ij], times = 100)
Unit: milliseconds
expr min lq median uq max neval
x[ii] 25.574977 26.15414 28.299858 31.080903 82.04686 100
x[ij] 3.037134 3.31821 3.670096 7.516761 12.39738 100
更新:
可能一个原因是,索引数字的较小长度可以减少子集的(内部)循环,并导致较慢的评估。你可以找到ik
&lt; ij
&lt; il
但是会有另一个区别,因为ii
和il
之间存在巨大差异。
> ii <- x > 4
>
> ij <- which(x > 4)
> ik <- which(x > 9)
> il <- which(x > -1)
>
> microbenchmark(x[ii], x[ij], x[ik], x[il], times = 100)
Unit: microseconds
expr min lq median uq max neval
x[ii] 25645.621 25986.2720 28466.412 30693.158 79582.484 100
x[ij] 3111.974 3281.8280 3477.627 6142.216 55076.121 100
x[ik] 585.723 628.2125 650.184 682.888 7551.084 100
x[il] 5266.032 5773.9015 9073.614 10583.312 15113.791 100
答案 2 :(得分:3)
这是我对它的看法。对数字进行子集可以精确地提取所需的元素。对逻辑进行子集需要检查索引向量的每个元素以查看它是否为TRUE
,然后构建目标向量所需元素的内部列表。涉及两个步骤,因此需要更长的时间。
差异最大的是提取的元素数量相对于原始向量的大小而言较小。例如:
> z <- rnorm(1e8)
> system.time(z[which(z < -5)])
user system elapsed
0.58 0.03 0.60
> system.time(z[z < -5])
user system elapsed
2.56 0.14 2.70
> system.time(z[which(z < 5)])
user system elapsed
1.39 0.30 1.68
> system.time(z[z < 5])
user system elapsed
2.82 0.44 3.26
在这里,如果你只拔出一小部分元素(在我的测试中有23个元素z <-5),与逻辑索引相比,使用which
只需要很小的比例。但是,如果您提取大部分元素,则时间会更接近。