Можно ли использовать argsort в порядке убывания?

181

Рассмотрим следующий код:

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

Это дает мне индексы самых nмаленьких элементов. Можно ли использовать это argsortв порядке убывания, чтобы получить индексы nстарших элементов?

SHN
источник
3
Разве это не просто ids = np.array(avgDists).argsort()[-n:]?
Хайме
2
@ Джейме: Нет, это не работает. «Правильный ответ» есть [3, 1, 2]. Ваша строка производит [2, 1, 3](если n == 3 в качестве примера)
Dawg
2
@drewk Ну, тогда сделай это ids = np.array(avgDists).argsort()[-n:][::-1]. Дело в том, чтобы избежать копирования всего списка, который вы получите, добавив -перед ним. Не относится к маленькому примеру ОП, может быть для больших случаев.
Хайме
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. Итак, вы можете прочитать из хвоста argsort, чтобы найти самые 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)

Для больших массивов, 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)

Обратите внимание, что комментарий от Недима ниже является неправильным. Обрезание до или после реверса не влияет на эффективность, так как обе эти операции только по-разному оценивают массив и не копируют данные.

Wim
источник
14
Еще более эффективно нарезать перед np.array(avgDists).argsort()[:-n][::-1]
реверсом
3
Эти ответы не эквивалентны, если исходный массив содержит nans. В таком случае, первое решение, кажется, дает более естественный результат с nans в конце, а не в начале.
feilchenfeldt
1
Как они сравниваются, когда желательна стабильная сортировка? Предположительно стратегия нарезки меняет равные позиции?
Эрик,
1
@ user3666197 Я чувствовал, что это не имеет отношения к ответу. Независимо от того, создает ли отрицание копию или нет (это делает), здесь не очень важно, важная информация состоит в том, что вычисление отрицания представляет собой сложность O (n) по сравнению с принятием другого среза, который является O (1) .
Вим
1
@ user3666197 Да, это хороший момент - если массив занимает 50% доступной памяти, мы, безусловно, захотим избежать его копирования и замены. Я снова отредактирую, чтобы упомянуть, что там создается копия.
Вим
70

Точно так же, как и Python, он [::-1]переворачивает массив, возвращаемый argsort()и [:n]дает последние 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

Метод просмотра значительно быстрее (и использует 1/2 памяти ...)

Dawg
источник
4
Этот ответ хорош, но я чувствую, что ваша формулировка искажает реальные характеристики производительности: «даже при этом очень небольшом наборе данных метод просмотра значительно быстрее» . В действительности отрицание - это O (n), а argsort - это O (n log n) . Это означает, что расхождение во времени уменьшится для больших наборов данных - доминирует термин O (n log n) , однако ваше предложение является оптимизацией части O (n) . Таким образом, сложность остается той же, и именно для этого небольшого набора данных, в частности , мы видим существенные различия.
Вим
2
Асимптотически эквивалентная сложность все еще может означать, что один алгоритм асимптотически в два раза быстрее другого. Отбрасывание таких различий может иметь последствия. Например, даже если расхождение во времени (в процентах) приближается к 0, я готов поспорить, что алгоритм с отрицанием все еще использует вдвое больше памяти.
ошибка
@bug Может, но не в этом случае. Я добавил несколько моментов в свой ответ. Числа показывают, что для больших массивов эти подходы имеют схожие временные характеристики, что подтверждает гипотезу о том, что argsort является доминирующим. Для отрицания я бы предположил, что вы правы в отношении использования памяти, но пользователи все равно могут предпочесть это, если им небезразлично положение нанов и / или нужна стабильная сортировка.
Вим
6

Вы можете использовать команды flip numpy.flipud()или numpy.fliplr()получить индексы в порядке убывания после сортировки с помощью argsortкоманды. Это то, что я обычно делаю.

Kanmani
источник
Это гораздо медленнее , чем нарезка 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)
MSeifert
источник
Или, если вы используете два вместе, то есть argsort и argpartition, операция должна быть выполнена над операцией argpartition.
Демонголем
3

Вы можете создать копию массива, а затем умножить каждый элемент на -1.
Как результат, ранее самые большие элементы стали бы самыми маленькими.
Индексы n самых маленьких элементов в копии - это n самых больших элементов в оригинале.

MentholBonbon
источник
это делается легко, отрицая массив, как указано в других ответах:-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'). Измените имя столбца соответствующим образом. Конечно, столбец должен быть числовым.

Бисваджит Гошал
источник