È possibile utilizzare argsort in ordine decrescente?


181

Considera il seguente codice:

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

Questo mi dà indici degli nelementi più piccoli. È possibile utilizzare questo stesso argsortin ordine decrescente per ottenere gli indici degli nelementi più alti?


3
Non è semplicemente ids = np.array(avgDists).argsort()[-n:]?
Jaime,

2
@Jaime: No, non funziona. 'risposta giusta' è [3, 1, 2]. La tua linea produce [2, 1, 3](se n == 3 come esempio)
dawg

2
@drewk Bene, allora fallo ids = np.array(avgDists).argsort()[-n:][::-1]. La cosa è evitare di fare una copia dell'intero elenco, che è ciò che ottieni quando aggiungi un -davanti. Non pertinente per il piccolo esempio del PO, potrebbe essere per casi più grandi.
Jaime,

1
@Jaime: hai ragione. Vedi la mia risposta aggiornata. La sintassi è esattamente opposta al tuo commento sulla porzione finale: np.array(avgDists).argsort()[::-1][:n]lo farà. Inoltre, se hai intenzione di usare numpy, rimani in numpy. Prima converti l'elenco in un array: avgDist=np.array(avgDists)poi diventaavgDist.argsort()[::-1][:n}
dawg

Risposte:


230

Se si annulla una matrice, gli elementi più bassi diventano gli elementi più alti e viceversa. Pertanto, gli indici degli nelementi più alti sono:

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

Un altro modo di ragionare su questo, come menzionato nei commenti , è osservare che i grandi elementi stanno arrivando per ultimi nell'argsort. Quindi, puoi leggere dalla coda dell'argsort per trovare gli nelementi più alti:

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

Entrambi i metodi sono O (n log n) nella complessità temporale, perché qui la argsortchiamata è il termine dominante. Ma il secondo approccio ha un bel vantaggio: sostituisce una negazione O (n) dell'array con una sezione O (1) . Se si lavora con array di piccole dimensioni all'interno di loop, è possibile che si ottengano alcuni miglioramenti delle prestazioni evitando tale negazione e se si utilizzano array di grandi dimensioni, è possibile risparmiare sull'utilizzo della memoria perché la negazione crea una copia dell'intero array.

Si noti che questi metodi non danno sempre risultati equivalenti: se viene richiesta un'implementazione dell'ordinamento stabile argsort, ad esempio passando l'argomento della parola chiave kind='mergesort', la prima strategia manterrà la stabilità dell'ordinamento, ma la seconda strategia romperà la stabilità (ovvero le posizioni di uguale gli articoli verranno invertiti).

Tempi di esempio:

Utilizzando un piccolo array di 100 galleggianti e una lunghezza di 30 code, il metodo di visualizzazione è stato di circa il 15% più veloce

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

Per array più grandi, l'argsort è dominante e non vi è alcuna differenza temporale significativa

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

Si prega di notare che il commento di nedim di seguito non è corretto. Il troncamento prima o dopo l'inversione non fa alcuna differenza in termini di efficienza, poiché entrambe queste operazioni stanno solo spostando una vista dell'array in modo diverso e non copiando effettivamente i dati.


14
È ancora più efficiente tagliare prima di invertire, cioènp.array(avgDists).argsort()[:-n][::-1]
nedim il

3
Queste risposte non sono equivalenti se l'array originale contiene nans. In tal caso, la prima soluzione sembra dare il risultato più naturale con nans alla fine piuttosto che all'inizio.
feilchenfeldt,

1
Come si confrontano quando si desidera un ordinamento stabile? Presumibilmente la strategia di taglio inverte gli oggetti uguali?
Eric

1
@ user3666197 Ho ritenuto che non fosse rilevante per la risposta. Se la negazione crea o meno una copia (non lo fa) qui non è davvero importante, le informazioni rilevanti sono che il calcolo della negazione è la complessità O (n) rispetto a prendere un'altra fetta che è O (1) .
mercoledì

1
@ user3666197 Sì, questo è un buon punto: se un array occupa il 50% di memoria disponibile, vorremmo sicuramente evitare di copiarlo e causare lo scambio. Modificherò di nuovo per menzionare che una copia viene creata lì.
mercoledì

