R 为什么在“a”上进行子集划分;逻辑的;输入速度比“上的子集”慢;“数字”;类型?

R 为什么在“a”上进行子集划分;逻辑的;输入速度比“上的子集”慢;“数字”;类型?,r,subset,R,Subset,假设我们有一个向量(或者一个data.frame),如下所示: set.seed(1) x <- sample(10, 1e6, TRUE) 我想大多数人更喜欢x[x>4]。但令人惊讶的是(至少对我来说),使用进行子集设置的速度更快 require(microbenchmark) microbenchmark(x[x > 4], x[which(x > 4)], times = 100) Unit: milliseconds expr mi

假设我们有一个
向量
(或者一个
data.frame
),如下所示:

set.seed(1)
x <- sample(10, 1e6, TRUE)
我想大多数人更喜欢
x[x>4]
。但令人惊讶的是(至少对我来说),使用
进行子集设置的速度更快

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倍

我认为,一个可能的差异可能是由于<>代码> 不考虑<代码> n>代码>但是<代码> > /代码>也返回它们。但逻辑运算本身应该是造成这种差异的原因,事实并非如此(显然)。即:

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
使用
,它在子集之前的速度大约慢1.7倍。但是
似乎在子集设置上/在子集设置过程中迅速赶上了

似乎不可能使用我通常选择的武器
debugonce
()作为
调用
.Internal(which(x))
,而
==
调用
.Primitive(==”


因此,我的问题是,为什么
[
是由
产生的
数值
类型,它比
产生的逻辑向量快?有什么想法吗?

这似乎是因为按逻辑向量子集比按数字索引子集慢

> 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

下面是我的看法。在数字上子集可以精确地提取出所需的元素。在逻辑上子集需要检查索引向量的每个元素,看看它是否为
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 
>z系统时间(z[哪个(z<-5)])
用户系统运行时间
0.58    0.03    0.60 
>系统时间(z[z<-5])
用户系统运行时间
2.56    0.14    2.70 
>系统时间(z[其中(z<5)])
用户系统运行时间
1.39    0.30    1.68 
>系统时间(z[z<5])
用户系统运行时间
2.82    0.44    3.26 

这里,如果你只提取一小部分元素(在我的测试中有23个z<-5的元素),使用
,与逻辑索引相比,
所占的比例非常小。但是,如果提取的元素比例很大,时间就更近了。

我想我应该离开评论,添加一个答案。这是我的直觉,建立在其他人回答和讨论的基础上。(我确信真正的答案存在于子集\u dflt的C源代码中。)

一旦我有了一个向量
x
和一个逻辑向量
x>0
,我就可以用两种方法在
x>0
上对
x
进行子集。我可以使用
哪个
或者我可以直接使用向量
x>0
作为索引。但是,我们必须注意,这两种方法并不相同,因为
x[x>0]
将保留
NA
s,而
x[哪个(x>0)]
将不保留

然而,在任何一种方法中,我都需要检查向量
x>0
的每个元素。在显式
调用中,我必须只检查元素的布尔状态,而在直接子设置操作中,我必须检查每个元素的缺失状态和布尔状态

@flodel带来了一个有趣的观察结果。
[
是.na
哪个
,以及
|
都是原语或内部例程,让我们假设没有额外的开销,然后做这个实验:

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 |是.na(x)
是我所说的发生在逻辑子环境中的粗略模型,那么“子集”中的实际时间约为500 us。“子集”中的时间约为700 us。这两个数字都是可比的,表明在某种方法中成本高昂的不是“子集”本身。相反,这是为了共同利益而采取的措施用
which
方法计算所需的更便宜的子集。

谢谢Kohske。我的问题基本上是为什么会出现这种情况。我做了一个编辑以使其更清楚。更新了,但可能你最好深入研究源代码(c实现)如果你真的想知道开销在哪里,你可能想更新一下这篇文章,因为根据问题的新标题,你的第一句话听起来有点可笑的重复。但是,
which
不也是这样做的吗,即“检查索引向量的每个元素”?那么问题就变成了为什么是
which
比逻辑子集设置用于“构建目标向量所需元素的内部列表”的任何方法都要快。@Arun:我的观点是,无论是进行
which
还是进行逻辑子集设置,都要检查逻辑向量中的每个元素。只有使用
which
一个是显式地做,而逻辑子设置则是隐式地做。一定是对NAs传播的漠不关心使前者更快,因为这是后者正在做的唯一额外工作。也许,我们可以想出一种方法来衡量这额外的工作?@Arun:你的例子虽然做得很好,但与我的观点相切。也许,我不够清楚。
这是贪婪的,只寻找真实。逻辑子设置OTOH需要在检查布尔状态之前担心缺失。在我看来,我可能错了,这是后者正在做的额外工作。@Arun:我试图“使用源代码,Luke”,但它超出了我的想象!:\@Arun,Spe仅计算,但我认为
system.time(x[idx1])
system.time(x[which(idx1 | is.na(idx1)))
可能会有所启示。两者在时间上非常接近。代价是
。是的,确实如此,我们都在猜测。我会在有时间的时候尝试更新这个
> 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 
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