argsortを降順で使用できますか?


181

次のコードを検討してください。

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

これにより、n最小の要素のインデックスが得られます。これargsortを降順で使用して、n最高の要素のインデックスを取得することは可能ですか?


3
単純ではないids = np.array(avgDists).argsort()[-n:]ですか?
ハイメ2013年

2
@ハイメ:いいえ、それはうまくいきません。「正解」です[3, 1, 2]。あなたの行は[2, 1, 3](例としてn == 3の場合)
dawg

2
@drewkまあ、それからそれを作るids = np.array(avgDists).argsort()[-n:][::-1]。問題は、リスト全体のコピーを作成しないことです。これは、リストの-前にを追加したときに得られるものです。OPの小さな例には関係ありませんが、大きなケースの場合もあります。
Jaime 2013年

1
@ハイメ:あなたは正しいです。私の更新された答えを見てください。構文thoは、最後のスライスに関するコメントとは正反対np.array(avgDists).argsort()[::-1][:n]です。また、numpyを使用する場合は、numpyのままにします。最初にリストを配列に変換します。avgDist=np.array(avgDists)それが次のようになりますavgDist.argsort()[::-1][:n}
dawg

回答:


230

配列を否定すると、最低の要素が最高の要素になり、逆も同様です。したがって、n最も高い要素のインデックスは次のとおりです。

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

コメントで言及されているように、これを推論する別の方法は、大きな要素が引数ソートの最後に来ることを観察することです。したがって、argsortの末尾から読み取って、n最高の要素を見つけることができます。

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

ここでは呼び出しが主な用語であるため、どちらの方法も時間の複雑さではO(n log n)argsortです。しかし、2番目のアプローチには優れた利点があります。それは、配列のO(n)否定をO(1)スライスに置き換えることです。ループ内で小さな配列を操作している場合は、その否定を回避することでパフォーマンスが向上する可能性があります。また、巨大な配列を操作している場合は、否定によって配列全体のコピーが作成されるため、メモリ使用量を節約できます。

これらのメソッドが常に同等の結果をもたらすとは限らないことに注意してくださいargsort。たとえば、キーワード引数を渡すことによって安定したソートの実装が要求されたkind='mergesort'場合、最初の戦略はソートの安定性を維持しますが、2番目の戦略は安定性を壊します(つまり、等しい位置アイテムは逆になります)。

タイミングの例:

100個のフロートと長さ30の尾の小さな配列を使用すると、表示方法は約15%速くなりました

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

より大きな配列の場合、argsortが支配的であり、大きなタイミングの違いはありません。

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

以下のnedimからのコメントは正しくないことに注意しください。これらの操作は両方とも配列のビューを別々にストライドするだけで、実際にはデータをコピーしないため、リバースの前後で切り捨てるかどうかは効率に違いはありません。


14
これは、反転前のスライスに、より効率的である、すなわち、np.array(avgDists).argsort()[:-n][::-1]
ネディム

3
元の配列にnanが含まれている場合、これらの答えは等しくありません。このような場合、最初の解決策では、ナンが最初ではなく最後にある方がより自然な結果が得られるようです。
feilchenfeldt 2017年

1
安定したソートが必要な場合、これらはどのように比較されますか?おそらくスライシング戦略は等しいアイテムを逆にしますか?
エリック

1
@ user3666197答えとは無関係だと感じました。否定によってコピーが作成されるかどうか(作成されるかどうか)はここではそれほど重要ではありませんが、関連情報は、否定の計算がO(n)の複雑さに対してO(1)である別のスライスを取ることです。
2017

1
@ user3666197はい、それは良い点です-配列が50%の利用可能なメモリを使用している場合、それをコピーしてスワッピングを引き起こさないようにする必要があります。もう一度編集して、そこにコピーが作成されることを述べます。
2017

70

Pythonのように、[::-1]によって返された配列を逆にし、最後のn個の要素argsort()[:n]与えます。

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

この方法の利点は、つまり、idsあるビュー avgDistsの:

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

(「OWNDATA」がFalseの場合、これはビューであり、コピーではないことを示します)

これを行う別の方法は次のようなものです:

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

問題は、これが機能する方法が、配列内の各要素の負を作成することであるということです。

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

ANdはそのためのコピーを作成します。

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

したがって、それぞれの時間を計ると、この非常に小さなデータセットで:

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

表示方法はかなり高速です(メモリを半分使用します...)


4
この答えは良いですが、あなたの言葉遣いは実際のパフォーマンス特性を誤って表現しているように感じます。「この非常に小さなデータセットでも、viewメソッドはかなり高速です」。実際には、否定はO(n)であり、引数ソートはO(n log n)です。これは、データセットが大きいほどタイミングのずれが少なくなることを意味します。O(n log n)項が支配的ですが、O(n)部分の最適化が推奨されます。そのため、複雑さは同じままであり、特にこの小さなデータセットについて、大きな違いが見られます。
2015

2
漸近的に同等の複雑さは、あるアルゴリズムが別のアルゴリズムの漸近的に2倍の速さであることを意味します。そのような区別を捨てると、結果が生じる可能性があります。たとえば、時間の不一致(パーセンテージとして)が0に近づいたとしても、否定のアルゴリズムはまだ2倍のメモリを使用することを望んでいます。
バグ

@bugできますが、この場合はできません。回答にいくつかのタイミングを追加しました。数値は、より大きなアレイの場合、これらのアプローチには同様のタイミングがあることを示しており、argsortが支配的であるという仮説をサポートしています。否定については、私はあなたがメモリ使用量について正しいと思いますが、ナンの位置を気にしたり、安定したソートが必要な場合は、ユーザーはそれを好むかもしれません。
WIM

6

フリップコマンドを使用するnumpy.flipud()numpy.fliplr()argsortコマンドを使用してソートした後、インデックスを降順で取得できます。それは私が通常行うことです。


これは、スライスよりもはるかに遅いですstackoverflow.com/a/44921013/125507
endolith

5

を使用np.argsortする代わりに、使用することができますnp.argpartition-最小/最大のn要素のインデックスのみが必要な場合。

配列全体を並べ替える必要はありませんが、必要な部分だけを並べ替える必要がありますが、「パーティション内の順序」は定義されていないため、正しいインデックスを提供しますが、正しく順序付けされない場合があります。

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

または、argsortとargpartitionの2つを一緒に使用している場合、その操作はargpartition操作で実行する必要があります。
demongolem

3

配列のコピーを作成し、各要素に-1を掛けることができます。
効果として、以前最大の要素が最小になります。
コピー内の最小のn個の要素のインデックスは、元の最大のn個の要素です。


:これは、他の回答で述べたように簡単に、配列を否定行われている-array
onofricamila

2

@Kanmaniが示唆numpy.flipしたように、次のように、実装を解釈するのがより簡単な場合はを使用できます。

import numpy as np

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

メンバー関数ではなくビジターパターンを使用することで、操作の順序を簡単に読み取ることができます。


1

あなたの例で:

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

n個の最大値のインデックスを取得します。

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

降順に並べ替えます。

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

結果を取得する(n = 4の場合):

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

-1

もう1つの方法は、argsortの引数に「-」のみを使用することです。 "df [np.argsort(-df [:, 0])]"、ただし、dfがデータフレームであり、最初にソートしたい場合列(列番号「0」で表されます)。必要に応じて列名を変更します。もちろん、列は数値である必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.