将数字向量与另一向量中的值进行比较的最佳方法

时间:2011-02-24 20:27:11

标签: r vector

假设我有两个值向量:

a <- c(1,3,4,5,6,7,3)
b <- c(3,5,1,3,2)

我想对FUN的每个输入应用一些函数a,而不是整个b,这是最有效的方法。

更具体地说,在这种情况下,对于a中的每个元素,我想知道“a”的每个值,b中有多少元素大于或等于值。天真的做法是做到以下几点:

sum(a < b)

当然,这不起作用,因为它试图并行迭代每个向量并给出警告:

  

较长的物体长度不是较短物体长度的倍数

该命令的输出btw为3

然而,在我的情况下,我希望看到的是输出:

0 2 4 4 5 5 2

当然,我意识到我可以使用for循环这样做:

out <- c()
for (i in a) {
    for (i in a) { out[length(out) + 1] = sum(b<i)}
}

同样,我可以使用sapply

sapply(a, function(x)sum(b<x))

然而,我正在努力成为一名优秀的R程序员并远离for循环而sapply似乎非常缓慢。还有其他选择吗?

对于它的价值,我这样做了几百万次,length(b)总是小于length(a)length(a)的范围是1到30。

4 个答案:

答案 0 :(得分:4)

试试这个:

findInterval(a - 0.5, sort(b))

通过使用更简单的sort包装来避免findInterval和b)避免order.Internal的开销,从而提高速度:

order2 = function(x) .Internal(order(T, F, x))

findInterval2 = function(x, vec, rightmost.closed=F, all.inside=F) {
  nx <- length(x)
  index <- integer(nx)
  .C('find_interv_vec', xt=as.double(vec), n=length(vec),
    x=as.double(x), nx=nx, as.logical(rightmost.closed),
    as.logical(all.inside), index, DUP = FALSE, NAOK=T,
    PACKAGE='base')
  index
}

> system.time(for (i in 1:10000) findInterval(a - 0.5, sort(b)))
   user  system elapsed 
   1.22    0.00    1.22 
> system.time(for (i in 1:10000) sapply(a, function(x)sum(b<x)))
   user  system elapsed 
   0.79    0.00    0.78 
> system.time(for (i in 1:10000) rowSums(outer(a, b, ">")))
   user  system elapsed 
   0.72    0.00    0.72 
> system.time(for (i in 1:10000) findInterval(a - 0.5, b[order(b)]))
   user  system elapsed 
   0.42    0.00    0.42 
> system.time(for (i in 1:10000) findInterval2(a - 0.5, b[order2(b)]))
   user  system elapsed 
   0.16    0.00    0.15 

定义findInterval2order2的复杂性可能仅在你有大量N的迭代时才有必要。

更大N的时间安排:

> a = rep(a, 100)
> b = rep(b, 100)
> system.time(for (i in 1:100) findInterval(a - 0.5, sort(b)))
   user  system elapsed 
   0.01    0.00    0.02 
> system.time(for (i in 1:100) sapply(a, function(x)sum(b<x)))
   user  system elapsed 
   0.67    0.00    0.68 
> system.time(for (i in 1:100) rowSums(outer(a, b, ">")))
   user  system elapsed 
   3.67    0.26    3.94 
> system.time(for (i in 1:100) findInterval(a - 0.5, b[order(b)]))
   user  system elapsed 
      0       0       0 
> system.time(for (i in 1:100) findInterval2(a - 0.5, b[order2(b)]))
   user  system elapsed 
      0       0       0 

答案 1 :(得分:3)

一种选择是使用outer()将二元运算符函数>应用于ab

> outer(a, b, ">")
      [,1]  [,2]  [,3]  [,4]  [,5]
[1,] FALSE FALSE FALSE FALSE FALSE
[2,] FALSE FALSE  TRUE FALSE  TRUE
[3,]  TRUE FALSE  TRUE  TRUE  TRUE
[4,]  TRUE FALSE  TRUE  TRUE  TRUE
[5,]  TRUE  TRUE  TRUE  TRUE  TRUE
[6,]  TRUE  TRUE  TRUE  TRUE  TRUE
[7,] FALSE FALSE  TRUE FALSE  TRUE

