A maneira mais rápida de encontrar o segundo (terceiro ...) valor mais alto / mais baixo no vetor ou na coluna


160

R oferece max e min, mas não vejo uma maneira muito rápida de encontrar outro valor na ordem, além de classificar o vetor inteiro e escolher o valor x desse vetor.

Existe uma maneira mais rápida de obter o segundo valor mais alto (por exemplo)?

obrigado


O kit de pacote no CRAN tem uma topnfunção que é mais rápida que sort, ordere nth. Veja a documentação.
Suresh_Patel

Respostas:


195

Use o partialargumento de sort(). Para o segundo valor mais alto:

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

4
Qual é a vantagem desse método, em oposição ao sort(x, TRUE)[2]descrito na resposta de @ Abrar, além de não satisfazer a restrição da pergunta?
Hugh

5
Eu usei esse método, mas obtém o seguinte erro: Error in sort.int(x, na.last = na.last, decreasing = decreasing, ...) : index 4705 outside bounds Alguma idéia de qual pode ser o problema? Alguns detalhes: Meu x é um vetor numérico de comprimento 4706 com alguns NAs nos dados. Tentei obter o segundo valor mais alto do vetor usando exatamente o mesmo código sugerido pelo @RobHyndman.
sriramn

Por que você não ordena a descida e assume o segundo de apenas dois valores? Isso não seria mais rápido?
GTC

3
O argumento decrescente não é compatível com a classificação parcial.
21415 Rob Robndndman

7
Embora o decreasingargumento não seja compatível com a classificação parcial, você pode sempre -sort(-x, partial=n-1)[n-1]; é logicamente a mesma coisa e leva consideravelmente menos tempo que sort(x, decreasing=TRUE)[n-1].
r2evans

52

Alternativa um pouco mais lenta, apenas para os registros:

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

Parece surpreendente se isso fosse mais rápido do que classificar o vetor inteiro e pegar o n-ésimo valor!
Jwg 17/08

@jwg Este é O (n), portanto deve ser mais rápido do que classificar em grandes conjuntos de dados.
Museful

Funciona melhor com NAs do que com a outra resposta aceita - basta usar o 'na.rm = TRUE' como argumento para a função 'min'.
Yair Daon

2
Parece-me que você pode obter alguma melhora considerável velocidade com uma pequena modificação:max(x[-which.max(x)])
sindri_baldur

31

Coloquei a resposta de Rob em uma função um pouco mais geral, que pode ser usada para encontrar o 2º, 3º, 4º (etc.) máximo:

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
Legal. Esse uso é particularmente útil maxN(1:10, 1:3)(eu teria definir o N padrão a 1)
PatrickT

23

Rfast tem uma função chamada nth_element que faz exatamente o que você pede e é mais rápida que todas as implementações discutidas acima

Além disso, os métodos discutidos acima, baseados em classificação parcial, não suportam encontrar os k menores valores

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

Retornará o quinto maior elemento de x, enquanto

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

Retornará o quinto elemento menor de x

Referências abaixo em relação às respostas mais populares.

Para 10 mil números:

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

Para 1 milhão de números:

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
Agradável! Normalmente, quando vejo um usuário com um representante relativamente baixo adicionar uma resposta a uma pergunta antiga e popular, a qualidade é muito baixa. Por outro lado, isso é uma excelente adição. Fiz algumas edições de legibilidade, mas está ótimo!
Gregor Thomas

3
É importante mencionar que Rfast::nthpode retornar vários elementos (por exemplo, 8º e 9º maiores elementos), bem como os índices desses elementos.
`` #

3
O que eu gosto na solução Rfast é que o pacote também possui uma solução facilmente implementada para fazer isso em cada linha ou coluna.
Jay

16

Aqui está uma maneira fácil de encontrar os índices de N menores / maiores valores em um vetor (exemplo para N = 3):

N <- 3

N Menor:

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

N Maior:

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

Então você pode extrair os valores como:

x[ndx]

Isso é executado em L log L time, onde L é o comprimento de x. Eu acho que o usuário estava esperando por um método que é executado no log L time.
arsmath

Essa pode ser a segunda maneira mais rápida se os métodos forem ordenados por tempo e o N mais rápido extraído. Eu também gosto porque é um código muito claro comparado à solução aceita.
Pete

