Est-il possible d'utiliser argsort dans l'ordre décroissant?

181

Considérez le code suivant:

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

Cela me donne des indices des nplus petits éléments. Est-il possible d'utiliser la même chose argsortdans l'ordre décroissant pour obtenir les indices des néléments les plus élevés?

shn
la source
3
N'est-ce pas simplement ids = np.array(avgDists).argsort()[-n:]?
Jaime
2
@Jaime: Non, ça ne marche pas. «bonne réponse» est [3, 1, 2]. Votre ligne produit [2, 1, 3](si n == 3 à titre d'exemple)
dawg
2
@drewk Eh bien, alors fais-le ids = np.array(avgDists).argsort()[-n:][::-1]. Le problème est d'éviter de faire une copie de la liste entière, ce que vous obtenez lorsque vous ajoutez un -devant. Non pertinent pour le petit exemple du PO, pourrait l'être pour des cas plus importants.
Jaime
1
@Jaime: Vous avez raison. Voir ma réponse mise à jour. La syntaxe est juste à l'opposé de votre commentaire sur la tranche de fin: np.array(avgDists).argsort()[::-1][:n]va le faire. De plus, si vous comptez utiliser numpy, restez dans numpy. Commencez par convertir la liste en tableau: avgDist=np.array(avgDists)puis elle devientavgDist.argsort()[::-1][:n}
dawg

Réponses:

230

Si vous annulez un tableau, les éléments les plus bas deviennent les éléments les plus élevés et vice-versa. Par conséquent, les indices des néléments les plus élevés sont:

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

Une autre façon de raisonner à ce sujet, comme mentionné dans les commentaires , est d'observer que les gros éléments arrivent en dernier dans l'argsort. Ainsi, vous pouvez lire à partir de la queue de l'argument pour trouver les néléments les plus élevés:

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

Les deux méthodes sont O (n log n) en complexité temporelle, car l' argsortappel est ici le terme dominant. Mais la seconde approche a un bel avantage: elle remplace une négation O (n) du tableau par un O (1) tranche . Si vous travaillez avec de petits tableaux à l'intérieur de boucles, vous pouvez obtenir des gains de performances en évitant cette négation, et si vous travaillez avec d'énormes tableaux, vous pouvez économiser sur l'utilisation de la mémoire car la négation crée une copie de l'ensemble du tableau.

Notez que ces méthodes ne donnent pas toujours des résultats équivalents: si une implémentation de tri stable est demandée argsort, par exemple en passant l'argument mot-clé kind='mergesort', alors la première stratégie préservera la stabilité du tri, mais la deuxième stratégie cassera la stabilité (c'est-à-dire les positions égales les éléments seront inversés).

Exemple d'horaires:

En utilisant un petit tableau de 100 flotteurs et une longueur de 30 queue, la méthode de visualisation était environ 15% plus rapide

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

Pour les tableaux plus grands, le tri d'argument est dominant et il n'y a pas de différence de temps significative

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

Veuillez noter que le commentaire de nedim ci-dessous est incorrect. Le fait de tronquer avant ou après l'inversion ne fait aucune différence en termes d'efficacité, car ces deux opérations ne font que parcourir différemment une vue du tableau et ne copient pas réellement les données.

wim
la source
14
Il est encore plus efficace de couper avant l'inversion, c'est-à-direnp.array(avgDists).argsort()[:-n][::-1]
nedim
3
Ces réponses ne sont pas équivalentes si le tableau d'origine contient nans. Dans un tel cas, la première solution semble donner le résultat le plus naturel avec nans à la fin plutôt qu'au début.
feilchenfeldt
1
Comment se comparent-ils lorsqu'un tri stable est souhaité? Vraisemblablement, la stratégie de découpage inverse les éléments égaux?
Eric
1
@ user3666197 J'ai senti que ce n'était pas pertinent pour la réponse. Que la négation crée une copie ou non (elle le fait) n'est pas vraiment importante ici, l'information pertinente est que le calcul de la négation est une complexité O (n) vs prendre une autre tranche qui est O (1) .
wim
1
@ user3666197 Oui, c'est un bon point - si un tableau occupe 50% de la mémoire disponible, nous voudrons certainement éviter de le copier et de provoquer un échange. Je modifierai à nouveau pour mentionner qu'une copie y est créée.
wim
70

