Powinno być jasne, czy nie może być rozwiązania (ponieważ np. Odpowiedź argmax nie zadziała w tym przypadku (max (0,0,0,0) = 0), jak skomentował
ambrus
Odpowiedzi:
199
To jest trochę szybsze (i wygląda ładniej)
np.argmax(aa>5)
Ponieważ argmaxzatrzyma się na pierwszym True(„W przypadku wielokrotnego wystąpienia wartości maksymalnych zwracane są indeksy odpowiadające pierwszemu wystąpieniu.”) I nie zapisuje kolejnej listy.
In[2]: N =10000In[3]: aa = np.arange(-N,N)In[4]: timeit np.argmax(aa>N/2)100000 loops, best of 3:52.3 us per loop
In[5]: timeit np.where(aa>N/2)[0][0]10000 loops, best of 3:141 us per loop
In[6]: timeit np.nonzero(aa>N/2)[0][0]10000 loops, best of 3:142 us per loop
Tylko słowo ostrzeżenia: jeśli w tablicy wejściowej nie ma wartości True, np.argmax z radością zwróci 0 (co nie jest tym, czego chcesz w tym przypadku).
ambrus,
8
Wyniki są prawidłowe, ale wyjaśnienie wydaje mi się nieco podejrzane. argmaxnie wydaje się zatrzymywać na pierwszym True. (Można to sprawdzić, tworząc tablice boolowskie z pojedynczym Truew różnych pozycjach). Szybkość jest prawdopodobnie wyjaśniona przez fakt, że argmaxnie ma potrzeby tworzenia listy wyników.
DrV
1
Myślę, że masz rację, @DrV. Moje wyjaśnienie miało dotyczyć tego, dlaczego daje prawidłowy wynik pomimo pierwotnego zamiaru, a nie szuka maksimum, a nie dlaczego jest szybszy, ponieważ nie mogę twierdzić, że rozumiem wewnętrzne szczegóły argmax.
askewchan
1
@George, obawiam się, że nie wiem dokładnie dlaczego. Mogę tylko powiedzieć, że jest szybszy w konkretnym przykładzie, który pokazałem, więc nie uważałbym tego ogólnie za szybszy bez (i) wiedzy, dlaczego tak jest (patrz komentarz @ DrV) lub (ii) testowania większej liczby przypadków (np. Czy aajest posortowany, jak w odpowiedzi @ Michael).
askewchan
3
@DrV, właśnie uruchomiłem argmaxtablice Boolean z 10 milionami elementów z pojedynczym Truew różnych pozycjach przy użyciu NumPy 1.11.2 i pozycji spraw True. Więc 1.11.2 argmaxwydaje się „zwierać” na tablicach boolowskich.
Ulrich Stern
96
biorąc pod uwagę posortowaną zawartość tablicy, istnieje jeszcze szybsza metoda: wyszukiwanie posortowane .
import time
N =10000
aa = np.arange(-N,N)%timeit np.searchsorted(aa, N/2)+1%timeit np.argmax(aa>N/2)%timeit np.where(aa>N/2)[0][0]%timeit np.nonzero(aa>N/2)[0][0]# Output100000 loops, best of 3:5.97µs per loop
10000 loops, best of 3:46.3µs per loop
10000 loops, best of 3:154µs per loop
10000 loops, best of 3:154µs per loop
To naprawdę najlepsza odpowiedź, zakładając, że tablica jest posortowana (co w rzeczywistości nie jest określone w pytaniu). Możesz uniknąć niezręczności +1dziękinp.searchsorted(..., side='right')
askewchan
3
Myślę, że sideargument ma znaczenie tylko wtedy, gdy w posortowanej tablicy występują powtarzające się wartości. Nie zmienia znaczenia zwracanego indeksu, który jest zawsze indeksem, do którego można wstawić wartość zapytania, przesuwając wszystkie kolejne wpisy w prawo i zachowując posortowaną tablicę.
Gus
@Gus, sidedziała, gdy ta sama wartość znajduje się zarówno w posortowanej, jak i wstawionej tablicy, niezależnie od powtarzających się wartości w obu. Powtarzające się wartości w posortowanej tablicy tylko wyolbrzymiają efekt (różnica między stronami to liczba razy, gdy wstawiana wartość pojawia się w posortowanej tablicy). sidenie zmienia znaczenia zwracanego indeksu, chociaż nie zmienia wynikowej tablicy po wstawieniu wartości do posortowanej tablicy w tych indeksach. Subtelne, ale ważne rozróżnienie; w rzeczywistości ta odpowiedź daje zły indeks, jeśli go N/2nie ma aa.
askewchan
Jak wskazano w powyższym komentarzu, ta odpowiedź jest oddzielona o jeden, jeśli N/2nie ma aa. Prawidłowa forma to np.searchsorted(aa, N/2, side='right')(bez +1). W przeciwnym razie obie formularze mają ten sam indeks. Rozważ przypadek testowy Nbycia nieparzystym (i N/2.0wymuszenia float, jeśli używasz Pythona 2).
askewchan
21
To też mnie zainteresowało i porównałem wszystkie sugerowane odpowiedzi z perfplotem . (Zastrzeżenie: jestem autorem perfplot.)
Jeśli wiesz, że przeglądana tablica jest już posortowana , to
numpy.searchsorted(a, alpha)
jest dla Ciebie. Jest to operacja działająca w czasie stałym, tj. Prędkość nie zależy od rozmiaru tablicy. Nie możesz być szybszy niż to.
Jeśli nie wiesz nic o swojej tablicy, nie pomylisz się
np.searchsortednie jest stały. Właściwie to O(log(n)). Ale twój przypadek testowy faktycznie porównuje najlepszy przypadek searchsorted(którym jest O(1)).
MSeifert
@MSeifert Jakiego rodzaju tablicy wejściowej / alfa potrzebujesz, aby zobaczyć O (log (n))?
Nico Schlömer
1
Uzyskanie pozycji w indeksie sqrt (length) doprowadziło do bardzo złych wyników. Napisałem również tutaj odpowiedź , w tym ten test.
W przypadku rangetablicy lub innej liniowo rosnącej tablicy możesz po prostu obliczyć indeks programowo, bez potrzeby iteracji po tablicy:
def first_index_calculate_range_like(val, arr):if len(arr)==0:raiseValueError('no value greater than {}'.format(val))elif len(arr)==1:if arr[0]> val:return0else:raiseValueError('no value greater than {}'.format(val))
first_value = arr[0]
step = arr[1]- first_value
# For linearly decreasing arrays or constant arrays we only need to check# the first element, because if that does not satisfy the condition# no other element will.if step <=0:if first_value > val:return0else:raiseValueError('no value greater than {}'.format(val))
calculated_position =(val - first_value)/ step
if calculated_position <0:return0elif calculated_position > len(arr)-1:raiseValueError('no value greater than {}'.format(val))return int(calculated_position)+1
Prawdopodobnie można by to trochę poprawić. Upewniłem się, że działa poprawnie dla kilku przykładowych tablic i wartości, ale to nie znaczy, że nie może tam być błędów, zwłaszcza biorąc pod uwagę, że używa pływaków ...
Biorąc pod uwagę, że może obliczyć pozycję bez żadnej iteracji, będzie to stały czas ( O(1)) i prawdopodobnie może pokonać wszystkie inne wymienione podejścia. Jednak wymaga stałego kroku w tablicy, w przeciwnym razie da błędne wyniki.
Ogólne rozwiązanie przy użyciu numba
Bardziej ogólnym podejściem byłoby użycie funkcji numba:
Mimo że Nico Schlömer przedstawił już pewne wzorce, pomyślałem, że przydatne może być uwzględnienie moich nowych rozwiązań i przetestowanie pod kątem różnych „wartości”.
Konfiguracja testu:
import numpy as np
import math
import numba as nb
def first_index_using_argmax(val, arr):return np.argmax(arr > val)def first_index_using_where(val, arr):return np.where(arr > val)[0][0]def first_index_using_nonzero(val, arr):return np.nonzero(arr > val)[0][0]def first_index_using_searchsorted(val, arr):return np.searchsorted(arr, val)+1def first_index_using_min(val, arr):return np.min(np.where(arr > val))def first_index_calculate_range_like(val, arr):if len(arr)==0:raiseValueError('empty array')elif len(arr)==1:if arr[0]> val:return0else:raiseValueError('no value greater than {}'.format(val))
first_value = arr[0]
step = arr[1]- first_value
if step <=0:if first_value > val:return0else:raiseValueError('no value greater than {}'.format(val))
calculated_position =(val - first_value)/ step
if calculated_position <0:return0elif calculated_position > len(arr)-1:raiseValueError('no value greater than {}'.format(val))return int(calculated_position)+1@nb.njit
def first_index_numba(val, arr):for idx in range(len(arr)):if arr[idx]> val:return idx
return-1
funcs =[
first_index_using_argmax,
first_index_using_min,
first_index_using_nonzero,
first_index_calculate_range_like,
first_index_numba,
first_index_using_searchsorted,
first_index_using_where
]from simple_benchmark import benchmark,MultiArgument
a wykresy zostały wygenerowane przy użyciu:
%matplotlib notebook
b.plot()
pozycja jest na początku
b = benchmark(
funcs,{2**i:MultiArgument([0, np.arange(2**i)])for i in range(2,20)},
argument_name="array size")
Najlepiej działa funkcja numba, po której następuje funkcja obliczeniowa i funkcja posortowana z wyszukiwaniem. Inne rozwiązania działają znacznie gorzej.
pozycja jest na końcu
b = benchmark(
funcs,{2**i:MultiArgument([2**i-2, np.arange(2**i)])for i in range(2,20)},
argument_name="array size")
W przypadku małych tablic funkcja numba działa zadziwiająco szybko, jednak w przypadku większych tablic jest lepsza od funkcji obliczającej i funkcji sortowania.
pozycja jest na sqrt (len)
b = benchmark(
funcs,{2**i:MultiArgument([np.sqrt(2**i), np.arange(2**i)])for i in range(2,20)},
argument_name="array size")
To jest bardziej interesujące. Ponownie numba i funkcja obliczająca działają świetnie, jednak w rzeczywistości powoduje to najgorszy przypadek sortowania wyszukiwania, który naprawdę nie działa dobrze w tym przypadku.
Porównanie funkcji, gdy żadna wartość nie spełnia warunku
Innym interesującym punktem jest zachowanie tych funkcji, jeśli nie ma wartości, której indeks powinien zostać zwrócony:
arr = np.ones(100)
value =2for func in funcs:print(func.__name__)try:print('-->', func(value, arr))exceptExceptionas e:print('-->', e)
Z tym wynikiem:
first_index_using_argmax
-->0
first_index_using_min
--> zero-size array to reduction operation minimum which has no identity
first_index_using_nonzero
--> index 0is out of bounds for axis 0with size 0
first_index_calculate_range_like
--> no value greater than 2
first_index_numba
-->-1
first_index_using_searchsorted
-->101
first_index_using_where
--> index 0is out of bounds for axis 0with size 0
Searchsorted, argmax i numba po prostu zwracają nieprawidłową wartość. Jednak searchsortedi numbazwróć indeks, który nie jest prawidłowym indeksem dla tablicy.
Funkcje where, min, nonzeroa calculaterzut wyjątek. Jednak tylko wyjątek calculatefaktycznie mówi coś pomocnego.
Oznacza to, że w rzeczywistości należy zawrzeć te wywołania w odpowiedniej funkcji opakowującej, która wyłapuje wyjątki lub nieprawidłowe wartości zwracane i odpowiednio je obsługuje, przynajmniej jeśli nie jesteś pewien, czy wartość może znajdować się w tablicy.
Uwaga: Obliczanie i searchsortedopcje działają tylko w specjalnych warunkach. Funkcja „oblicz” wymaga stałego kroku, a posortowane wyszukiwanie wymaga posortowania tablicy. Mogą więc być przydatne w odpowiednich okolicznościach, ale nie są ogólnymi rozwiązaniami tego problemu. W przypadku, gdy mamy do czynienia z posortowanych list Pythona warto spojrzeć na przepoławiać modułu zamiast korzystania Numpys searchsorted.
Zwróci to najmniejszy indeks, w którym warunek jest spełniony, podczas gdy wherezwróci nieskończoność, jeśli warunek nigdy nie zostanie spełniony (i zwróci pustą tablicę).
Odpowiedzi:
To jest trochę szybsze (i wygląda ładniej)
Ponieważ
argmax
zatrzyma się na pierwszymTrue
(„W przypadku wielokrotnego wystąpienia wartości maksymalnych zwracane są indeksy odpowiadające pierwszemu wystąpieniu.”) I nie zapisuje kolejnej listy.źródło
argmax
nie wydaje się zatrzymywać na pierwszymTrue
. (Można to sprawdzić, tworząc tablice boolowskie z pojedynczymTrue
w różnych pozycjach). Szybkość jest prawdopodobnie wyjaśniona przez fakt, żeargmax
nie ma potrzeby tworzenia listy wyników.argmax
.aa
jest posortowany, jak w odpowiedzi @ Michael).argmax
tablice Boolean z 10 milionami elementów z pojedynczymTrue
w różnych pozycjach przy użyciu NumPy 1.11.2 i pozycji sprawTrue
. Więc 1.11.2argmax
wydaje się „zwierać” na tablicach boolowskich.biorąc pod uwagę posortowaną zawartość tablicy, istnieje jeszcze szybsza metoda: wyszukiwanie posortowane .
źródło
+1
dziękinp.searchsorted(..., side='right')
side
argument ma znaczenie tylko wtedy, gdy w posortowanej tablicy występują powtarzające się wartości. Nie zmienia znaczenia zwracanego indeksu, który jest zawsze indeksem, do którego można wstawić wartość zapytania, przesuwając wszystkie kolejne wpisy w prawo i zachowując posortowaną tablicę.side
działa, gdy ta sama wartość znajduje się zarówno w posortowanej, jak i wstawionej tablicy, niezależnie od powtarzających się wartości w obu. Powtarzające się wartości w posortowanej tablicy tylko wyolbrzymiają efekt (różnica między stronami to liczba razy, gdy wstawiana wartość pojawia się w posortowanej tablicy).side
nie zmienia znaczenia zwracanego indeksu, chociaż nie zmienia wynikowej tablicy po wstawieniu wartości do posortowanej tablicy w tych indeksach. Subtelne, ale ważne rozróżnienie; w rzeczywistości ta odpowiedź daje zły indeks, jeśli goN/2
nie maaa
.N/2
nie maaa
. Prawidłowa forma tonp.searchsorted(aa, N/2, side='right')
(bez+1
). W przeciwnym razie obie formularze mają ten sam indeks. Rozważ przypadek testowyN
bycia nieparzystym (iN/2.0
wymuszenia float, jeśli używasz Pythona 2).To też mnie zainteresowało i porównałem wszystkie sugerowane odpowiedzi z perfplotem . (Zastrzeżenie: jestem autorem perfplot.)
Jeśli wiesz, że przeglądana tablica jest już posortowana , to
jest dla Ciebie. Jest to operacja działająca w czasie stałym, tj. Prędkość nie zależy od rozmiaru tablicy. Nie możesz być szybszy niż to.
Jeśli nie wiesz nic o swojej tablicy, nie pomylisz się
Już posortowane:
Nieposortowany:
Kod do odtworzenia fabuły:
źródło
np.searchsorted
nie jest stały. Właściwie toO(log(n))
. Ale twój przypadek testowy faktycznie porównuje najlepszy przypadeksearchsorted
(którym jestO(1)
).searchsorted
(lub jakikolwiek algorytm) może pokonaćO(log(n))
binarne wyszukiwanie posortowanych, równomiernie rozłożonych danych. EDYCJA:searchsorted
to wyszukiwanie binarne.źródło
Tablice, które mają stały krok między elementami
W przypadku
range
tablicy lub innej liniowo rosnącej tablicy możesz po prostu obliczyć indeks programowo, bez potrzeby iteracji po tablicy:Prawdopodobnie można by to trochę poprawić. Upewniłem się, że działa poprawnie dla kilku przykładowych tablic i wartości, ale to nie znaczy, że nie może tam być błędów, zwłaszcza biorąc pod uwagę, że używa pływaków ...
Biorąc pod uwagę, że może obliczyć pozycję bez żadnej iteracji, będzie to stały czas (
O(1)
) i prawdopodobnie może pokonać wszystkie inne wymienione podejścia. Jednak wymaga stałego kroku w tablicy, w przeciwnym razie da błędne wyniki.Ogólne rozwiązanie przy użyciu numba
Bardziej ogólnym podejściem byłoby użycie funkcji numba:
To zadziała dla dowolnej tablicy, ale musi iterować po tablicy, więc w przeciętnym przypadku będzie to
O(n)
:Reper
Mimo że Nico Schlömer przedstawił już pewne wzorce, pomyślałem, że przydatne może być uwzględnienie moich nowych rozwiązań i przetestowanie pod kątem różnych „wartości”.
Konfiguracja testu:
a wykresy zostały wygenerowane przy użyciu:
pozycja jest na początku
Najlepiej działa funkcja numba, po której następuje funkcja obliczeniowa i funkcja posortowana z wyszukiwaniem. Inne rozwiązania działają znacznie gorzej.
pozycja jest na końcu
W przypadku małych tablic funkcja numba działa zadziwiająco szybko, jednak w przypadku większych tablic jest lepsza od funkcji obliczającej i funkcji sortowania.
pozycja jest na sqrt (len)
To jest bardziej interesujące. Ponownie numba i funkcja obliczająca działają świetnie, jednak w rzeczywistości powoduje to najgorszy przypadek sortowania wyszukiwania, który naprawdę nie działa dobrze w tym przypadku.
Porównanie funkcji, gdy żadna wartość nie spełnia warunku
Innym interesującym punktem jest zachowanie tych funkcji, jeśli nie ma wartości, której indeks powinien zostać zwrócony:
Z tym wynikiem:
Searchsorted, argmax i numba po prostu zwracają nieprawidłową wartość. Jednak
searchsorted
inumba
zwróć indeks, który nie jest prawidłowym indeksem dla tablicy.Funkcje
where
,min
,nonzero
acalculate
rzut wyjątek. Jednak tylko wyjątekcalculate
faktycznie mówi coś pomocnego.Oznacza to, że w rzeczywistości należy zawrzeć te wywołania w odpowiedniej funkcji opakowującej, która wyłapuje wyjątki lub nieprawidłowe wartości zwracane i odpowiednio je obsługuje, przynajmniej jeśli nie jesteś pewien, czy wartość może znajdować się w tablicy.
Uwaga: Obliczanie i
searchsorted
opcje działają tylko w specjalnych warunkach. Funkcja „oblicz” wymaga stałego kroku, a posortowane wyszukiwanie wymaga posortowania tablicy. Mogą więc być przydatne w odpowiednich okolicznościach, ale nie są ogólnymi rozwiązaniami tego problemu. W przypadku, gdy mamy do czynienia z posortowanych list Pythona warto spojrzeć na przepoławiać modułu zamiast korzystania Numpys searchsorted.źródło
Chciałbym zaproponować
Zwróci to najmniejszy indeks, w którym warunek jest spełniony, podczas gdy
where
zwróci nieskończoność, jeśli warunek nigdy nie zostanie spełniony (i zwróci pustą tablicę).źródło
Poszedłbym z
gdzie
V
jest wektor (tablica 1d),x
jest wartością ii
jest wynikiem indeksu.źródło