13

假设我们有一个vector(或data.frame就此而言的)如下:

set.seed(1)
x <- sample(10, 1e6, TRUE)

并且想要获得xwhere的所有值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感谢@GavinSimpson)作为which调用.Internal(which(x)),而==调用.Primitive("==")

因此,我的问题是,为什么[on numerictypewhich 比逻辑向量更快>?有任何想法吗?

4

3 回答 3

4

我想我应该退出评论并添加答案。这是我在其他人回答和讨论的内容上建立起来的预感。(我敢肯定,subset_dflt 的 C 源代码中存在真正的答案。)

一旦我有了一个向量x和一个逻辑向量x > 0,我可以x通过x > 0两种方式进行子集化。我可以使用which或者我可以直接使用向量x > 0作为索引。但是,我们必须注意,两者并不相同,因为x[x > 0]将保留NAs 而x[which(x > 0)]不会保留。

但是,无论哪种方法,我都需要检查 vector 的每个元素x > 0。在显式which调用中,我将只需要检查元素的布尔状态,而在直接子设置操作中,我将不得不检查每个元素的缺失和布尔状态。

@flodel 带来了一个有趣的观察结果。由于[is.nawhich|都是原语或内部例程,让我们假设没有特别的开销并做这个实验:

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 微秒。而在“子集”中花费的时间约为 700 us。这两个数字是可比较的,并表明不是“子集”本身在一种方法或另一种方法中成本很高。相反,在该which方法中计算所需子集的成本更低。

于 2013-07-07T14:45:19.703 回答
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与逻辑索引相比,使用的比例非常小。但是,如果您要提取大部分元素,则时间会更接近。

于 2013-07-07T09:52:23.950 回答
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<<ijil

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
于 2013-07-07T09:26:04.747 回答