Чи можна використовувати аргсорт у порядку зменшення?


181

Розглянемо наступний код:

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

Це дає мені показники nнайменших елементів. Чи можна використовувати те саме argsortв порядку зменшення, щоб отримати показники nнайвищих елементів?


3
Чи не просто ids = np.array(avgDists).argsort()[-n:]?
Хайме

2
@Jaime: Ні, це не працює. "правильна відповідь" [3, 1, 2]. Ваш рядок створює [2, 1, 3](якщо n == 3 як приклад)
dawg

2
@drewk Ну, тоді зроби це ids = np.array(avgDists).argsort()[-n:][::-1]. Річ у тому, щоб уникати копії всього списку, що ви отримуєте, додаючи -перед ним. Не стосується невеликого прикладу ОП, це може бути для більш великих випадків.
Хайме

1
@Jaime: Ви маєте рацію. Дивіться мою оновлену відповідь. Синтаксис Тхо якраз протилежний вашому коментарю до кінця фрагмента: np.array(avgDists).argsort()[::-1][:n]зроби це. Крім того, якщо ви збираєтесь використовувати numpy, залишайтеся в numpy. Спочатку перетворіть список у масив: avgDist=np.array(avgDists)потім він стаєavgDist.argsort()[::-1][:n}
dawg

Відповіді:


230

Якщо ви заперечуєте масив, найнижчі елементи стають найвищими елементами і навпаки. Тому індексами nнайвищих елементів є:

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

Ще один спосіб аргументувати це, як згадувалося в коментарях , - спостерігати, що великі елементи стають останніми в арґорті. Отже, ви можете прочитати з хвоста аргсорту, щоб знайтиn найвищі елементи:

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

Обидва способи є O (n log n) за часовою складністю, оскільки argsortвиклик є домінуючим терміном тут. Але другий підхід має приємну перевагу: він замінює O (n) заперечення масиву на зріз O (1) . Якщо ви працюєте з невеликими масивами всередині циклів, то, можливо, ви отримаєте певну прибутковість від уникнення цього заперечення, а якщо ви працюєте з величезними масивами, ви можете заощадити на використанні пам'яті, оскільки заперечення створює копію всього масиву.

Зауважте, що ці методи не завжди дають еквівалентні результати: якщо вимагається стабільна реалізація сортування argsort , наприклад, передаючи аргумент ключового слова kind='mergesort', тоді перша стратегія збереже стабільність сортування, а друга стратегія порушить стабільність (тобто позиції рівні елементи будуть повернені).

Приклад часу:

Використовуючи невеликий масив із 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)

Для великих масивів домінуючий арґсорт є суттєвою різницею в часі

>>> 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
Ці відповіді не рівнозначні, якщо оригінальний масив містить нан. У такому випадку перше рішення, здається, дає більш природний результат з нанами в кінці, а не на початку.
feilchenfeldt

1
Як їх порівняти, коли бажаний стабільний сорт? Імовірно, стратегія нарізки повертає рівні предмети?
Ерік

1
@ user3666197 Я вважав, що відповідь не стосується. Незалежно від того, створює заперечення копію чи ні (це робить), насправді це не важливо, відповідна інформація полягає в тому, що обчислення заперечення є складністю O (n) порівняно з прийняттям іншого зрізу, який є O (1) .
Вім

1
@ user3666197 Так, це хороший момент - якщо масив займає 50% доступної пам’яті, ми, безумовно, хочемо уникати його копіювання і не спричинити заміну. Ще раз відредагую, щоб згадати, що там створюється копія.
Вім

70

Так само, як і Python, в цьому [::-1]повертається масив, повернутий 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" є помилковим - це перегляд, а не копія)

Ще один спосіб зробити це щось на кшталт:

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

Метод перегляду істотно швидший (і використовує 1/2 пам'яті ...)


4
Ця відповідь хороша, але я вважаю, що ваше формулювання неправильно відображає реальні характеристики продуктивності: "навіть при цьому дуже невеликий набір даних метод перегляду значно швидший" . Насправді заперечення дорівнює O (n), а аргумент - O (n log n) . Це означає, що розбіжність у часі зменшиться для більших наборів даних - термін O (n log n) домінує, однак ваша пропозиція є оптимізацією O (n) частини. Таким чином, складність залишається тим же самим , і це для цього невеликого набору даних , зокрема , що ми бачимо якісь - небудь істотні відмінності.
Вім

2
Асимптотично еквівалентна складність все ще може означати, що один алгоритм асимптотично вдвічі швидший за інший. Викидання таких відмінностей може мати наслідки. Наприклад, навіть якщо невідповідність часу (у відсотках) наближається до 0, я б хотів зробити ставку, що алгоритм із запереченням все ще використовує вдвічі більше пам’яті.
помилка

@bug Це може, але це не в цьому випадку. Я додав кілька моментів у свою відповідь. Цифри показують, що для більших масивів ці підходи мають подібні терміни, що підтверджує гіпотезу про домінуючий аргурт. Що стосується заперечення, я б припустив, що ви маєте рацію щодо використання пам'яті, але користувачі можуть все-таки віддати перевагу тому, якщо вони дбають про місце няні та / або потребують стабільного сортування.
Вім

6

Ви можете використовувати команди перевертання numpy.flipud()або numpy.fliplr()отримати індекси у порядку зменшення після сортування за допомогою argsortкоманди. Ось що я зазвичай роблю.


Це набагато повільніше , ніж нарізка stackoverflow.com/a/44921013/125507
ендоліти

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, операція повинна бути виконана в операції argpartition.
демонголем

3

Ви можете створити копію масиву, а потім помножити кожен елемент на -1.
Як результат, раніше найбільші елементи ставали б найменшими.
Індекси n найменших елементів у копії - це n найбільших елементів в оригіналі.


це робиться легко, відкидаючи масив, як зазначено в інших відповідях:-array
onofricamila

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

Як натякнув @Kanmani, реалізація реалізації може бути простішою numpy.flip, як у наступному:

import numpy as np

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

Використовуючи шаблон відвідувачів, а не функції членів, простіше читати порядок операцій.


-1

Інший спосіб - використовувати лише аргумент '-' в аргументі для argsort, як у: "df [np.argsort (-df [:, 0])]", за умови, що df є фреймом даних, і ви хочете сортувати його за першим стовпчик (представлений номером стовпця '0'). Змініть назву стовпця відповідно. Звичайно, стовпець повинен бути числовим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.