La forma más rápida de encontrar el segundo (tercer ...) valor más alto / más bajo en un vector o columna


160

R ofrece max y min, pero no veo una forma realmente rápida de encontrar el otro valor en el orden, aparte de ordenar el vector completo y no elegir el valor x de este vector.

¿Hay alguna forma más rápida de obtener el segundo valor más alto (p. Ej.)?

Gracias


El paquete de kit en CRAN tiene una topnfunción que es más rápida que sort, ordery nth. Mira la documentación.
Suresh_Patel

Respuestas:


195

Usa el partialargumento de sort(). Para el segundo valor más alto:

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

44
¿Cuál es la ventaja de este método en comparación con sort(x, TRUE)[2]lo descrito en la respuesta de @ Abrar, además de no satisfacer la restricción en la pregunta?
Hugh

55
Utilicé este método, pero aparece el siguiente error: ¿ Error in sort.int(x, na.last = na.last, decreasing = decreasing, ...) : index 4705 outside bounds Alguna idea de cuál podría ser el problema? Algunos detalles: Mi x es un vector numérico de longitud 4706 con algunos NAs en los datos. Intenté obtener el segundo valor más alto en el vector usando exactamente el mismo código que sugirió @RobHyndman.
sriramn

¿Por qué no ordena descendente y toma el segundo de solo dos valores? ¿No sería esto más rápido?
jwg

3
El argumento decreciente no es compatible con la ordenación parcial.
Rob Hyndman

77
Aunque el decreasingargumento no es compatible con la ordenación parcial, siempre puedes -sort(-x, partial=n-1)[n-1]; lógicamente es lo mismo y lleva mucho menos tiempo que sort(x, decreasing=TRUE)[n-1].
r2evans

52

Alternativa ligeramente más lenta, solo para los registros:

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

¡Parecería sorprendente si esto fuera más rápido que ordenar todo el vector y tomar el valor n-1!
jwg

@jwg Esto es O (n), por lo que tiene que ser más rápido que ordenar en grandes conjuntos de datos.
Museful

Funciona mejor con NA que con otra respuesta aceptada: solo use 'na.rm = TRUE' como argumento para la función 'min'.
Yair Daon

2
Me parece que puede obtener una mejora considerable de velocidad con una pequeña modificación:max(x[-which.max(x)])
sindri_baldur

31

Envolví la respuesta de Rob en una función un poco más general, que se puede usar para encontrar el segundo, tercero, cuarto (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
Frio. Este uso es particularmente útil maxN(1:10, 1:3)(hubiera configurado el valor predeterminado N a 1)
PatrickT

23

Rfast tiene una función llamada nth_element que hace exactamente lo que le pides y es más rápida que todas las implementaciones discutidas anteriormente

Además, los métodos discutidos anteriormente que se basan en una ordenación parcial, no admiten encontrar los k valores más pequeños

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

Devolverá el quinto elemento más grande de x, mientras que

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

Devolverá el quinto elemento más pequeño de x

Los puntos de referencia a continuación contra las respuestas más populares.

Por 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 millón 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
¡Agradable! Normalmente, cuando veo que un usuario de relativamente baja reputación agrega una respuesta a una vieja pregunta popular, es de muy baja calidad. Esto, por otro lado, es una excelente adición. Hice un par de ediciones de legibilidad, ¡pero se ve genial!
Gregor Thomas el

3
Vale la pena mencionar que Rfast::nthpuede devolver múltiples elementos (por ejemplo, el octavo y el noveno elemento más grande), así como los índices de esos elementos.
Jasha

3
Lo que me gusta de la solución Rfast es que el paquete también tiene una solución fácil de implementar para hacer esto para cada fila o columna.
Jay

16

Aquí hay una manera fácil de encontrar los índices de N valores más pequeños / más grandes en un vector (Ejemplo para N = 3):

N <- 3

N más pequeño:

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

N más grande:

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

Para que pueda extraer los valores como:

x[ndx]

Esto se ejecuta en L log L time, donde L es la longitud de x. Creo que el usuario esperaba un método que se ejecuta en tiempo de registro L.
arsmath

Esta podría ser la segunda forma más rápida si los métodos se ordenaron por tiempo y se extrajo el N más rápido. También me gusta porque es un código muy claro en comparación con la solución aceptada.
Pete

1
El mejor método teórico y el método aceptado (con suerte) se ejecuta en tiempo O (L), no O (log L). Este se ejecuta en O (L log L).
Valentas

6

Para el enésimo valor más alto,

sort(x, TRUE)[n]

8
El OP ya dijo en su publicación que esta era una solución que no quería usar: "aparte de ordenar todo el vector y luego elegir el valor x de este vector".
Paul Hiemstra

3

Descubrí que eliminar el elemento max primero y luego hacer otra carrera máxima a una velocidad comparable:

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

Aquí está la forma más simple que encontré,

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

Cuando estaba buscando recientemente una función R que devolviera índices de los N números máximos / mínimos superiores en un vector dado, me sorprendió que no existiera dicha función.

Y esto es algo muy similar.

La solución de fuerza bruta usando la función base :: order parece ser la más fácil.

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

Pero no es el más rápido en caso de que su valor de N sea ​​relativamente pequeño en comparación con la longitud del vector x .

Por otro lado, si la N es realmente pequeña, puede usar la función base :: whichMax de forma iterativa y en cada iteración puede reemplazar el 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
}

