Il modo più veloce per trovare il secondo (terzo ...) valore più alto / più basso nel vettore o nella colonna


161

R offre il massimo e il minimo, ma non vedo un modo molto veloce per trovare un altro valore nell'ordine, a parte ordinare l'intero vettore e quindi scegliere un valore x da questo vettore.

Esiste un modo più veloce per ottenere il secondo valore più alto, ad esempio?


Il kit di pacchetti su CRAN ha una topnfunzione che è più veloce di sort, ordere nth. Guarda la documentazione.
Suresh_Patel

Risposte:


25

Rfast ha una funzione chiamata nth_element che fa esattamente quello che chiedi ed è più veloce di tutte le implementazioni discusse sopra

Anche i metodi discussi sopra che si basano su un ordinamento parziale, non supportano la ricerca dei k valori più piccoli

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

Restituirà il quinto elemento più grande di x, mentre

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

Restituirà il quinto elemento più piccolo di x

Di seguito i benchmark contro le risposte più popolari.

Per 10 mila numeri:

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

Per 1 milione di numeri:

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
Bello! Normalmente, quando vedo un utente con una reputazione relativamente bassa aggiungere una risposta a una vecchia domanda popolare, è piuttosto di bassa qualità. Questo, d'altra parte, è un'aggiunta eccellente. Ho apportato un paio di modifiche alla leggibilità, ma sembra fantastico!
Gregor Thomas,

3
Vale la pena ricordare che Rfast::nthpuò restituire più elementi (ad esempio 8 ° e 9 ° elemento più grande) nonché gli indici di tali elementi.
Jasha,

3
Quello che mi piace della soluzione Rfast è che il pacchetto ha anche una soluzione facilmente implementabile per farlo per ogni riga o colonna.
Jay,

195

Usa l' partialargomento di sort(). Per il secondo valore più alto:

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

4
Qual è il vantaggio di questo metodo rispetto a sort(x, TRUE)[2]quello descritto nella risposta di @ Abrar, oltre a non soddisfare il vincolo della domanda?
Hugh,

5
Ho usato questo metodo, ma ottengo il seguente errore: Error in sort.int(x, na.last = na.last, decreasing = decreasing, ...) : index 4705 outside bounds Hai idea di quale potrebbe essere il problema? Alcuni dettagli: la mia x è un vettore numerico di lunghezza 4706 con alcuni NAs nei dati. Ho cercato di ottenere il secondo valore più alto nel vettore usando lo stesso codice suggerito da @RobHyndman.
sriramn,

Perché non si ordina in ordine decrescente e si prende il secondo dei soli due valori? Non sarebbe più veloce?
jwg

3
L'argomento decrescente non è compatibile con l'ordinamento parziale.
Rob Hyndman,

7
Sebbene l' decreasingargomento non sia compatibile con l'ordinamento parziale, è sempre possibile -sort(-x, partial=n-1)[n-1]; è logicamente la stessa cosa e richiede molto meno tempo di sort(x, decreasing=TRUE)[n-1].
r2evans il

52

Alternativa leggermente più lenta, solo per i record:

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

Sembrerebbe sorprendente se questo fosse più veloce che ordinare l'intero vettore e prendere il valore n-1!
jwg

@jwg Questo è O (n), quindi deve essere più veloce dell'ordinamento su set di dati di grandi dimensioni.
Museful

Funziona meglio con le NA rispetto alle altre risposte accettate: basta usare "na.rm = TRUE" come argomento per la funzione "min".
Yair Daon,

2
Mi sembra che tu possa ottenere un notevole miglioramento della velocità con una piccola modifica:max(x[-which.max(x)])
sindri_baldur

31

Ho racchiuso la risposta di Rob in una funzione leggermente più generale, che può essere utilizzata per trovare il 2 °, 3 °, 4 ° (ecc.) Massimo:

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
Freddo. Questo utilizzo è particolarmente utile maxN(1:10, 1:3)(avrei impostato N predefinito su 1)
PatrickT,

16

Ecco un modo semplice per trovare gli indici di N valori più piccoli / più grandi in un vettore (Esempio per N = 3):

N <- 3

N più piccolo:

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

N più grande:

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

Quindi puoi estrarre i valori come:

x[ndx]

Questo viene eseguito in L log L time, dove L è la lunghezza di x. Penso che l'utente sperasse in un metodo che venga eseguito nel registro L tempo.
arsmath,

Questo potrebbe essere il secondo modo più veloce se i metodi fossero ordinati per tempo e il N più veloce estratto. Mi piace anche perché è un codice molto chiaro rispetto alla soluzione accettata.
Pete l'

