Le moyen le plus rapide de trouver la deuxième (troisième…) valeur la plus élevée / la plus basse dans un vecteur ou une colonne

161

R propose max et min, mais je ne vois pas de moyen très rapide de trouver une autre valeur dans l'ordre, mis à part le tri du vecteur entier, puis la sélection d'une valeur x à partir de ce vecteur.

Existe-t-il un moyen plus rapide d'obtenir la deuxième valeur la plus élevée, par exemple?

Jorgusch
la source
Le kit de package sur CRAN a une topnfonction qui est plus rapide que sort, orderet nth. Regardez la documentation.
Suresh_Patel

Réponses:

25

Rfast a une fonction appelée nth_element qui fait exactement ce que vous demandez et est plus rapide que toutes les implémentations décrites ci-dessus

De plus, les méthodes décrites ci-dessus qui sont basées sur un tri partiel ne prennent pas en charge la recherche des k plus petites valeurs

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

Renvoie le 5e plus grand élément de x, tandis que

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

Renvoie le 5e plus petit élément de x

Benchmarks ci-dessous par rapport aux réponses les plus populaires.

Pour 10 mille numéros:

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

Pour 1 million de numéros:

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
Stefanos
la source
8
Agréable! Normalement, quand je vois un utilisateur relativement peu répété ajouter une réponse à une vieille question populaire, c'est plutôt de mauvaise qualité. Ceci, en revanche, est un excellent ajout. J'ai fait quelques modifications de lisibilité, mais ça a l'air génial!
Gregor Thomas
3
Il convient de mentionner que Rfast::nthpeut renvoyer plusieurs éléments (par exemple les 8ème et 9ème éléments les plus grands) ainsi que les indices de ces éléments.
Jasha
3
Ce que j'aime dans la solution Rfast, c'est que le package a également une solution facilement implémentée pour le faire pour chaque ligne ou colonne.
Jay
195

Utilisez l' partialargument de sort(). Pour la deuxième valeur la plus élevée:

n <- length(x)
sort(x,partial=n-1)[n-1]
Rob Hyndman
la source
4
Quel est l'avantage de cette méthode par rapport à sort(x, TRUE)[2]celle décrite dans la réponse de @ Abrar, en dehors de ne pas satisfaire la contrainte de la question?
Hugh
5
J'ai utilisé cette méthode, mais j'obtiens l'erreur suivante: Error in sort.int(x, na.last = na.last, decreasing = decreasing, ...) : index 4705 outside bounds Une idée de quel pourrait être le problème? Quelques détails: My x est un vecteur numérique de longueur 4706 avec quelques NAs dans les données. J'ai essayé d'obtenir la deuxième valeur la plus élevée du vecteur en utilisant exactement le même code que @RobHyndman suggéré.
sriramn
Pourquoi ne pas trier par ordre décroissant et prendre la seconde de deux valeurs seulement? Ne serait-ce pas plus rapide?
jwg
3
L'argument décroissant n'est pas compatible avec le tri partiel.
Rob Hyndman
7
Bien que l' decreasingargument ne soit pas compatible avec le tri partiel, vous pouvez toujours -sort(-x, partial=n-1)[n-1]; c'est logiquement la même chose et prend beaucoup moins de temps que sort(x, decreasing=TRUE)[n-1].
r2evans
52

Alternative légèrement plus lente, juste pour les enregistrements:

x <- c(12.45,34,4,0,-234,45.6,4)
max( x[x!=max(x)] )
min( x[x!=min(x)] )
Paolo
la source
Il semblerait surprenant que ce soit plus rapide que de trier tout le vecteur et de prendre la n-1e valeur!
jwg
@jwg C'est O (n), donc il doit être plus rapide que le tri sur de grands ensembles de données.
Museful
Fonctionne mieux avec les NA qu'avec les autres réponses acceptées - utilisez simplement «na.rm = TRUE» comme argument pour la fonction «min».
Yair Daon
2
Il me semble que vous pouvez obtenir une amélioration considérable de la vitesse avec une petite modification:max(x[-which.max(x)])
sindri_baldur
31

