Najszybszy sposób na znalezienie drugiej (trzeciej…) najwyższej / najniższej wartości w wektorze lub kolumnie


161

R oferuje max i min, ale nie widzę naprawdę szybkiego sposobu na znalezienie innej wartości w zamówieniu, poza sortowaniem całego wektora, a następnie wybraniem wartości x z tego wektora.

Czy jest na przykład szybszy sposób uzyskania drugiej najwyższej wartości?


Zestaw pakiet na CRAN posiada topnfunkcję, która jest szybsza niż sort, orderi nth. Spójrz na dokumentację.
Suresh_Patel

Odpowiedzi:


195

Użyj partialargumentu sort(). Dla drugiej największej wartości:

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

4
Jaka jest zaleta tej metody w przeciwieństwie do sort(x, TRUE)[2]opisanej w odpowiedzi @ Abrar, poza niespełnieniem ograniczenia zawartego w pytaniu?
Hugh

5
Użyłem tej metody, ale otrzymałem następujący błąd: Error in sort.int(x, na.last = na.last, decreasing = decreasing, ...) : index 4705 outside bounds Masz pojęcie, na czym może polegać problem? Kilka szczegółów: My x jest wektorem numerycznym o długości 4706 z kilkoma NAs w danych. Próbowałem uzyskać drugą najwyższą wartość w wektorze, używając dokładnie tego samego kodu, co sugerował @RobHyndman.
sriramn

Dlaczego nie posortujesz malejąco i nie weźmiesz drugiej z dwóch wartości? Czy to nie byłoby szybsze?
jwg

3
Argument descreasing nie jest zgodny z sortowaniem częściowym.
Rob Hyndman

7
Chociaż decreasingargument nie jest zgodny z częściowym sortowaniem, zawsze możesz -sort(-x, partial=n-1)[n-1]; jest to logicznie to samo i zajmuje znacznie mniej czasu niż sort(x, decreasing=TRUE)[n-1].
r2evans

52

Nieco wolniejsza alternatywa, tylko dla rekordów:

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

Wydawałoby się zaskakujące, gdyby było to szybsze niż sortowanie całego wektora i przyjmowanie wartości n-1!
jwg

@jwg To jest O (n), więc musi być szybsze niż sortowanie na dużych zbiorach danych.
Rozmyślny

Działa lepiej z NA niż z innymi akceptowanymi odpowiedziami - po prostu użyj „na.rm = TRUE” jako argumentu funkcji „min”.
Yair Daon

2
Wydaje mi się, że dzięki niewielkiej modyfikacji można uzyskać znaczną poprawę szybkości:max(x[-which.max(x)])
sindri_baldur.

31

Zawinąłem odpowiedź Roba w nieco bardziej ogólną funkcję, której można użyć do znalezienia drugiego, trzeciego, czwartego (itd.) Maksimum:

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
Chłodny. To użycie jest szczególnie przydatne maxN(1:10, 1:3)(domyślnie
ustawiłbym

23

Rfast ma funkcję o nazwie nth_element, która robi dokładnie to, o co prosisz i jest szybsza niż wszystkie implementacje omówione powyżej

Również metody omówione powyżej, które są oparte na sortowaniu częściowym, nie obsługują znajdowania k najmniejszych wartości

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

Zwróci piąty co do wielkości element x, a

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

Zwróci 5. najmniejszy element x

Poniższe testy porównawcze z najpopularniejszymi odpowiedziami.

Za 10 tysięcy numerów:

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

Za 1 milion numerów:

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
Miły! Zwykle, gdy widzę, że użytkownik ma stosunkowo niską liczbę powtórzeń, dodaje odpowiedź na popularne stare pytanie, jest to dość niska jakość. To z drugiej strony doskonały dodatek. Dokonałem kilku poprawek czytelności, ale wygląda świetnie!
Gregor Thomas,

3
Warto wspomnieć, że Rfast::nthmoże zwrócić wiele elementów (np. 8. i 9. największy element), a także indeksy tych elementów.
Jasha

3
W rozwiązaniu Rfast podoba mi się to, że pakiet ma również łatwe do zaimplementowania rozwiązanie do robienia tego dla każdego wiersza lub kolumny.
Jay

16

Oto prosty sposób na znalezienie indeksów N najmniejszych / największych wartości w wektorze (przykład dla N = 3):

N <- 3

N najmniejszy:

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

N Największy:

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

Możesz więc wyodrębnić wartości jako:

x[ndx]

Działa to w L log L czas, gdzie L jest długością x. Myślę, że użytkownik miał nadzieję na metodę działającą w czasie L.
arsmath

Może to być drugi najszybszy sposób, jeśli metody zostały uporządkowane według czasu i najszybciej wyodrębniono N. Podoba mi się też, ponieważ jest to bardzo czytelny kod w porównaniu z przyjętym rozwiązaniem.
Pete,

1
Teoretycznie najlepsza i zaakceptowana metoda (miejmy nadzieję) działa w czasie O (L), a nie O (log L). Ten działa w O (L log L).
Valentas

6

Dla n-tej najwyższej wartości,

sort(x, TRUE)[n]

8
OP powiedział już w swoim poście, że jest to rozwiązanie, którego nie chce użyć: „oprócz sortowania całego wektora i wybierania wartości x z tego wektora”.
Paul Hiemstra,

3

Odkryłem, że najpierw usuwam element max, a następnie wykonuję kolejne przebiegi maksymalne z porównywalną prędkością:

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

Oto najprostszy sposób, jaki znalazłem,

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

Kiedy ostatnio szukałem funkcji R zwracającej indeksy najwyższych N max / min w danym wektorze, byłem zaskoczony, że nie ma takiej funkcji.

I to jest coś bardzo podobnego.

Rozwiązanie siłowe wykorzystujące funkcję base :: order wydaje się najłatwiejsze.

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

Ale nie jest to najszybsze, jeśli wartość N jest stosunkowo mała w porównaniu z długością wektora x .

Z drugiej strony, jeśli N jest naprawdę małe, możesz użyć iteracyjnie funkcji base :: whichMax, aw każdej iteracji możesz zastąpić znalezioną wartość -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
}

