逐列排序或矩阵的顶部n

时间:2012-06-07 07:01:35

标签: performance r sorting vectorization apply

我需要对矩阵进行排序,以便所有元素都保留在列中,每列都按升序排列。对于R中的矩阵或数据帧,是否存在矢量化列式排序? (我的矩阵是全正的并且由B限制,因此我可以将j*B添加到列j中的每个单元格并进行常规的一维排序:

> set.seed(100523); m <- matrix(round(runif(30),2), nrow=6); m
     [,1] [,2] [,3] [,4] [,5]
[1,] 0.47 0.32 0.29 0.54 0.38
[2,] 0.38 0.91 0.76 0.43 0.92
[3,] 0.71 0.32 0.48 0.16 0.85
[4,] 0.88 0.83 0.61 0.95 0.72
[5,] 0.16 0.57 0.70 0.82 0.05
[6,] 0.77 0.03 0.75 0.26 0.05
> offset <- rep(seq_len(5), rep(6, 5)); offset
 [1] 1 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 4 4 4 4 4 4 5 5 5 5 5 5
> m <- matrix(sort(m + offset), nrow=nrow(m)) - offset; m
     [,1] [,2] [,3] [,4] [,5]
[1,] 0.16 0.03 0.29 0.16 0.05
[2,] 0.38 0.32 0.48 0.26 0.05
[3,] 0.47 0.32 0.61 0.43 0.38
[4,] 0.71 0.57 0.70 0.54 0.72
[5,] 0.77 0.83 0.75 0.82 0.85
[6,] 0.88 0.91 0.76 0.95 0.92

但是有没有更漂亮的东西?)否则,如果我的矩阵有大约1M(10M,100M)条目(大致是方阵),那么最快的方法是什么?我担心apply和朋友的性能损失。

实际上,我不需要“排序”,只需要“前n”,n大约是30或100。我正在考虑使用applypartial的{​​{1}}参数,但我想知道这是否比仅进行矢量化排序更便宜。所以,在我自己做基准测试之前,我想请有经验的用户提出建议。

5 个答案:

答案 0 :(得分:4)

如果您想使用排序,?sort表示method = "quick"的速度可以是默认方法的两倍,大约为100万个元素。

apply(m, 2, sort, method = "quick")开始,看看是否提供足够的速度。

请注意?sort中对此的评论;领带以不稳定的方式排序。

答案 1 :(得分:4)

我已经为目前提出的解决方案制定了快速测试框架。

library(rbenchmark)

sort.q <- function(m) {
  sort(m, method='quick')
}
sort.p <- function(m) {
  mm <- sort(m, partial=TOP)[1:TOP]
  sort(mm)
}

sort.all.g <- function(f) {
  function(m) {
    o <- matrix(rep(seq_len(SIZE), rep(SIZE, SIZE)), nrow=SIZE)
    matrix(f(m+o), nrow=SIZE)[1:TOP,]-o[1:TOP,]
  }
}
sort.all <- sort.all.g(sort)
sort.all.q <- sort.all.g(sort.q)

apply.sort.g <- function(f) {
  function(m) {
    apply(m, 2, f)[1:TOP,]
  }
}
apply.sort <- apply.sort.g(sort)
apply.sort.p <- apply.sort.g(sort.p)
apply.sort.q <- apply.sort.g(sort.q)

bb <- NULL

SIZE_LIMITS <- 3:9
TOP_LIMITS <- 2:5

for (SIZE in floor(sqrt(10)^SIZE_LIMITS)) {
  for (TOP in floor(sqrt(10)^TOP_LIMITS)) {
    print(c(SIZE, TOP))
    TOP <- min(TOP, SIZE)
    m <- matrix(runif(SIZE*SIZE), floor(SIZE))
    if (SIZE < 1000) {
      mr <- apply.sort(m)
      stopifnot(apply.sort.q(m) == mr)
      stopifnot(apply.sort.p(m) == mr)
      stopifnot(sort.all(m) == mr)
      stopifnot(sort.all.q(m) == mr)
    }

    b <- benchmark(apply.sort(m),
                   apply.sort.q(m),
                   apply.sort.p(m),
                   sort.all(m),
                   sort.all.q(m),
                   columns= c("test", "elapsed", "relative",
                              "user.self", "sys.self"),
                   replications=1,
                   order=NULL)
    b$SIZE <- SIZE
    b$TOP <- TOP
    b$test <- factor(x=b$test, levels=b$test)

    bb <- rbind(bb, b)
  }
}

