Ist es möglich, argsort in absteigender Reihenfolge zu verwenden?

180

Betrachten Sie den folgenden Code:

avgDists = np.array([1, 8, 6, 9, 4])
ids = avgDists.argsort()[:n]

Dies gibt mir Indizes der nkleinsten Elemente. Ist es möglich, dasselbe argsortin absteigender Reihenfolge zu verwenden, um die Indizes der nhöchsten Elemente zu erhalten?

shn
quelle
3
Ist es nicht einfach ids = np.array(avgDists).argsort()[-n:]?
Jaime
2
@ Jaime: Nein, das funktioniert nicht. "Richtige Antwort" ist [3, 1, 2]. Ihre Linie produziert [2, 1, 3](wenn n == 3 als Beispiel)
dawg
2
@rewk Nun, dann mach es ids = np.array(avgDists).argsort()[-n:][::-1]. Die Sache ist, zu vermeiden, eine Kopie der gesamten Liste zu erstellen, was Sie erhalten, wenn Sie eine -davor hinzufügen . Nicht relevant für das kleine Beispiel des OP, könnte für größere Fälle sein.
Jaime
1
@ Jaime: Du hast recht. Siehe meine aktualisierte Antwort. Die Syntax tho ist genau entgegengesetzt zu Ihrem Kommentar zum End-Slice: np.array(avgDists).argsort()[::-1][:n]wird es tun. Wenn Sie numpy verwenden möchten, bleiben Sie in numpy. Konvertieren Sie zuerst die Liste in ein Array: avgDist=np.array(avgDists)dann wird esavgDist.argsort()[::-1][:n}
dawg

Antworten:

226

Wenn Sie ein Array negieren, werden die niedrigsten Elemente zu den höchsten Elementen und umgekehrt. Daher sind die Indizes der nhöchsten Elemente:

(-avgDists).argsort()[:n]

Eine andere Möglichkeit, dies zu begründen, besteht, wie in den Kommentaren erwähnt , darin, zu beobachten, dass die großen Elemente im Argsort an letzter Stelle stehen. Sie können also am Ende des Argsorts lesen, um die nhöchsten Elemente zu finden :

avgDists.argsort()[::-1][:n]

Beide Methoden sind zeitliche Komplexität von O (n log n) , da der argsortAufruf hier der dominierende Begriff ist. Der zweite Ansatz hat jedoch einen schönen Vorteil: Er ersetzt eine O (n) -Negation des Arrays durch ein O (1) -Slice. Wenn Sie mit kleinen Arrays in Schleifen arbeiten, können Sie einige Leistungssteigerungen erzielen, wenn Sie diese Negation vermeiden. Wenn Sie mit großen Arrays arbeiten, können Sie Speicherplatz sparen, da durch die Negation eine Kopie des gesamten Arrays erstellt wird.

Beachten Sie, dass diese Methoden nicht immer gleichwertige Ergebnisse liefern: Wenn eine stabile Sortierimplementierung angefordert wird argsort, z. B. durch Übergeben des Schlüsselwortarguments kind='mergesort', behält die erste Strategie die Sortierstabilität bei, die zweite Strategie unterbricht jedoch die Stabilität (dh die Positionen gleich) Artikel werden umgekehrt).

Beispielzeiten:

Bei Verwendung einer kleinen Anordnung von 100 Schwimmern und einer Länge von 30 Schwanz war die Ansichtsmethode etwa 15% schneller

>>> avgDists = np.random.rand(100)
>>> n = 30
>>> timeit (-avgDists).argsort()[:n]
1.93 µs ± 6.68 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> timeit avgDists.argsort()[::-1][:n]
1.64 µs ± 3.39 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> timeit avgDists.argsort()[-n:][::-1]
1.64 µs ± 3.66 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Bei größeren Arrays ist der Argsort dominant und es gibt keinen signifikanten Zeitunterschied

