【问题标题】:More efficient strategy for which() or match()更有效的策略 which() 或 match()
【发布时间】:2013-04-19 05:54:16
【问题描述】:

我有一个正负数向量

vec<-c(seq(-100,-1), rep(0,20), seq(1,100))

向量大于示例,并采用一组随机值。我必须重复找到向量中负数的数量......我发现这效率很低。

由于我只需要找到负数的个数,并且对向量进行排序,我只需要知道第一个0或正数的索引(实际随机向量中可能没有0)。

目前我正在使用此代码来查找长度

length(which(vec<0))

但这会强制R遍历整个向量,但是既然是排序的,就没有必要了。

我可以使用

match(0, vec)

但我的向量并不总是有 0

所以我的问题是,是否有某种match() 函数应用条件而不是查找特定值?或者有没有更有效的方法来运行我的which() 代码?

【问题讨论】:

    标签: r match vectorization


    【解决方案1】:

    目前提供的解决方案都意味着创建logical(length(vec)) 并对其进行全面或部分扫描。如您所见,向量已排序。我们可以通过二分搜索来利用这一点。我开始认为我会非常聪明并在 C 中实现它以获得更快的速度,但是在调试算法的索引时遇到了麻烦(这是棘手的部分!)。所以我用R写了:

    f3 <- function(x) {
        imin <- 1L
        imax <- length(x)
        while (imax >= imin) {
            imid <- as.integer(imin + (imax - imin) / 2)
            if (x[imid] >= 0)
                imax <- imid - 1L
            else
                imin <- imid + 1L
        }
        imax
    }
    

    为了与其他建议进行比较

    f0 <- function(v) length(which(v < 0))
    f1 <- function(v) sum(v < 0)
    f2 <- function(v) which.min(v < 0) - 1L
    

    为了好玩

    library(compiler)
    f3.c <- cmpfun(f3)
    

    导致

    > vec <- c(seq(-100,-1,length.out=1e6), rep(0,20), seq(1,100,length.out=1e6))
    > identical(f0(vec), f1(vec))
    [1] TRUE
    > identical(f0(vec), f2(vec))
    [1] TRUE
    > identical(f0(vec), f3(vec))
    [1] TRUE
    > identical(f0(vec), f3.c(vec))
    [1] TRUE
    > microbenchmark(f0(vec), f1(vec), f2(vec), f3(vec), f3.c(vec))
    Unit: microseconds
          expr       min        lq     median         uq       max neval
       f0(vec) 15274.275 15347.870 15406.1430 15605.8470 19890.903   100
       f1(vec) 15513.807 15575.229 15651.2970 17064.8830 18326.293   100
       f2(vec) 21473.814 21558.989 21679.3210 22733.1710 27435.889   100
       f3(vec)    51.715    56.050    75.4495    78.5295   100.730   100
     f3.c(vec)    11.612    17.147    28.5570    31.3160    49.781   100
    

    可能有一些棘手的边缘情况我弄错了!转到 C,我做到了

    library(inline)
    f4 <- cfunction(c(x = "numeric"), "
        int imin = 0, imax = Rf_length(x) - 1, imid;
        while (imax >= imin) {
            imid = imin + (imax - imin) / 2;
            if (REAL(x)[imid] >= 0)
                imax = imid - 1;
            else
                imin = imid + 1;
        }
        return ScalarInteger(imax + 1);
    ")
    

    > identical(f3(vec), f4(vec))
    [1] TRUE
    > microbenchmark(f3(vec), f3.c(vec), f4(vec))
    Unit: nanoseconds
          expr   min      lq  median      uq   max neval
       f3(vec) 52096 53192.0 54918.5 55539.0 69491   100
     f3.c(vec) 10924 12233.5 12869.0 13410.0 20038   100
       f4(vec)   553   796.0   893.5  1004.5  2908   100
    

    findInterval 出现在R-help 列表中提出类似问题时。它缓慢但安全,检查vec 是否实际上已排序并处理 NA 值。如果一个人想要生活在边缘(可以说不比实现 f3 或 f4 更糟),那么

    f5.i <- function(v)
        .Internal(findInterval(v, 0 - .Machine$double.neg.eps, FALSE, FALSE))
    

    几乎与 C 实现一样快,但可能更健壮且矢量化(即,在第二个参数中查找值的向量,以便于进行类似范围的计算)。

    【讨论】:

    【解决方案2】:

    使用sum() 和逻辑比较:

    sum( vec < 0 )
    [1] 100
    

    这会很快,当您对逻辑求和时,TRUE 为 1,FALSE 为 0,因此总数将是负值的数量。

    呃哦,我觉得需要进行基准比较... :-) 向量长度为​​ 2e5

    library(microbenchmark)
    vec<-c(seq(-100,-1,length.out=1e5), rep(0,20), seq(1,100,length.out=1e5))
    microbenchmark( (which.min(vec < 0) - 1L) , (sum( vec < 0 )) )
    
    Unit: milliseconds
                          expr      min       lq   median       uq       max neval
     (which.min(vec < 0) - 1L) 1.883847 2.130746 2.554725 3.141787 75.943911   100
                (sum(vec < 0)) 1.398100 1.500639 1.508688 1.745088  2.662164   100
    

    【讨论】:

    • s/subsetting/comparison/ ;-)
    • Simon,这是 sed 和/或 unix shell 命令语法的一部分。前导“s”是“substitute”的缩写。
    【解决方案3】:

    你可以使用which.min

     which.min(vec < 0) - 1L
    

    这将返回第一个 FALSE 值,即第一个 0。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-22
      • 1970-01-01
      • 1970-01-01
      • 2013-10-30
      • 2019-07-20
      相关资源
      最近更新 更多