70

Proprio come Python, in questo [::-1]inverte l'array restituito da argsort()e [:n]dà gli ultimi n elementi:

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

Il vantaggio di questo metodo è che idsè una vista di avgDists:

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

(Il 'OWNDATA' essendo False indica che questa è una vista, non una copia)

Un altro modo per farlo è qualcosa di simile:

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

Il problema è che il modo in cui funziona è creare un negativo per ogni elemento dell'array:

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

E crea una copia per fare ciò:

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

Quindi, se cronometri ciascuno, con questo set di dati molto piccolo:

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

Il metodo di visualizzazione è sostanzialmente più veloce (e utilizza 1/2 della memoria ...)


4
Questa risposta è buona, ma sento che il tuo testo travisa le reali caratteristiche prestazionali: "anche con questo set di dati molto piccolo, il metodo di visualizzazione è sostanzialmente più veloce" . In realtà, la negazione è O (n) e l'argsort è O (n log n) . Ciò significa che la discrepanza di temporizzazione diminuirà per set di dati più grandi - domina il termine O (n log n) , tuttavia il tuo suggerimento è un'ottimizzazione della parte O (n) . Quindi la complessità rimane la stessa ed è in particolare per questo piccolo set di dati che vediamo differenze significative.
mercoledì

2
La complessità asintoticamente equivalente può ancora significare che un algoritmo è asintoticamente due volte più veloce di un altro. Eliminare tali distinzioni può avere conseguenze. Ad esempio, anche se la discrepanza temporale (in percentuale) si avvicina a 0, sarei disposto a scommettere che l'algoritmo con negazione utilizza ancora il doppio della memoria.
bug

@bug Può, ma non in questo caso. Ho aggiunto alcuni tempi alla mia risposta. I numeri mostrano che per array più grandi questi approcci hanno tempistiche simili, il che supporta l'ipotesi che argsort sia dominante. Per negazione, immagino che tu abbia ragione sull'utilizzo della memoria, ma gli utenti potrebbero ancora preferire che se si preoccupano della posizione di nan e / o necessitano di un ordinamento stabile.
mercoledì

6

È possibile utilizzare i comandi di inversione numpy.flipud()o numpy.fliplr()per disporre gli indici in ordine decrescente dopo l'ordinamento mediante il argsortcomando. Questo è quello che faccio di solito.


È molto più lento dell'affettare stackoverflow.com/a/44921013/125507
endolith

5

Invece di usare np.argsortpotresti usare np.argpartition- se hai solo bisogno degli indici degli elementi n più bassi / più alti.

Ciò non richiede di ordinare l'intero array ma solo la parte di cui hai bisogno, ma nota che "l'ordine all'interno della tua partizione" non è definito, quindi mentre fornisce gli indici corretti potrebbero non essere ordinati correttamente:

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

Oppure, se si usano i due insieme, ovvero argsort e argpartition, l'operazione deve essere eseguita sull'operazione argpartition.
demongolem,

3

È possibile creare una copia dell'array e moltiplicare ciascun elemento con -1.
Di conseguenza, gli elementi più grandi prima sarebbero diventati i più piccoli.
Gli indizi degli n elementi più piccoli nella copia sono gli n elementi più grandi nell'originale.


questo viene fatto negando facilmente l'array, come indicato nelle altre risposte:-array
onofricamila,

2

Come ha suggerito @Kanmani, è possibile utilizzare un'implementazione più semplice da interpretare numpy.flip, come nel seguito:

import numpy as np

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

Utilizzando il modello visitatore anziché le funzioni membro, è più semplice leggere l'ordine delle operazioni.


1

Con il tuo esempio:

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

Ottieni indici di n valori massimi:

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

Ordinali in ordine decrescente:

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

Ottieni risultati (per n = 4):

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

-1

Un altro modo è usare solo un '-' nell'argomento per argsort come in: "df [np.argsort (-df [:, 0])]", a condizione che df sia il frame di dati e si desidera ordinarlo per primo colonna (rappresentata dal numero di colonna '0'). Modificare il nome della colonna come appropriato. Naturalmente, la colonna deve essere numerica.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.