>>> avgDists = np.random.rand(1000)
>>> n = 300
>>> timeit (-avgDists).argsort()[:n]
21.9 µs ± 51.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> timeit avgDists.argsort()[::-1][:n]
21.7 µs ± 33.3 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> timeit avgDists.argsort()[-n:][::-1]
21.9 µs ± 37.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Bitte beachten Sie, dass der Kommentar von nedim unten falsch ist. Ob vor oder nach dem Umkehren abgeschnitten werden soll, spielt keine Rolle für die Effizienz, da beide Vorgänge nur eine unterschiedliche Ansicht des Arrays anzeigen und keine Daten tatsächlich kopieren.

wim
quelle
14
Es ist noch effizienter, vor dem Rückwärtsfahren zu schneiden, dhnp.array(avgDists).argsort()[:-n][::-1]
nedim
3
Diese Antworten sind nicht äquivalent, wenn das ursprüngliche Array nans enthält. In einem solchen Fall scheint die erste Lösung das natürlichere Ergebnis mit nans am Ende und nicht am Anfang zu liefern.
Feilchenfeldt
1
Wie vergleichen sich diese, wenn eine stabile Sortierung gewünscht wird? Vermutlich kehrt die Schneidestrategie gleiche Posten um?
Eric
1
@ user3666197 Ich hatte das Gefühl, dass es für die Antwort nicht relevant ist. Ob die Negation eine Kopie erstellt oder nicht (dies ist der Fall), ist hier nicht wirklich wichtig. Die relevante Information ist, dass die Berechnung der Negation eine Komplexität von O (n) ist, während eine andere Schicht genommen wird, die O (1) ist .
wim
1
@ user3666197 Ja, das ist ein guter Punkt. Wenn ein Array 50% des verfügbaren Speichers belegt, möchten wir auf jeden Fall vermeiden, es zu kopieren und einen Austausch zu verursachen. Ich werde noch einmal bearbeiten, um zu erwähnen, dass dort eine Kopie erstellt wird.
wim
70

Genau wie bei Python [::-1]kehrt dies das von zurückgegebene Array um argsort()und [:n]gibt die letzten n Elemente an:

>>> avgDists=np.array([1, 8, 6, 9, 4])
>>> n=3
>>> ids = avgDists.argsort()[::-1][:n]
>>> ids
array([3, 1, 2])

Der Vorteil dieser Methode ist , dass idsa Ansicht von avgDists:

>>> ids.flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

(Wenn 'OWNDATA' falsch ist, ist dies eine Ansicht, keine Kopie.)

Ein anderer Weg, dies zu tun, ist so etwas wie:

(-avgDists).argsort()[:n]

Das Problem ist, dass dies so funktioniert, dass für jedes Element im Array ein Negativ erstellt wird:

>>> (-avgDists)
array([-1, -8, -6, -9, -4])

ANd erstellt dazu eine Kopie:

>>> (-avgDists_n).flags['OWNDATA']
True

Wenn Sie also jeweils eine Zeit festlegen, mit diesem sehr kleinen Datensatz:

>>> import timeit
>>> timeit.timeit('(-avgDists).argsort()[:3]', setup="from __main__ import avgDists")
4.2879798610229045
>>> timeit.timeit('avgDists.argsort()[::-1][:3]', setup="from __main__ import avgDists")
2.8372560259886086

Die Ansichtsmethode ist wesentlich schneller (und verwendet die Hälfte des Speichers ...)

dawg
quelle
4
Diese Antwort ist gut, aber ich fühle mich Ihre Formulierung misrepresents die wirklichen Leistungsmerkmale: „auch bei dieser sehr kleinen Datenmenge, das View - Verfahren schneller ist im Wesentlichen“ . In Wirklichkeit ist die Negation O (n) und der Argsort ist O (n log n) . Dies bedeutet, dass sich die zeitliche Diskrepanz bei größeren Datenmengen verringert - der O (n log n) -Term dominiert, Ihr Vorschlag ist jedoch eine Optimierung des O (n) -Teils. Die Komplexität bleibt also gleich, und insbesondere bei diesem kleinen Datensatz sehen wir signifikante Unterschiede.
wim
2
Asymptotisch äquivalente Komplexität kann immer noch bedeuten, dass ein Algorithmus asymptotisch doppelt so schnell ist wie ein anderer. Das Wegwerfen solcher Unterscheidungen kann Konsequenzen haben. Selbst wenn sich die Zeitdiskrepanz (in Prozent) 0 nähert, würde ich wetten, dass der Algorithmus mit Negation immer noch doppelt so viel Speicher benötigt.
Fehler
@bug Es kann, aber in diesem Fall nicht. Ich habe meiner Antwort einige Zeitangaben hinzugefügt. Die Zahlen zeigen, dass diese Ansätze für größere Arrays ähnliche Zeitpunkte haben, was die Hypothese stützt, dass Argsort dominant ist. Für die Verneinung würde ich vermuten, dass Sie mit der Speichernutzung Recht haben, aber Benutzer bevorzugen dies möglicherweise immer noch, wenn sie sich um die Position von Nans kümmern und / oder eine stabile Sortierung benötigen.
wim
6