Creo que usted ve el problema: la naturaleza de copiar y modificar de R. Por lo tanto, funcionará mejor para N muy muy pequeño (1,2,3) pero se ralentizará rápidamente para valores de N más grandes. Y está iterando sobre todos los elementos en vector x N veces.

Creo que la mejor solución en clean R es usar parcial base :: sort .

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

Luego puede seleccionar el último ( N º) elemento del resultado de las funciones definidas anteriormente.

Nota: las funciones definidas anteriormente son solo ejemplos: si desea usarlas, debe verificar / controlar las entradas (por ejemplo, N> longitud (x) ).

Escribí un pequeño artículo sobre algo muy similar (obtenga índices de los valores máximos de N máximos / mínimos de un vector) en http://palusga.cz/?p=18 ; aquí puede encontrar algunos puntos de referencia de funciones similares que definí anteriormente.



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

Esta función devolverá una matriz con los valores superiores de n y sus índices. espero que ayude a VDevi-Chou


0

Esto encontrará el índice del enésimo valor más pequeño o más grande en el vector numérico de entrada x. Establezca bottom = TRUE en los argumentos si desea el N'th desde abajo, o bottom = FALSE si desea el N'th desde arriba. N = 1 y bottom = TRUE es equivalente a which.min, N = 1 y bottom = FALSE es equivalente a 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 tiene la función nth, donde el primer argumento es el vector y el segundo es el lugar que desea. Esto también se aplica a elementos repetitivos. Por ejemplo:

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

Encontrar el segundo valor más grande:

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

[1] 17

2
¿Es esto rápido ...?
Ben Bolker

2
internamente esto se usa x[[order(order_by)[[n]]]], por lo que requiere ordenar todo el vector. Por lo tanto, no será tan rápido como la respuesta aceptada.
Ben Bolker

55
pero se usa sort con el argumento parcial = (que lo cambia todo)
Ben Bolker

¿@BenBolker que implica que la respuesta de Paolo o Rob podría usarse para mejorar dplyr::nth()? bench::mark(max(x[-which.max(x)]), x[[order(-x)[[2]]]] ), nth()parece casi 10 veces más lento, donde length(x)son 3 millones.
sindri_baldur

-1

Puede identificar el siguiente valor más alto con cummax(). Si desea la ubicación de cada nuevo valor superior, por ejemplo, puede pasar su vector de cummax()valores a la diff()función para identificar las ubicaciones en las que cummax()cambió el valor. decimos que tenemos el vector

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

Ahora, si desea encontrar la ubicación de un cambio cummax(), tiene muchas opciones que suelo usar sign(diff(cummax(v))). Tienes que ajustar por el primer elemento perdido debido a diff(). El código completo para el vector vsería:

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

Creo que malinterpretas la pregunta. El objetivo es encontrar, digamos, el segundo valor más alto. ¿Cómo ayuda esto a pasar de v a 12 ... y al tercero más alto a 8?
Frank

-1

Puede usar la sortpalabra clave de esta manera:

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

Ejemplo:

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

dará los primeros 5 números máximos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.