J'ai enveloppé la réponse de Rob dans une fonction un peu plus générale, qui peut être utilisée pour trouver le 2e, 3e, 4e (etc.) max:

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)
Zach
la source
1
Cool. Cette utilisation est particulièrement utile maxN(1:10, 1:3)(j'aurais mis le N par défaut à 1)
PatrickT
16

Voici un moyen simple de trouver les indices de N plus petites / plus grandes valeurs dans un vecteur (Exemple pour N = 3):

N <- 3

N Plus petit:

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

N Plus grand:

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

Ainsi, vous pouvez extraire les valeurs comme suit:

x[ndx]
Davit Sargsyan
la source
Cela s'exécute en temps L log L, où L est la longueur de x. Je pense que l'utilisateur espérait une méthode qui s'exécute dans le temps du journal L.
arsmath
Cela pourrait être le deuxième moyen le plus rapide si les méthodes étaient classées par temps et le N le plus rapide extrait. Je l'aime aussi car c'est un code très clair par rapport à la solution acceptée.
Pete
1
Le meilleur théorique et la méthode acceptée (espérons-le) s'exécute en temps O (L), et non en O (log L). Celui-ci fonctionne en O (L log L).
Valentas
6

Pour la nième valeur la plus élevée,

sort(x, TRUE)[n]
Abrar
la source
9
L'OP a déjà dit dans son message que c'était une solution qu'il ne voulait pas utiliser: "à part trier tout le vecteur et choisir la valeur x de ce vecteur".
Paul Hiemstra
3

J'ai trouvé que supprimer d'abord l'élément max, puis faire un autre max fonctionne à une vitesse 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 
John Jiang
la source
2

Voici le moyen le plus simple que j'ai trouvé,

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
Vin
la source
1

Quand je cherchais récemment une fonction R renvoyant les index des N premiers nombres max / min dans un vecteur donné, j'ai été surpris qu'il n'y ait pas une telle fonction.

Et c'est quelque chose de très similaire.

La solution de force brute utilisant la fonction base :: order semble être la plus simple.

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

Mais ce n'est pas le plus rapide au cas où votre valeur N serait relativement petite par rapport à la longueur du vecteur x .

De l'autre côté, si le N est vraiment petit, vous pouvez utiliser la fonction base :: whichMax de manière itérative et à chaque itération, vous pouvez remplacer la valeur trouvée par -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
}

Je crois que vous voyez le problème - la nature de copie sur modification de R. Donc, cela fonctionnera mieux pour très très très petit N (1, 2, 3) mais il ralentira rapidement pour des valeurs de N plus grandes. Et vous itérez sur tous les éléments en vecteur x N fois.

Je pense que la meilleure solution dans clean R est d'utiliser partial base :: sort .

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

Ensuite, vous pouvez sélectionner le dernier ( N ème) élément du résultat des fonctions définies ci-dessus.

Remarque: les fonctions définies ci-dessus ne sont que des exemples - si vous souhaitez les utiliser, vous devez vérifier les entrées / sanity (par exemple N> longueur (x) ).

J'ai écrit un petit article sur quelque chose de très similaire (obtenir les index des N meilleures valeurs max / min d'un vecteur) sur http://palusga.cz/?p=18 - vous pouvez trouver ici quelques repères de fonctions similaires que j'ai définies ci-dessus.

Donarus
la source
1

head(sort(x),..)ou tail(sort(x),...)devrait fonctionner

Emploi Mangelmans
la source
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)
}

cette fonction renverra une matrice avec les n premières valeurs et leurs indices. espérons que cela aide VDevi-Chou

vdc320
la source
0

Ceci trouvera l'indice de la N'th plus petite ou plus grande valeur dans le vecteur numérique d'entrée x. Définissez bottom = TRUE dans les arguments si vous voulez le N'th du bas, ou bottom = FALSE si vous voulez le N'th du haut. N = 1 et bottom = TRUE est équivalent à which.min, N = 1 et bottom = FALSE est équivalent à 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)
}
Ralph
la source
0

dplyr a la fonction nth, où le premier argument est le vecteur et le second est l'emplacement souhaité. Cela vaut également pour les éléments répétés. Par exemple:

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

Trouver la deuxième valeur la plus élevée:

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

[1] 17
Noale
la source
2
est-ce rapide ...?
Ben Bolker
2
en interne, cela utilise x[[order(order_by)[[n]]]]- il faut donc trier tout le vecteur. Ce ne sera donc pas aussi rapide que la réponse acceptée.
Ben Bolker
5
mais il utilise sort avec l'argument partial = (qui change tout)
Ben Bolker
@BenBolker qui implique que la réponse de Paolo ou Rob pourrait être utilisée pour s'améliorer dplyr::nth()? bench::mark(max(x[-which.max(x)]), x[[order(-x)[[2]]]] ), nth()semble presque 10 fois plus lent, où length(x)est 3 millions.
sindri_baldur
-1

Vous pouvez identifier la valeur immédiatement supérieure avec cummax(). Si vous voulez par exemple l'emplacement de chaque nouvelle valeur supérieure, vous pouvez transmettre votre vecteur de cummax()valeurs à la diff()fonction pour identifier les emplacements où la cummax()valeur a changé. disons que nous avons le vecteur

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

Maintenant, si vous voulez trouver l'emplacement d'un changement, cummax()vous avez de nombreuses options que j'ai tendance à utiliser sign(diff(cummax(v))). Vous devez ajuster le premier élément perdu à cause de diff(). Le code complet du vecteur vserait:

which(sign(diff(cummax(v)))==1)+1
user3507767
la source
Je pense que vous avez mal compris la question. Le but est de trouver, par exemple, la deuxième valeur la plus élevée. Comment cela vous aide-t-il à passer de v à 12 ... et pour le troisième plus élevé à 8?
Frank
-1

Vous pouvez utiliser le sortmot - clé comme ceci:

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

Exemple:

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

donnera les 5 premiers nombres maximum.

Chethanraj Rao
la source