ftable(xtabs(user.self ~ SIZE+test+TOP, bb))

到目前为止的结果表明,对于除了最大的矩阵以外的所有矩阵,apply确实会伤害性能,除非做一个“前n”。对于“小”矩阵&lt; 1e6,只是在没有apply的情况下对整个事物进行排序是有竞争力的。对于“巨大”矩阵,对整个数组进行排序变得比apply慢。使用partial最适合“巨大”矩阵,对于“小”矩阵来说只是轻微损失。

请随意添加自己的排序程序: - )

                      TOP      10      31     100     316
SIZE  test                                               
31    apply.sort(m)         0.004   0.012   0.000   0.000
      apply.sort.q(m)       0.008   0.016   0.000   0.000
      apply.sort.p(m)       0.008   0.020   0.000   0.000
      sort.all(m)           0.000   0.008   0.000   0.000
      sort.all.q(m)         0.000   0.004   0.000   0.000
100   apply.sort(m)         0.012   0.016   0.028   0.000
      apply.sort.q(m)       0.016   0.016   0.036   0.000
      apply.sort.p(m)       0.020   0.020   0.040   0.000
      sort.all(m)           0.000   0.004   0.008   0.000
      sort.all.q(m)         0.004   0.004   0.004   0.000
316   apply.sort(m)         0.060   0.060   0.056   0.060
      apply.sort.q(m)       0.064   0.060   0.060   0.072
      apply.sort.p(m)       0.064   0.068   0.108   0.076
      sort.all(m)           0.016   0.016   0.020   0.024
      sort.all.q(m)         0.020   0.016   0.024   0.024
1000  apply.sort(m)         0.356   0.276   0.276   0.292
      apply.sort.q(m)       0.348   0.316   0.288   0.296
      apply.sort.p(m)       0.256   0.264   0.276   0.320
      sort.all(m)           0.268   0.244   0.213   0.244
      sort.all.q(m)         0.260   0.232   0.200   0.208
3162  apply.sort(m)         1.997   1.948   2.012   2.108
      apply.sort.q(m)       1.916   1.880   1.892   1.901
      apply.sort.p(m)       1.300   1.316   1.376   1.544
      sort.all(m)           2.424   2.452   2.432   2.480
      sort.all.q(m)         2.188   2.184   2.265   2.244
10000 apply.sort(m)        18.193  18.466  18.781  18.965
      apply.sort.q(m)      15.837  15.861  15.977  16.313
      apply.sort.p(m)       9.005   9.108   9.304   9.925
      sort.all(m)          26.030  25.710  25.722  26.686
      sort.all.q(m)        23.341  23.645  24.010  24.073
31622 apply.sort(m)       201.265 197.568 196.181 196.104
      apply.sort.q(m)     163.190 160.810 158.757 160.050
      apply.sort.p(m)      82.337  81.305  80.641  82.490
      sort.all(m)         296.239 288.810 289.303 288.954
      sort.all.q(m)       260.872 249.984 254.867 252.087

答案 2 :(得分:3)

apply(m, 2, sort)

做这个工作? :)

或者对于前10名,比方说,使用:

apply(m, 2 ,function(x) {sort(x,dec=TRUE)[1:10]})

性能很强 - 对于1e7行和5个cols(总共5e7个数字),我的电脑花了大约9或10秒钟。

答案 3 :(得分:3)

R在矩阵计算中非常快。在1e4列中具有1e7个元素的矩阵在我的机器上在3秒内排序

set.seed(1)
m <- matrix(runif(1e7), ncol=1e4)

system.time(sm <- apply(m, 2, sort))
   user  system elapsed 
   2.62    0.14    2.79 

前5列:

sm[1:15, 1:5]
              [,1]         [,2]         [,3]         [,4]         [,5]
 [1,] 2.607703e-05 0.0002085913 9.364448e-05 0.0001937598 1.157424e-05
 [2,] 9.228056e-05 0.0003156713 4.948019e-04 0.0002542199 2.126186e-04
 [3,] 1.607228e-04 0.0003988042 5.015987e-04 0.0004544661 5.855639e-04
 [4,] 5.756689e-04 0.0004399747 5.762535e-04 0.0004621083 5.877446e-04
 [5,] 6.932740e-04 0.0004676797 5.784736e-04 0.0004749235 6.470268e-04
 [6,] 7.856274e-04 0.0005927107 8.244428e-04 0.0005443178 6.498618e-04
 [7,] 8.489799e-04 0.0006210336 9.249109e-04 0.0005917936 6.548134e-04
 [8,] 1.001975e-03 0.0006522120 9.424880e-04 0.0007702231 6.569310e-04
 [9,] 1.042956e-03 0.0007237203 1.101990e-03 0.0009826915 6.810103e-04
[10,] 1.246256e-03 0.0007968422 1.117999e-03 0.0009873926 6.888523e-04
[11,] 1.337960e-03 0.0009294956 1.229132e-03 0.0009997757 8.671272e-04
[12,] 1.372295e-03 0.0012221676 1.329478e-03 0.0010375632 8.806398e-04
[13,] 1.583430e-03 0.0012781983 1.433513e-03 0.0010662393 8.886999e-04
[14,] 1.603961e-03 0.0013518191 1.458616e-03 0.0012068383 8.903167e-04
[15,] 1.673268e-03 0.0013697683 1.590524e-03 0.0013617468 1.024081e-03

答案 4 :(得分:1)

他们说天才与疯狂之间存在着微妙的界限......看看这个,看看你对这个想法的看法。在问题中,目标是找到可能很长的向量vec的前30个元素(1e7,1e8或更多元素)。

topn = 30
sdmult = max(1,qnorm(1-(topn/length(vec))))
sdmin = 1e-5
acceptmult = 10
calcsd = max(sd(vec),sdmin)
calcmn = mean(vec)
thresh = calcmn + sdmult*calcsd
subs = which(vec > thresh)
while (length(subs) > topn * acceptmult) {
    thresh = thresh + calcsd
    subs = which(vec > thresh)
}
while (length(subs) < topn) {
    thresh = thresh - calcsd
    subs = which(vec > thresh)
}
topvals = sort(vec[subs],dec=TRUE)[1:topn]

基本思想是,即使我们对vec的分布知之甚少,我们当然希望vec中的最高值比平均值高几个标准偏差。如果vec是正态分布的,那么第2行的qnorm表达式可以粗略地了解我们需要查找最高topn值的平均值(例如,如果vec包含1e8值,前30个值可能位于从均值上方5 sd开始的区域。)即使vec不正常,这个假设也不太可能远离事实。

好的,我们计算vec的均值和sd,并使用这些来提出一个阈值来看上面 - 一定数量的sd高于均值。我们希望在这个上尾中找到略高于topn值的子集。如果我们这样做,我们可以对其进行排序并轻松识别最高topn值 - 这将是topn总体中vec值最高的值。

现在这里的确切规则可能会稍微调整一下,但我们的想法是,由于某种原因,我们需要防止原来的阈值“出局”。因此,我们利用了这样一个事实,即可以快速检查有多少元素超过某个阈值。因此,我们首先以calcsd为增量提高阈值,直到阈值以上的10 * topn个元素少于thresh个元素。然后,如果需要的话。我们减少calcsd(再次以topn为步骤),直到我们确实至少有topn个元素高于阈值。这种双向搜索应始终导致“阈值集”,其大小非常接近topn(希望在10或100之内)。由于topn相对较小(典型值为30),因此对此阈值集进行排序的速度非常快,这当然会立即为我们提供原始向量vec中最高的{{1}}元素。

我的主张是,生成一个合适的阈值集所涉及的计算都是快速的R,所以如果只需要一个非常大的向量的前30个元素,这种间接方法将击败任何涉及排序的方法整个载体。

你怎么看?!如果您认为这是一个有趣的想法,请喜欢/投票:)我会考虑做一些正确的时间,但我对随机生成的数据的初步测试真的很有希望 - 在“真实”数据上测试它会很棒虽然...!

干杯:)