Wydaje mi się, że widzisz problem - naturę R. polegającą na kopiowaniu przy modyfikacji, więc będzie to działać lepiej dla bardzo, bardzo, bardzo małych N (1, 2, 3), ale szybko zwolni przy większych wartościach N. I iterujesz po wszystkich elementach w wektorze x N razy.

Myślę, że najlepszym rozwiązaniem w czystym R jest użycie częściowej bazy :: sort .

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

Następnie możesz wybrać ostatnią ( N- tą) pozycję z wyniku funkcji defiend powyżej.

Uwaga: funkcje zdefiniowane powyżej to tylko przykłady - jeśli chcesz z nich skorzystać, musisz sprawdzić dane wejściowe / sanity (np. N> length (x) ).

Napisałem mały artykuł o czymś bardzo podobnym (pobierz indeksy górnych wartości N max / min wektora) na http://palusga.cz/?p=18 - możesz znaleźć tutaj kilka testów podobnych funkcji, które zdefiniowałem powyżej.



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

ta funkcja zwróci macierz z n górnymi wartościami i ich indeksami. mam nadzieję, że to pomaga VDevi-Chou


0

Pozwoli to znaleźć indeks N-tej najmniejszej lub największej wartości w wejściowym wektorze liczbowym x. Ustaw bottom = TRUE w argumentach, jeśli chcesz, aby N-ty od dołu, lub bottom = FALSE, jeśli chcesz, aby N-ty od góry. N = 1 i bottom = TRUE jest równoważne któremu. Min, N = 1 i bottom = FALSE jest równoważne któremu. 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 ma funkcję nth, gdzie pierwszy argument to wektor, a drugi to żądane miejsce. Dotyczy to również powtarzających się elementów. Na przykład:

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

Znajdowanie drugiej największej wartości:

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

[1] 17

2
czy to szybko ...?
Ben Bolker,

2
wewnętrznie to wykorzystuje x[[order(order_by)[[n]]]]- więc wymaga sortowania całego wektora. Więc nie będzie tak szybko, jak zaakceptowana odpowiedź.
Ben Bolker,

5
ale używa sort z argumentem częściowym = (który zmienia wszystko)
Ben Bolker,

@BenBolker, co sugeruje, że odpowiedź Paolo lub Roba może zostać wykorzystana do poprawy dplyr::nth()? bench::mark(max(x[-which.max(x)]), x[[order(-x)[[2]]]] ), nth()wydaje się prawie 10 razy wolniejszy, gdzie length(x)wynosi 3 miliony.
sindri_baldur

-1

Możesz zidentyfikować następną wyższą wartość za pomocą cummax(). Jeśli chcesz na przykład lokalizację każdej nowej wyższej wartości, możesz przekazać swój wektor cummax()wartości do diff()funkcji, aby zidentyfikować lokalizacje, w których cummax()wartość uległa zmianie. powiedzmy, że mamy wektor

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

Teraz, jeśli chcesz znaleźć lokalizację zmiany cummax(), masz wiele opcji, z których zwykle korzystam sign(diff(cummax(v))). Musisz dostosować się do utraconego pierwszego elementu z powodu diff(). Pełny kod dla wektora vwyglądałby tak:

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

Myślę, że źle zrozumiałeś pytanie. Celem jest, powiedzmy, znalezienie drugiej co do wielkości wartości. Jak to pomogło, aby przejść z v do 12 ... a z trzeciego najwyższego do 8?
Frank

-1

Możesz użyć tego sortsłowa kluczowego w następujący sposób:

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

Przykład:

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

poda pierwsze 5 maksymalnych liczb.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.