【问题标题】:Fastest way to find second (third...) highest/lowest value in vector or column在向量或列中查找第二(第三...)最高/最低值的最快方法
【发布时间】:2011-01-28 00:59:30
【问题描述】:

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

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

【问题讨论】:

  • CRAN 上的包套件有一个topn 功能,比sortordernth 更快。查看文档。
  • @Suresh_Patel 您能否提供示例,将其与 Rfast::nth 提供的示例进行基准比较?如果与 Rfast::nth 相比它确实更快,那么它应该是公认的答案
  • @Stefanos,我在下面发布了基准...基于您的基准
  • 我刚刚与kit::topn(hasna=F) 进行了第二次运行...我相信我现在提供了最佳答案,不是吗?

标签: r vector


【解决方案1】:

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

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

【讨论】:

  • 这种方法与@Abrar 的回答中描述的sort(x, TRUE)[2] 相比,除了不满足问题中的约束之外,还有什么优势?
  • 我使用了这种方法,但得到以下错误:Error in sort.int(x, na.last = na.last, decreasing = decreasing, ...) : index 4705 outside bounds 知道可能是什么问题吗?一些细节:我的 x 是一个长度为 4706 的数字向量,数据中有一些 NAs。我尝试使用与@RobHyndman 建议的完全相同的代码来获得向量中的第二高值。
  • 为什么不按降序排序并取两个值中的第二个?这不是更快吗?
  • 递减参数与部分排序不兼容。
  • 虽然decreasing 参数与部分排序不兼容,但您始终可以-sort(-x, partial=n-1)[n-1];这在逻辑上是相同的,并且比sort(x, decreasing=TRUE)[n-1]花费的时间要少得多。
【解决方案2】:

稍慢的替代方案,仅用于记录:

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

【讨论】:

  • 如果这比对整个向量进行排序并取第 n-1 个值更快,这似乎令人惊讶!
  • @jwg 这是 O(n),所以它必须比在大型数据集上排序更快。
  • 在我看来,只要稍加修改,您就可以获得相当大的速度提升:max(x[-which.max(x)])
【解决方案3】:

Rfast 有一个名为 nth_element 的函数,可以完全按照您的要求进行操作。

上面讨论的其他基于部分排序的方法,不支持查找 k 个最小

更新 (28/FEB/21) 包套件提供更快的实施(topn)请参阅https://stackoverflow.com/a/66367996/4729755https://stackoverflow.com/a/53146559/4729755

免责声明:处理整数时似乎会出现问题,可以通过使用 as.numeric 绕过(例如 Rfast::nth(as.numeric(1:10), 2)),并将在 Rfast 的下一次更新中解决。

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

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

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

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

下面针对最受欢迎的答案进行基准测试。

对于一万个数字:

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

对于 1 百万个数字:

N = 1e6
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

【讨论】:

  • 不错!通常,当我看到一个相对低代表的用户添加一个流行的老问题的答案时,它的质量相当低。另一方面,这是一个很好的补充。我做了一些可读性编辑,但看起来很棒!
  • 值得一提的是,Rfast::nth 可以返回多个元素(例如第 8 和第 9 大元素)以及这些元素的索引。
  • 我喜欢 Rfast 解决方案的地方在于,该软件包还有一个易于实现的解决方案,可以为每一行或每一列执行此操作。
  • nth 中存在整数值错误。我知道,我会修复它以供将来更新包。现在你可以使用Rfast::nth(as.numeric(1:10), 2)。虽然,我并不认为Rfast::nth(1:10, 2) 是一个很好的例子。如果你有一个排序数组,为什么要使用nth?检查它是否已排序然后提取值甚至更好地提取值本身要快得多。
【解决方案4】:

我将 Rob 的答案包装成一个更通用的函数,可用于查找第 2、第 3、第 4(等)最大值:

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)

【讨论】:

  • 酷。这种用法特别有用maxN(1:10, 1:3)(我会设置默认的 N 为 1)
  • 为什么不把 fx 中的主线设为 sort(x, reduction=T, partial=N)[N​​]?
【解决方案5】:

这是一种查找向量中 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,这可能是第二快的方法。我也喜欢它,因为与公认的解决方案相比,它的代码非常清晰。
  • 理论上最好的和公认的方法(希望)在 O(L) 时间内运行,而不是 O(log L)。这个运行时间为 O(L log L)。
【解决方案6】:

对于第 n 个最高值,

sort(x, TRUE)[n]

【讨论】:

  • OP 已经在他的帖子中说这是他不想使用的解决方案:“除了对整个向量进行排序和从这个向量中选择值 x 之外”。
  • 方便,因为一个人可以轻松抓住三个(四个,随便)最高排序(x,TRUE)[1:3]
【解决方案7】:

这是我找到的最简单的方法,

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

【讨论】:

    【解决方案8】:

    给你... kit 是明显的赢家!

    N = 1e6
    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]],
      kit = x[kit::topn(x, 5L,decreasing = T)[5L]]
    ) 
    # Unit: milliseconds
    # expr       min        lq     mean    median        uq        max neval
    # Rfast 12.311168 12.473771 16.36982 12.702134 16.110779 102.749873   100
    # maxN  12.922118 13.124358 17.49628 18.977537 20.053139  28.928694   100
    # order 50.443100 50.926975 52.54067 51.270163 52.323116  66.561606   100
    # kit    1.177202  1.216371  1.29542  1.240228  1.297286   2.771715   100
    

    编辑:我忘了kit::topnhasna 选项...让我们再运行一​​次。

    microbenchmark::microbenchmark(
      Rfast = Rfast::nth(x,5,descending = T),
      maxN = maxN(x,5),
      order = x[order(x, decreasing = T)[5]],
      kit = x[kit::topn(x, 5L,decreasing = T)[5L]],
      kit2 = x[kit::topn(x, 5L,decreasing = T,hasna = F)[5L]],
      unit = "ms"
    ) 
    # Unit: milliseconds
    # expr       min        lq       mean     median        uq       max neval
    # Rfast 13.194314 13.358787 14.7227116 13.4560340 14.551194 24.524105   100
    # maxN   7.378960  7.527661 10.0747803  7.7119715 12.217756 67.409526   100
    # order 50.088927 50.488832 52.4714347 50.7415680 52.267003 70.062662   100
    # kit    1.180698  1.217237  1.2975441  1.2429790  1.278243  3.263202   100
    # kit2   0.842354  0.876329  0.9398055  0.9109095  0.944407  2.135903   100
    

    【讨论】:

      【解决方案9】:

      我发现先删除 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 
      

      【讨论】:

        【解决方案10】:

        当我最近在寻找一个返回给定向量中前 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]
        }
        

        然后你可以从上面defiend函数的结果中选择最后一个(Nth)项。

        注意:上面定义的函数只是示例 - 如果您想使用它们,您必须检查/健全输入(例如。N > length(x))。

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

        【讨论】:

          【解决方案11】:

          head(sort(x),..)tail(sort(x),...) 应该可以工作

          【讨论】:

            【解决方案12】:

            这将找到输入数字向量 x 中第 N 个最小值或最大值的索引。如果您想要从底部开始的第 N 个,则在参数中设置 bottom=TRUE,如果您想要从顶部开始的第 N 个,则设置 bottom=FALSE。 N=1 and bottom=TRUE 相当于which.min,N=1 and 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)
            }
            

            【讨论】:

              【解决方案13】:
              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

              【讨论】:

                【解决方案14】:

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

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

                求第二大值:

                 nth(unique(x),length(unique(x))-1)
                
                [1] 17
                

                【讨论】:

                • 这么快吗……?
                • 在内部使用x[[order(order_by)[[n]]]] - 所以它需要对整个向量进行排序。所以它不会像接受的答案那么快。
                • 但它使用sort 带有 partial= 参数(这会改变一切)
                • @BenBolker 暗示 Paolo 或 Rob 的回答可用于改进 dplyr::nth()? bench::mark(max(x[-which.max(x)]), x[[order(-x)[[2]]]] )nth()似乎慢了将近 10 倍,其中 length(x) 是 300 万。
                【解决方案15】:

                您可以使用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(),您必须调整丢失的第一个元素。矢量v 的完整代码为:

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

                【讨论】:

                • 我认为您误解了这个问题。目标是找到第二高的值。这对您从 v 到 12... 以及从第三高到 8 有何帮助?
                【解决方案16】:

                您可以像这样使用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 个最大数字。

                【讨论】:

                  猜你喜欢
                  • 2019-12-06
                  • 1970-01-01
                  • 2019-04-02
                  • 2018-05-03
                  • 1970-01-01
                  • 2010-10-04
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多