1
Il metodo teorico migliore e il metodo accettato (si spera) vengono eseguiti nel tempo O (L), non O (log L). Questo funziona in O (L log L).
Valentas

6

Per l'ennesimo valore più alto,

sort(x, TRUE)[n]

9
Nel suo post l'OP ha già affermato che questa era una soluzione che non voleva usare: "a parte l'ordinamento dell'intero vettore e la raccolta del valore x da questo vettore".
Paul Hiemstra,

3

Ho scoperto che rimuovendo prima l'elemento max e poi eseguendo un'altra corsa max a velocità comparabile:

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

Ecco il modo più semplice che ho trovato,

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

Quando di recente stavo cercando una funzione R che restituisse gli indici dei primi N numeri max / min in un dato vettore, sono rimasto sorpreso che non esistesse una tale funzione.

E questo è qualcosa di molto simile.

La soluzione di forza bruta che utilizza la funzione base :: order sembra essere la più semplice.

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

Ma non è il più veloce nel caso in cui il tuo valore N sia relativamente piccolo rispetto alla lunghezza del vettore x .

D'altra parte se la N è veramente piccola, puoi usare la base :: whichMax in modo iterativo e in ogni iterazione puoi sostituire il valore trovato con -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
}

Credo che tu veda il problema - la natura della copia su modifica di R. Quindi, questo funzionerà meglio per N (1,2,3) molto molto molto piccolo, ma rallenterà rapidamente per valori N più grandi. E stai ripetendo tutti gli elementi nel vettore x N volte.

Penso che la soluzione migliore in R pulita sia usare la base :: ordinamento parziale .

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

Quindi è possibile selezionare l'ultimo ( N esimo) elemento dal risultato delle funzioni di cui sopra.

Nota: le funzioni sopra definite sono solo esempi - se si desidera utilizzarle, è necessario controllare gli ingressi / sanità (ad es. N> lunghezza (x) ).

Ho scritto un piccolo articolo su qualcosa di molto simile (ottieni indici dei migliori valori N / max di un vettore) su http://palusga.cz/?p=18 - puoi trovare qui alcuni parametri di riferimento di funzioni simili che ho definito sopra.



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

questa funzione restituirà una matrice con i primi n valori e i loro indici. spero che aiuti VDevi-Chou


0

Questo troverà l'indice dell'ennesimo valore più piccolo o più grande nel vettore numerico di input x. Impostare bottom = TRUE negli argomenti se si desidera l'N'th dal basso, oppure bottom = FALSE se si desidera l'N'th dall'alto. N = 1 e bottom = TRUE è equivalente a which.min, N = 1 e bottom = FALSE è equivalente a who.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 ha la funzione nth, in cui il primo argomento è il vettore e il secondo è il posto desiderato. Questo vale anche per gli elementi ripetuti. Per esempio:

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

Trovare il secondo valore più grande:

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

[1] 17

2
è veloce ...?
Ben Bolker,

2
internamente utilizza questo x[[order(order_by)[[n]]]]- quindi richiede l'ordinamento dell'intero vettore. Quindi non sarà veloce come la risposta accettata.
Ben Bolker,

5
ma usa sort con l'argomento partial = (che cambia tutto)
Ben Bolker

@BenBolker che implica che la risposta di Paolo o Rob potrebbe essere utilizzata per migliorare dplyr::nth()? bench::mark(max(x[-which.max(x)]), x[[order(-x)[[2]]]] ), nth()sembra quasi 10 volte più lento, dove length(x)sono 3 milioni.
sindri_baldur,

-1

È possibile identificare il valore più alto successivo con cummax(). Se ad esempio si desidera la posizione di ciascun nuovo valore più alto, è possibile passare il vettore di cummax()valori alla diff()funzione per identificare le posizioni in cui il cummax()valore è cambiato. diciamo che abbiamo il vettore

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

Ora, se vuoi trovare la posizione di un cambiamento, cummax()hai molte opzioni che tendo a usare sign(diff(cummax(v))). Devi regolare il primo elemento perso a causa di diff(). Il codice completo per il vettore vsarebbe:

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

Penso che tu abbia frainteso la domanda. L'obiettivo è trovare, diciamo, il secondo valore più alto. In che modo ti aiuta a passare da v a 12 ... e per il terzo più alto a 8?
Frank

-1

Puoi usare la sortparola chiave in questo modo:

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

Esempio:

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

darà i primi 5 numeri massimi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.