在向量或列中查找第二(第三…)最高/最低值的最快方法


160

R提供了最大值和最小值,但是除了排序整个向量以及从该向量中选择值x之外,我没有看到一种真正快速的方法来按顺序查找另一个值。

有没有更快的方法来获得第二高的值(例如)?

谢谢


上CRAN的包装试剂盒具有topn功能比更快sortordernth。查看文档。
Suresh_Patel

Answers:


195

使用的partial参数sort()。对于第二高的值:

n <- length(x)
sort(x,partial=n-1)[n-1]

4
sort(x, TRUE)[2]除了不满足问题中的约束条件之外,与@Abrar的答案中所述的方法相比,该方法的优点是什么?

5
我使用了此方法,但收到以下错误:Error in sort.int(x, na.last = na.last, decreasing = decreasing, ...) : index 4705 outside bounds 任何想法可能是什么问题?一些细节:My x是长度为4706的数字矢量,NA数据中带有s。我尝试使用与@RobHyndman建议的完全相同的代码来获取向量中的第二个最大值。
sriramn

为什么不对降序进行排序并仅取两个值的第二个?这不会更快吗?
jwg 2015年

3
递减的参数与部分排序不兼容。
Rob Hyndman

7
尽管该decreasing参数与部分排序不兼容,但您始终可以-sort(-x, partial=n-1)[n-1];从逻辑上讲,这是同一件事,而且比sort(x, decreasing=TRUE)[n-1]
r2evans

52

替代方法稍微慢一些,仅用于记录:

x <- c(12.45,34,4,0,-234,45.6,4)
max( x[x!=max(x)] )
min( x[x!=min(x)] )

如果这比对整个向量排序并取第n-1个值快得多,那将令人惊讶。
jwg 2015年

@jwg这是O(n),因此它必须比对大型数据集进行排序更快。
Museful '16

与NA相比,使用NA的效果更好-只需使用“ na.rm = TRUE”作为“ min”函数的参数即可。
Yair Daon

2
在我看来,您可以通过一些小的改动就可以显着提高速度:max(x[-which.max(x)])
sindri_baldur

31

我将Rob的答案包装到一个稍微更通用的函数中,该函数可用于找到最大2nd,3rd,4th(等等):

maxN <- function(x, N=2){
  len <- length(x)
  if(N>len){
    warning('N greater than length(x).  Setting N=length(x)')
    N <- length(x)
  }
  sort(x,partial=len-N+1)[len-N+1]
}

maxN(1:10)

1
凉。这种用法特别有用maxN(1:10, 1:3)(我会将默认N设置为1)
PatrickT

23

Rfast具有一个称为nth_element的函数,它可以完全满足您的要求,并且比上述所有实现都要快

也在上面所讨论的方法是基于局部排序,不支持找到k个最小的

Rfast::nth(x, 5, descending = T)

将返回x的第五大元素,而

Rfast::nth(x, 5, descending = F)

将返回x的第五个最小元素

以下是针对最流行答案的基准。

对于1万个数字:

N = 10000
x = rnorm(N)

maxN <- function(x, N=2){
    len <- length(x)
    if(N>len){
        warning('N greater than length(x).  Setting N=length(x)')
        N <- length(x)
    }
    sort(x,partial=len-N+1)[len-N+1]
}

microbenchmark::microbenchmark(
    Rfast = Rfast::nth(x,5,descending = T),
    maxn = maxN(x,5),
    order = x[order(x, decreasing = T)[5]]
)

Unit: microseconds
  expr      min       lq      mean   median        uq       max neval
 Rfast  160.364  179.607  202.8024  194.575  210.1830   351.517   100
  maxN  396.419  423.360  559.2707  446.452  487.0775  4949.452   100
 order 1288.466 1343.417 1746.7627 1433.221 1500.7865 13768.148   100

对于一百万个数字:

N = 1e6 #evaluates to 1 million
x = rnorm(N)

microbenchmark::microbenchmark(
    Rfast = Rfast::nth(x,5,descending = T),
    maxN = maxN(x,5),
    order = x[order(x, decreasing = T)[5]]
)

Unit: milliseconds
  expr      min        lq      mean   median        uq       max neval
 Rfast  89.7722  93.63674  114.9893 104.6325  120.5767  204.8839   100
  maxN 150.2822 207.03922  235.3037 241.7604  259.7476  336.7051   100
 order 930.8924 968.54785 1005.5487 991.7995 1031.0290 1164.9129   100

8
真好!通常,当我看到一个访问量相对较低的用户为一个受欢迎的旧问题添加答案时,它的质量就很低。另一方面,这是一个很好的补充。我进行了几次可读性编辑,但看起来很棒!
格雷戈尔·托马斯

3
值得一提的是,它Rfast::nth可以返回多个元素(例如,第8个和第9个最大的元素)以及这些元素的索引。
Jasha

3
我喜欢Rfast解决方案的原因是,该软件包还具有一个易于实现的解决方案,可以针对每一行或每一列执行此操作。
杰伊,

16

这是查找向量中N个最小/最大值的索引的简单方法(例如N = 3):

N <- 3

N最小:

ndx <- order(x)[1:N]

N最大:

ndx <- order(x, decreasing = T)[1:N]

因此,您可以将值提取为:

x[ndx]

这以L log L时间运行,其中L是x的长度。我认为用户希望使用一种能在log L时间内运行的方法。
阿斯玛思

如果方法按时间排序并提取最快的N,则这可能是第二快的方法。我也喜欢它,因为与公认的解决方案相比,它是非常清晰的代码。
皮特2015年

1
理论上最好的方法和公认的方法(希望)在O(L)时间而不是O(log L)内运行。这个在O(L log L)中运行。
瓦伦塔斯(Valentas)'18年