1
O melhor teórico e o método aceito (espero) são executados em O (L), não em O (log L). Este é executado em O (L log L).
Valentas

6

Para o enésimo valor mais alto,

sort(x, TRUE)[n]

8
O OP já disse em seu post que essa era uma solução que ele não queria usar: "além de classificar todo o vetor e depois de escolher o valor x desse vetor".
Paul Hiemstra

3

Descobri que remover o elemento max primeiro e depois executar outro max é executado em velocidade comparável:

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

Aqui está a maneira mais simples que encontrei,

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

Recentemente, quando eu estava procurando por uma função R retornando índices dos principais números N max / min em um determinado vetor, fiquei surpreso por não haver essa função.

E isso é algo muito semelhante.

A solução de força bruta usando a função base :: order parece ser a mais fácil.

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

Mas não é o mais rápido, caso o valor de N seja relativamente pequeno comparado ao comprimento do vetor x .

Por outro lado, se N é realmente pequeno, você pode usar a função base :: whichMax iterativamente e em cada iteração você pode substituir o valor encontrado por -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
}

Acredito que você veja o problema - a natureza de copiar na modificação de R. Portanto, isso terá um desempenho melhor para N muito muito muito pequeno (1,2,3), mas diminuirá rapidamente para valores de N maiores. E você está iterando sobre todos os elementos no vetor x N vezes.

Eu acho que a melhor solução no R limpo é usar parcial base :: sort .

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

Depois, você pode selecionar o último ( N item de th) a partir do resultado das funções defiend acima.

Nota: as funções definidas acima são apenas exemplos - se você quiser usá-las, deverá verificar as entradas / sanidade (por exemplo, N> length (x) ).

Escrevi um pequeno artigo sobre algo muito semelhante (obtenha índices dos principais valores máximos de N / min de um vetor) em http://palusga.cz/?p=18 - você pode encontrar aqui alguns benchmarks de funções semelhantes que defini acima.



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)
}

essa função retornará uma matriz com os n valores superiores e seus índices. espero que ajude VDevi-Chou


0

Isso localizará o índice do enésimo menor ou maior valor no vetor numérico de entrada x. Defina bottom = TRUE nos argumentos se você quiser o N'th do fundo, ou bottom = FALSE se você quiser o N'th do topo. N = 1 e inferior = TRUE é equivalente a qual.min, N = 1 e inferior = FALSE é equivalente a qual.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

O dplyr tem a função enésima, onde o primeiro argumento é o vetor e o segundo é o local que você deseja. Isso vale para repetir elementos também. Por exemplo:

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

Encontrando o segundo maior valor:

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

[1] 17

2
isso é rápido ...?
Ben Bolker 08/02

2
internamente, isso usa x[[order(order_by)[[n]]]]- portanto, é necessário classificar todo o vetor. Portanto, não será tão rápido quanto a resposta aceita.
Ben Bolker 08/02

5
mas ele usa sort com o parcial = argumento (que muda tudo)
Ben Bolker

@BenBolker, que implica a resposta de Paolo ou Rob, poderia ser usado para melhorar dplyr::nth()? bench::mark(max(x[-which.max(x)]), x[[order(-x)[[2]]]] ), nth()parece quase 10 vezes mais lento, onde length(x)são 3 milhões.
sindri_baldur

-1

Você pode identificar o próximo valor mais alto com cummax(). Se você deseja a localização de cada novo valor mais alto, por exemplo, pode passar seu vetor de cummax()valores para a diff()função para identificar os locais nos quais o cummax()valor foi alterado. diga que temos o vetor

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

Agora, se você deseja encontrar o local de uma alteração, cummax()tem muitas opções que eu costumo usar sign(diff(cummax(v))). Você deve ajustar o primeiro elemento perdido por causa de diff(). O código completo para vetor vseria:

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

Eu acho que você não entendeu a pergunta. O objetivo é encontrar, digamos, o segundo valor mais alto. Como isso ajuda você a passar de v para 12 ... e para o terceiro maior para 8?
19416 Frank

-1

Você pode usar a sortpalavra - chave assim:

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

Exemplo:

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

dará os 5 primeiros números máximos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.