Sie können die Flip-Befehle verwenden numpy.flipud()oder numpy.fliplr()die Indizes nach dem Sortieren mit dem argsortBefehl in absteigender Reihenfolge abrufen . Das mache ich normalerweise.

Kanmani
quelle
Das ist viel langsamer als das Schneiden von stackoverflow.com/a/44921013/125507
Endolith
5

Anstatt zu verwenden np.argsort, könnten Sie verwenden np.argpartition- wenn Sie nur die Indizes der niedrigsten / höchsten n Elemente benötigen.

Dazu muss nicht das gesamte Array sortiert werden, sondern nur der Teil, den Sie benötigen. Beachten Sie jedoch, dass die "Reihenfolge innerhalb Ihrer Partition" undefiniert ist. Obwohl sie die richtigen Indizes enthält, sind sie möglicherweise nicht richtig geordnet:

>>> avgDists = [1, 8, 6, 9, 4]
>>> np.array(avgDists).argpartition(2)[:2]  # indices of lowest 2 items
array([0, 4], dtype=int64)

>>> np.array(avgDists).argpartition(-2)[-2:]  # indices of highest 2 items
array([1, 3], dtype=int64)
MSeifert
quelle
Wenn Sie beide zusammen verwenden, dh argsort und argpartition, muss die Operation für die argpartition-Operation ausgeführt werden.
Demongolem
3

Sie können eine Kopie des Arrays erstellen und dann jedes Element mit -1 multiplizieren.
Infolgedessen würden die vorher größten Elemente die kleinsten werden.
Die Unabhängigkeiten der n kleinsten Elemente in der Kopie sind die n größten Elemente im Original.

MentholBonbon
quelle
Dies geschieht leicht, indem das Array negiert wird, wie in den anderen Antworten angegeben:-array
onofricamila
2

Wie @Kanmani angedeutet hat, kann eine einfacher zu interpretierende Implementierung verwendet werden numpy.flip, wie im Folgenden:

import numpy as np

avgDists = np.array([1, 8, 6, 9, 4])
ids = np.flip(np.argsort(avgDists))
print(ids)

Durch die Verwendung des Besuchermusters anstelle von Mitgliedsfunktionen ist es einfacher, die Reihenfolge der Vorgänge zu lesen.

Adam Erickson
quelle
1

Mit Ihrem Beispiel:

avgDists = np.array([1, 8, 6, 9, 4])

Erhalten Sie Indizes von n Maximalwerten:

ids = np.argpartition(avgDists, -n)[-n:]

Sortieren Sie sie in absteigender Reihenfolge:

ids = ids[np.argsort(avgDists[ids])[::-1]]

Ergebnisse erhalten (für n = 4):

>>> avgDists[ids]
array([9, 8, 6, 4])
Alexey Antonenko
quelle
-1

Eine andere Möglichkeit besteht darin, im Argument für argsort nur ein '-' zu verwenden, wie in: "df [np.argsort (-df [:, 0])]", vorausgesetzt, df ist der Datenrahmen und Sie möchten ihn nach dem ersten sortieren Spalte (dargestellt durch die Spaltennummer '0'). Ändern Sie den Spaltennamen entsprechend. Natürlich muss die Spalte eine numerische sein.

Biswajit Ghoshal
quelle