6

对于第n个最高值,

sort(x, TRUE)[n]

8
OP在他的帖子中已经说过,这是他不想使用的解决方案:“除了对整个向量进行排序,而不是从该向量中选择值x”。
Paul Hiemstra

3

我发现先删除max元素,然后再执行另一个max,运行速度相当:

system.time({a=runif(1000000);m=max(a);i=which.max(a);b=a[-i];max(b)})
   user  system elapsed 
  0.092   0.000   0.659 

system.time({a=runif(1000000);n=length(a);sort(a,partial=n-1)[n-1]})
   user  system elapsed 
  0.096   0.000   0.653 

2

这是我发现的最简单的方法,

num <- c(5665,1615,5154,65564,69895646)

num <- sort(num, decreasing = F)

tail(num, 1)                           # Highest number
head(tail(num, 2),1)                   # Second Highest number
head(tail(num, 3),1)                   # Third Highest number
head(tail(num, n),1)                   # Generl equation for finding nth Highest number

1

当我最近寻找给定向量中前N个最大/最小编号的索引的R函数时,令我惊讶的是,没有这样的函数。

这是非常相似的东西。

使用base :: order函数的强力解决方案似乎是最简单的解决方案。

topMaxUsingFullSort <- function(x, N) {
  sort(x, decreasing = TRUE)[1:min(N, length(x))]
}

但是,如果您的N值与向量x的长度相比相对较小,则它不是最快的方法。

另一方面,如果N很小,则可以迭代使用base :: whichMax函数,并且在每次迭代中,都可以用-Inf替换找到的值

# the input vector 'x' must not contain -Inf value 
topMaxUsingWhichMax <- function(x, N) {
  vals <- c()
  for(i in 1:min(N, length(x))) {
    idx      <- which.max(x)
    vals     <- c(vals, x[idx]) # copy-on-modify (this is not an issue because idxs is relative small vector)
    x[idx]   <- -Inf            # copy-on-modify (this is the issue because data vector could be huge)
  }
  vals
}

我相信您已经看到了问题-R的修改后复制性质。因此,对于非常非常小的N(1,2,3),它会表现更好,但对于较大的N值,它将迅速放慢速度。您正在遍历向量x N次中的所有元素。

我认为干净的R中最好的解决方案是使用局部base :: sort

topMaxUsingPartialSort <- function(x, N) {
  N <- min(N, length(x))
  x[x >= -sort(-x, partial=N)[N]][1:N]
}

然后您可以从上面定义的函数结果中选择最后一个(第N个)项目。

注意:上面定义的功能仅是示例-如果要使用它们,则必须检查/理清输入(例如N> length(x))。

我在http://palusga.cz/?p=18上写了一篇关于非常相似的东西的小文章(获取向量的前N个max / min值的索引)-在这里您可以找到一些我上面定义的相似函数的基准。



0
topn = function(vector, n){
  maxs=c()
  ind=c()
  for (i in 1:n){
    biggest=match(max(vector), vector)
    ind[i]=biggest
    maxs[i]=max(vector)
    vector=vector[-biggest]
  }
  mat=cbind(maxs, ind)
  return(mat)
}

此函数将返回具有前n个值及其索引的矩阵。希望对VDevi-Chou有帮助


0

这将在输入数值向量x中找到第N个最小值或最大值的索引。如果要从底部开始第N个,请在参数中设置bottom = TRUE,如果要从顶部开始第N个,请在bottom = FALSE中设置。N = 1和bottom = TRUE等效于which.min,N = 1和bottom = FALSE等效于which.max。

FindIndicesBottomTopN <- function(x=c(4,-2,5,-77,99),N=1,bottom=FALSE)
{

  k1 <- rank(x)
  if(bottom==TRUE){
    Nindex <- which(k1==N)
    Nindex <- Nindex[1]
  }

  if(bottom==FALSE){
    Nindex <- which(k1==(length(x)+1-N))
    Nindex <- Nindex[1]
  }

  return(Nindex)
}

0

dplyr具有函数nth,其中第一个参数是向量,第二个参数是您想要的位置。这也适用于重复元素。例如:

x = c(1,2, 8, 16, 17, 20, 1, 20)

寻找第二大价值:

 nth(unique(x),length(unique(x))-1)

[1] 17

2
这样快吗?
本·博克

2
在内部使用x[[order(order_by)[[n]]]]-因此需要对整个向量进行排序。因此,它不会像接受的答案那样快。
本·博克

5
但它sort 与partial =参数一起使用(这会改变一切)
Ben Bolker

@BenBolker暗示可以使用Paolo或Rob的答案来改进dplyr::nth()bench::mark(max(x[-which.max(x)]), x[[order(-x)[[2]]]] )nth()几乎慢了10倍,即length(x)300万。
sindri_baldur

-1

您可以使用确定下一个更高的值cummax()。例如,如果您想要每个新的较高值的位置,则可以将cummax()值向量传递给diff()函数以标识cummax()值更改的位置。说我们有载体

v <- c(4,6,3,2,-5,6,8,12,16)
cummax(v) will give us the vector
4  6  6  6  6  6  8 12 16

现在,如果您想查找更改的位置,则cummax()可以使用许多选项sign(diff(cummax(v)))。由于,您必须调整丢失的第一个元素diff()。vector的完整代码为v

which(sign(diff(cummax(v)))==1)+1

我认为您误解了这个问题。例如,目标是找到第二高的值。这如何帮助您从v升至12 ...并从第三高升至8?
弗兰克

-1

您可以sort像这样使用关键字:

sort(unique(c))[1:N]

例:

c <- c(4,2,44,2,1,45,34,2,4,22,244)
sort(unique(c), decreasing = TRUE)[1:5]

将给出前5个最大数字。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.