Q的答案由上面结果的行和给出:

> rowSums(outer(a, b, ">"))
[1] 0 2 4 4 5 5 2

对于此示例数据集,此解决方案比findIntervals()略快但不是很多:

> system.time(replicate(1000, findInterval(a - 0.5, sort(b))))
   user  system elapsed 
  0.131   0.000   0.132 
> system.time(replicate(1000, rowSums(outer(a, b, ">"))))
   user  system elapsed 
  0.078   0.000   0.079

它也比sapply()版本略快,但略有增加:

> system.time(replicate(1000, sapply(a, function(x)sum(b<x))))
   user  system elapsed 
  0.082   0.000   0.082

@Charles指出findInterval()示例中的大部分时间都由sort()使用,可以通过order()来规避。完成此操作后,findInterval()解决方案比outer()解决方案更快:

> system.time(replicate(1000, findInterval(a - 0.5, b[order(b)])))
   user  system elapsed 
  0.049   0.000   0.049

答案 2 :(得分:0)

只是一个附加说明:如果您知道每个向量的值的范围,那么首先计算最大值和分钟可能会更快,例如

order2 = function(x) .Internal(order(T, F, x))
findInterval2 = function(x, vec, rightmost.closed=F, all.inside=F) {
  nx <- length(x)
  index <- integer(nx)
  .C('find_interv_vec', xt=as.double(vec), n=length(vec),
    x=as.double(x), nx=nx, as.logical(rightmost.closed),
    as.logical(all.inside), index, DUP = FALSE, NAOK=T,
    PACKAGE='base')
  index
}

f <- function(a, b) {
  # set up vars
  a.length <- length(a)
  b.length <- length(b)
  b.sorted <- b[order2(b)]
  b.min <- b.sorted[1]
  b.max <- b.sorted[b.length]
  results <- integer(a.length)

  # pre-process minimums
  v.min <- which(a <= b.min)

  # pre-process maximums
  v.max <- which(a > b.max)
  results[v.max] <- b.max

  # compare the rest
  ind <- c(v.min, v.max)
  results[-ind] <- findInterval2(a[-ind] - 0.5, b.sorted)
  results
}

其中给出以下时间

> N <- 10
> n <- 1e5
> b <- runif(n, 0, 100)
> a <- runif(n, 40, 60) # NB smaller range of values than b
> summary( replicate(N, system.time(findInterval2(a - 0.5, b[order2(b)]))[3]) )
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0300  0.0300  0.0400  0.0390  0.0475  0.0500 
> summary( replicate(N, system.time(f(a, b))[3]) )
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.010   0.030   0.030   0.027   0.030   0.040 

但是,如果你不提前知道范围,或者无法对它们进行有根据的猜测,那么这可能会更慢。

答案 3 :(得分:0)

我非常警惕在生产代码中使用R的内部。内部可以在不同版本之间轻松更改。

sort.int比sort更快 - 而且b [order(b)]比sort.int(b)更快,这简直很奇怪。 R肯定能改善它的分类...

除非你使用R的内部,否则使用vapply似乎更快:

> system.time(for (i in 1:10000) findInterval(a - 0.5, sort(b)))
   user  system elapsed 
   0.99    0.00    0.98 
> system.time(for (i in 1:10000) findInterval(a - 0.5, sort.int(b)))
   user  system elapsed 
    0.8     0.0     0.8 
> system.time(for (i in 1:10000) findInterval(a - 0.5, b[order(b)]))
   user  system elapsed 
   0.32    0.00    0.32 
> system.time(for (i in 1:10000) sapply(a, function(x)sum(b<x)))
   user  system elapsed 
   0.61    0.00    0.59 
> system.time(for (i in 1:10000) vapply(a, function(x)sum(b<x), 0L))
   user  system elapsed 
   0.18    0.00    0.19