Tout comme Python, en cela [::-1]inverse le tableau retourné par argsort()et [:n]donne les n derniers éléments:

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

L'avantage de cette méthode est qu'il idss'agit d'une vue des avgDists:

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

(Le 'OWNDATA' étant False indique qu'il s'agit d'une vue, pas d'une copie)

Une autre façon de faire est quelque chose comme:

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

Le problème est que la façon dont cela fonctionne est de créer un négatif de chaque élément du tableau:

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

ANd crée une copie pour ce faire:

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

Donc, si vous chronométrez chacun, avec ce très petit ensemble de données:

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

La méthode d'affichage est nettement plus rapide (et utilise 1/2 de la mémoire ...)

dawg
la source
4
Cette réponse est bonne, mais je pense que votre formulation déforme les caractéristiques de performances réelles: «même avec ce très petit ensemble de données, la méthode de visualisation est nettement plus rapide» . En réalité, la négation est O (n) et le tri d'arguments est O (n log n) . Cela signifie que le décalage de synchronisation diminuera pour les ensembles de données plus volumineux - le terme O (n log n) domine, mais votre suggestion est une optimisation de la partie O (n) . La complexité reste donc la même, et c'est pour ce petit ensemble de données en particulier que nous constatons des différences significatives.
wim
2
Une complexité asymptotiquement équivalente peut encore signifier qu'un algorithme est asymptotiquement deux fois plus rapide qu'un autre. Jeter de telles distinctions peut avoir des conséquences. Par exemple, même si l'écart de temps (en pourcentage) approche 0, je serais prêt à parier que l'algorithme avec négation utilise toujours deux fois plus de mémoire.
bug
@bug C'est possible, mais ce n'est pas le cas dans ce cas. J'ai ajouté quelques timings à ma réponse. Les chiffres montrent que pour des tableaux plus grands, ces approches ont des timings similaires, ce qui soutient l'hypothèse selon laquelle argsort est dominant. Pour la négation, je suppose que vous avez raison sur l'utilisation de la mémoire, mais les utilisateurs peuvent toujours préférer cela s'ils se soucient de la position des nan et / ou ont besoin d'un tri stable.
wim le
6

Vous pouvez utiliser les commandes d'inversion numpy.flipud()ou numpy.fliplr()pour obtenir les index dans l'ordre décroissant après le tri à l'aide de la argsortcommande. C'est ce que je fais habituellement.

Kanmani
la source
C'est beaucoup plus lent que de trancher stackoverflow.com/a/44921013/125507
endolith
5

Au lieu d'utiliser, np.argsortvous pouvez utiliser np.argpartition- si vous n'avez besoin que des indices des n éléments les plus bas / les plus élevés.

Cela ne nécessite pas de trier tout le tableau mais juste la partie dont vous avez besoin, mais notez que "l'ordre à l'intérieur de votre partition" n'est pas défini, donc bien qu'il donne les bons indices, ils peuvent ne pas être correctement ordonnés:

>>> 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
la source
Ou, si vous utilisez les deux ensemble, c'est-à-dire argsort et argpartition, l'opération doit être effectuée sur l'opération argpartition.
demongolem
3

Vous pouvez créer une copie du tableau, puis multiplier chaque élément par -1.
En conséquence, les éléments avant les plus grands deviendraient les plus petits.
Les indéces des n plus petits éléments de la copie sont les n plus grands éléments de l'original.

MentholBonbon
la source
cela se fait facilement en annulant le tableau, comme indiqué dans les autres réponses:-array
onofricamila
1

Avec votre exemple:

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

Obtenez des index de n valeurs maximales:

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

Triez-les par ordre décroissant:

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

Obtenez des résultats (pour n = 4):

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

Comme @Kanmani l'a laissé entendre, une implémentation plus facile à interpréter peut être utilisée numpy.flip, comme dans l'exemple suivant:

import numpy as np

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

En utilisant le modèle de visiteur plutôt que les fonctions membres, il est plus facile de lire l'ordre des opérations.

Adam Erickson
la source
-1

Une autre façon consiste à n'utiliser qu'un '-' dans l'argument pour argsort comme dans: "df [np.argsort (-df [:, 0])]", à condition que df soit le dataframe et que vous vouliez le trier par le premier colonne (représentée par le numéro de colonne «0»). Modifiez le nom de la colonne selon vos besoins. Bien entendu, la colonne doit être numérique.

Biswajit Ghoshal
la source