Man sollte sich darüber im Klaren sein, ob es keine Lösung geben könnte (da z. B. die Argmax-Antwort in diesem Fall nicht funktioniert (max. (0,0,0,0) = 0), wie ambrus kommentierte
seanv507
Antworten:
198
Dies ist etwas schneller (und sieht besser aus)
np.argmax(aa>5)
Da argmaxwird beim ersten TrueAuftreten gestoppt ("Bei mehrfachem Auftreten der Maximalwerte werden die dem ersten Auftreten entsprechenden Indizes zurückgegeben.") Und speichert keine weitere Liste.
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 loopIn[5]: timeit np.where(aa>N/2)[0][0]10000 loops, best of 3:141 us per loopIn[6]: timeit np.nonzero(aa>N/2)[0][0]10000 loops, best of 3:142 us per loop
Nur ein Wort der Vorsicht: Wenn das Eingabearray keinen True-Wert enthält, gibt np.argmax gerne 0 zurück (was in diesem Fall nicht gewünscht ist).
Ambrus
8
Die Ergebnisse sind korrekt, aber ich finde die Erklärung etwas verdächtig. argmaxscheint beim ersten nicht aufzuhören True. (Dies kann getestet werden, indem boolesche Arrays mit einem einzelnen Truean verschiedenen Positionen erstellt werden.) Die Geschwindigkeit wird wahrscheinlich durch die Tatsache erklärt, dass argmaxkeine Ausgabeliste erstellt werden muss.
DrV
1
Ich denke du hast recht, @DrV. Meine Erklärung sollte sein, warum es das richtige Ergebnis liefert, obwohl die ursprüngliche Absicht nicht wirklich ein Maximum anstrebt, nicht warum es schneller ist, da ich nicht behaupten kann, die inneren Details von zu verstehen argmax.
Askewchan
1
@ George, ich fürchte ich weiß nicht warum genau. Ich kann nur sagen, dass es in dem Beispiel, das ich gezeigt habe, schneller ist, daher würde ich es im Allgemeinen nicht als schneller betrachten, ohne (i) zu wissen, warum es so ist (siehe Kommentar von @ DrV) oder (ii) mehr Fälle zu testen (z. B. ob aasortiert ist, wie in @ Michaels Antwort).
Askewchan
2
@DrV, ich habe gerade argmax10 Millionen-Elemente-Boolesche Arrays mit einem einzigen Truean verschiedenen Positionen unter Verwendung von NumPy 1.11.2 und der Position des TrueBetroffenen ausgeführt. 1.11.2 argmaxscheint also auf Booleschen Arrays "kurzzuschließen".
Ulrich Stern
95
Angesichts des sortierten Inhalts Ihres Arrays gibt es eine noch schnellere Methode: durchsucht .
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 loop10000 loops, best of 3:46.3µs per loop10000 loops, best of 3:154µs per loop10000 loops, best of 3:154µs per loop
Dies ist wirklich die beste Antwort, vorausgesetzt, das Array ist sortiert (was in der Frage nicht angegeben ist). Sie können das unangenehme +1mitnp.searchsorted(..., side='right')
askewchan
3
Ich denke, das sideArgument macht nur dann einen Unterschied, wenn das sortierte Array wiederholte Werte enthält. Die Bedeutung des zurückgegebenen Index wird nicht geändert. Dies ist immer der Index, bei dem Sie den Abfragewert einfügen können, indem Sie alle folgenden Einträge nach rechts verschieben und ein sortiertes Array beibehalten.
Gus
@Gus sidewirkt sich aus, wenn derselbe Wert sowohl im sortierten als auch im eingefügten Array vorhanden ist, unabhängig von wiederholten Werten in beiden. Wiederholte Werte im sortierten Array übertreiben den Effekt nur (der Unterschied zwischen den Seiten gibt an, wie oft der eingefügte Wert im sortierten Array angezeigt wird). sidenicht ändern , um die Bedeutung des zurückgegebenen Index, obwohl es nicht das resultierende Array von ändert an diesen Indizes die Werte in der sortierten Array eingefügt wird . Eine subtile, aber wichtige Unterscheidung; Tatsächlich gibt diese Antwort den falschen Index an, wenn N/2nicht in aa.
Askewchan
Wie im obigen Kommentar angedeutet, ist diese Antwort um eins deaktiviert, wenn sie N/2nicht aktiviert ist aa. Die richtige Form wäre np.searchsorted(aa, N/2, side='right')(ohne die +1). Ansonsten geben beide Formen den gleichen Index an. Betrachten Sie den Testfall Nals ungerade (und N/2.0um Float zu erzwingen, wenn Sie Python 2 verwenden).
Askewchan
21
Das hat mich auch interessiert und ich habe alle vorgeschlagenen Antworten mit perfplot verglichen . (Haftungsausschluss: Ich bin der Autor von perfplot.)
Wenn Sie wissen, dass das Array, das Sie durchsuchen, bereits sortiert ist , dann
numpy.searchsorted(a, alpha)
ist für Sie. Es handelt sich um eine Operation mit konstanter Zeit, dh die Geschwindigkeit hängt nicht von der Größe des Arrays ab. Schneller geht es nicht.
Wenn Sie nichts über Ihr Array wissen, können Sie nichts falsch machen
np.searchsortedist keine konstante Zeit. Es ist tatsächlich O(log(n)). Aber Ihr Testfall misst tatsächlich den besten Fall von searchsorted(was ist O(1)).
MSeifert
@MSeifert Welche Art von Eingabearray / Alpha benötigen Sie, um O (log (n)) zu sehen?
Nico Schlömer
1
Das Abrufen des Elements auf den Index sqrt (Länge) führte zu einer sehr schlechten Leistung. Ich habe hier auch eine Antwort geschrieben , einschließlich dieses Benchmarks.
MSeifert
Ich bezweifle, dass searchsorted(oder irgendein Algorithmus) die O(log(n))einer binären Suche nach sortierten gleichmäßig verteilten Daten übertreffen kann . BEARBEITEN: searchsortedist eine binäre Suche.
Mateen Ulhaq
16
In[34]: a=np.arange(-10,10)In[35]: a
Out[35]:
array([-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9])In[36]: np.where(a>5)Out[36]:(array([16,17,18,19]),)In[37]: np.where(a>5)[0][0]Out[37]:16
Arrays, die einen konstanten Schritt zwischen Elementen haben
Im Falle eines rangeoder eines anderen linear ansteigenden Arrays können Sie den Index einfach programmgesteuert berechnen, ohne dass Sie das Array tatsächlich durchlaufen müssen:
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
Das könnte man wohl etwas verbessern. Ich habe sichergestellt, dass es für einige Beispiel-Arrays und -Werte korrekt funktioniert, aber das bedeutet nicht, dass dort keine Fehler auftreten können, insbesondere wenn man bedenkt, dass Floats verwendet werden ...
Da es die Position ohne Iteration berechnen kann, ist es eine konstante Zeit ( O(1)) und kann wahrscheinlich alle anderen genannten Ansätze übertreffen. Es erfordert jedoch einen konstanten Schritt im Array, da sonst falsche Ergebnisse erzielt werden.
Allgemeine Lösung mit numba
Ein allgemeinerer Ansatz wäre die Verwendung einer Numba-Funktion:
Obwohl Nico Schlömer bereits einige Benchmarks bereitgestellt hat, hielt ich es für nützlich, meine neuen Lösungen einzubeziehen und auf unterschiedliche "Werte" zu testen.
Der Testaufbau:
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
und die Diagramme wurden erstellt mit:
%matplotlib notebook
b.plot()
Artikel ist am Anfang
b = benchmark(
funcs,{2**i:MultiArgument([0, np.arange(2**i)])for i in range(2,20)},
argument_name="array size")
Die numba-Funktion funktioniert am besten, gefolgt von der Berechnungsfunktion und der suchsortierten Funktion. Die anderen Lösungen schneiden viel schlechter ab.
Artikel ist am Ende
b = benchmark(
funcs,{2**i:MultiArgument([2**i-2, np.arange(2**i)])for i in range(2,20)},
argument_name="array size")
Bei kleinen Arrays arbeitet die numba-Funktion erstaunlich schnell, bei größeren Arrays jedoch besser als die Berechnungsfunktion und die suchsortierte Funktion.
Artikel ist bei 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")
Das ist interessanter. Wiederum funktionieren numba und die Berechnungsfunktion hervorragend, dies löst jedoch tatsächlich den schlimmsten Fall einer Suchsortierung aus, der in diesem Fall wirklich nicht gut funktioniert.
Vergleich der Funktionen, wenn kein Wert die Bedingung erfüllt
Ein weiterer interessanter Punkt ist, wie sich diese Funktionen verhalten, wenn es keinen Wert gibt, dessen Index zurückgegeben werden soll:
arr = np.ones(100)
value =2for func in funcs:print(func.__name__)try:print('-->', func(value, arr))exceptExceptionas e:print('-->', e)
Mit diesem Ergebnis:
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 und numba geben einfach einen falschen Wert zurück. Jedoch searchsortedund numbaRück einen Index, der kein gültiger Index für das Array ist.
Die Funktionen where, min, nonzeround calculateeine Ausnahme werfen. Allerdings calculatesagt nur die Ausnahme für eigentlich etwas hilfreiches.
Das bedeutet, dass diese Aufrufe tatsächlich in eine geeignete Wrapper-Funktion eingeschlossen werden müssen, die Ausnahmen oder ungültige Rückgabewerte abfängt und entsprechend behandelt, zumindest wenn Sie nicht sicher sind, ob der Wert im Array enthalten sein könnte.
Hinweis: Die Berechnungs- und searchsortedOptionsoptionen funktionieren nur unter besonderen Bedingungen. Die "Berechnen" -Funktion erfordert einen konstanten Schritt und die Suchsortierung erfordert das Sortieren des Arrays. Diese könnten unter den richtigen Umständen nützlich sein, sind jedoch keine allgemeinen Lösungen für dieses Problem. Wenn Sie es mit sortierten Python-Listen zu tun haben, sollten Sie sich das Bisect- Modul ansehen, anstatt Numpys searchsorted zu verwenden.
Dies gibt den kleinsten Index zurück, in dem die Bedingung erfüllt ist, während unendlich zurückgegeben wird, wenn die Bedingung nie erfüllt ist (und whereein leeres Array zurückgibt).
Antworten:
Dies ist etwas schneller (und sieht besser aus)
Da
argmax
wird beim erstenTrue
Auftreten gestoppt ("Bei mehrfachem Auftreten der Maximalwerte werden die dem ersten Auftreten entsprechenden Indizes zurückgegeben.") Und speichert keine weitere Liste.quelle
argmax
scheint beim ersten nicht aufzuhörenTrue
. (Dies kann getestet werden, indem boolesche Arrays mit einem einzelnenTrue
an verschiedenen Positionen erstellt werden.) Die Geschwindigkeit wird wahrscheinlich durch die Tatsache erklärt, dassargmax
keine Ausgabeliste erstellt werden muss.argmax
.aa
sortiert ist, wie in @ Michaels Antwort).argmax
10 Millionen-Elemente-Boolesche Arrays mit einem einzigenTrue
an verschiedenen Positionen unter Verwendung von NumPy 1.11.2 und der Position desTrue
Betroffenen ausgeführt. 1.11.2argmax
scheint also auf Booleschen Arrays "kurzzuschließen".Angesichts des sortierten Inhalts Ihres Arrays gibt es eine noch schnellere Methode: durchsucht .
quelle
+1
mitnp.searchsorted(..., side='right')
side
Argument macht nur dann einen Unterschied, wenn das sortierte Array wiederholte Werte enthält. Die Bedeutung des zurückgegebenen Index wird nicht geändert. Dies ist immer der Index, bei dem Sie den Abfragewert einfügen können, indem Sie alle folgenden Einträge nach rechts verschieben und ein sortiertes Array beibehalten.side
wirkt sich aus, wenn derselbe Wert sowohl im sortierten als auch im eingefügten Array vorhanden ist, unabhängig von wiederholten Werten in beiden. Wiederholte Werte im sortierten Array übertreiben den Effekt nur (der Unterschied zwischen den Seiten gibt an, wie oft der eingefügte Wert im sortierten Array angezeigt wird).side
nicht ändern , um die Bedeutung des zurückgegebenen Index, obwohl es nicht das resultierende Array von ändert an diesen Indizes die Werte in der sortierten Array eingefügt wird . Eine subtile, aber wichtige Unterscheidung; Tatsächlich gibt diese Antwort den falschen Index an, wennN/2
nicht inaa
.N/2
nicht aktiviert istaa
. Die richtige Form wärenp.searchsorted(aa, N/2, side='right')
(ohne die+1
). Ansonsten geben beide Formen den gleichen Index an. Betrachten Sie den TestfallN
als ungerade (undN/2.0
um Float zu erzwingen, wenn Sie Python 2 verwenden).Das hat mich auch interessiert und ich habe alle vorgeschlagenen Antworten mit perfplot verglichen . (Haftungsausschluss: Ich bin der Autor von perfplot.)
Wenn Sie wissen, dass das Array, das Sie durchsuchen, bereits sortiert ist , dann
ist für Sie. Es handelt sich um eine Operation mit konstanter Zeit, dh die Geschwindigkeit hängt nicht von der Größe des Arrays ab. Schneller geht es nicht.
Wenn Sie nichts über Ihr Array wissen, können Sie nichts falsch machen
Bereits sortiert:
Unsortiert:
Code zur Reproduktion der Handlung:
quelle
np.searchsorted
ist keine konstante Zeit. Es ist tatsächlichO(log(n))
. Aber Ihr Testfall misst tatsächlich den besten Fall vonsearchsorted
(was istO(1)
).searchsorted
(oder irgendein Algorithmus) dieO(log(n))
einer binären Suche nach sortierten gleichmäßig verteilten Daten übertreffen kann . BEARBEITEN:searchsorted
ist eine binäre Suche.quelle
Arrays, die einen konstanten Schritt zwischen Elementen haben
Im Falle eines
range
oder eines anderen linear ansteigenden Arrays können Sie den Index einfach programmgesteuert berechnen, ohne dass Sie das Array tatsächlich durchlaufen müssen:Das könnte man wohl etwas verbessern. Ich habe sichergestellt, dass es für einige Beispiel-Arrays und -Werte korrekt funktioniert, aber das bedeutet nicht, dass dort keine Fehler auftreten können, insbesondere wenn man bedenkt, dass Floats verwendet werden ...
Da es die Position ohne Iteration berechnen kann, ist es eine konstante Zeit (
O(1)
) und kann wahrscheinlich alle anderen genannten Ansätze übertreffen. Es erfordert jedoch einen konstanten Schritt im Array, da sonst falsche Ergebnisse erzielt werden.Allgemeine Lösung mit numba
Ein allgemeinerer Ansatz wäre die Verwendung einer Numba-Funktion:
Das funktioniert für jedes Array, muss jedoch über das Array iteriert werden. Im Durchschnitt ist dies also
O(n)
:Benchmark
Obwohl Nico Schlömer bereits einige Benchmarks bereitgestellt hat, hielt ich es für nützlich, meine neuen Lösungen einzubeziehen und auf unterschiedliche "Werte" zu testen.
Der Testaufbau:
und die Diagramme wurden erstellt mit:
Artikel ist am Anfang
Die numba-Funktion funktioniert am besten, gefolgt von der Berechnungsfunktion und der suchsortierten Funktion. Die anderen Lösungen schneiden viel schlechter ab.
Artikel ist am Ende
Bei kleinen Arrays arbeitet die numba-Funktion erstaunlich schnell, bei größeren Arrays jedoch besser als die Berechnungsfunktion und die suchsortierte Funktion.
Artikel ist bei sqrt (len)
Das ist interessanter. Wiederum funktionieren numba und die Berechnungsfunktion hervorragend, dies löst jedoch tatsächlich den schlimmsten Fall einer Suchsortierung aus, der in diesem Fall wirklich nicht gut funktioniert.
Vergleich der Funktionen, wenn kein Wert die Bedingung erfüllt
Ein weiterer interessanter Punkt ist, wie sich diese Funktionen verhalten, wenn es keinen Wert gibt, dessen Index zurückgegeben werden soll:
Mit diesem Ergebnis:
Searchsorted, argmax und numba geben einfach einen falschen Wert zurück. Jedoch
searchsorted
undnumba
Rück einen Index, der kein gültiger Index für das Array ist.Die Funktionen
where
,min
,nonzero
undcalculate
eine Ausnahme werfen. Allerdingscalculate
sagt nur die Ausnahme für eigentlich etwas hilfreiches.Das bedeutet, dass diese Aufrufe tatsächlich in eine geeignete Wrapper-Funktion eingeschlossen werden müssen, die Ausnahmen oder ungültige Rückgabewerte abfängt und entsprechend behandelt, zumindest wenn Sie nicht sicher sind, ob der Wert im Array enthalten sein könnte.
Hinweis: Die Berechnungs- und
searchsorted
Optionsoptionen funktionieren nur unter besonderen Bedingungen. Die "Berechnen" -Funktion erfordert einen konstanten Schritt und die Suchsortierung erfordert das Sortieren des Arrays. Diese könnten unter den richtigen Umständen nützlich sein, sind jedoch keine allgemeinen Lösungen für dieses Problem. Wenn Sie es mit sortierten Python-Listen zu tun haben, sollten Sie sich das Bisect- Modul ansehen, anstatt Numpys searchsorted zu verwenden.quelle
Ich würde gerne vorschlagen
Dies gibt den kleinsten Index zurück, in dem die Bedingung erfüllt ist, während unendlich zurückgegeben wird, wenn die Bedingung nie erfüllt ist (und
where
ein leeres Array zurückgibt).quelle
Ich würde mit gehen
Dabei
V
ist der Vektor (1d-Array)x
der Wert undi
der resultierende Index.quelle