Как получить индексы N максимальных значений в массиве NumPy?

485

NumPy предлагает способ получить индекс максимального значения массива через np.argmax.

Я хотел бы подобное, но возвращая индексы Nмаксимальных значений.

Например, если у меня есть массив, [1, 3, 2, 4, 5], function(array, n=3)будет возвращать индексы , [4, 3, 1]которые соответствуют элементам [5, 4, 3].

Алексис Метеро
источник
4
Ваш вопрос не очень четко определен. Например, что бы показатели (вы ожидаете) , чтобы быть для array([5, 1, 5, 5, 2, 3, 2, 4, 1, 5]), йоты n= 3? Какой из всех альтернатив, как [0, 2, 3], [0, 2, 9], ...будет правильным? Пожалуйста, опишите подробнее ваши конкретные требования. Спасибо
ешь
@eat, мне все равно, какой из них должен быть возвращен в этом конкретном случае. Даже если кажется логичным вернуть первое, с чем столкнулся, это не является обязательным требованием для меня.
Алексис Метайро
argsortможет быть жизнеспособной альтернативой, если вы не заботитесь о порядке возврата. Смотрите мой ответ ниже.
синий

Ответы:

349

Самое простое, что я смог придумать, это:

In [1]: import numpy as np

In [2]: arr = np.array([1, 3, 2, 4, 5])

In [3]: arr.argsort()[-3:][::-1]
Out[3]: array([4, 3, 1])

Это включает в себя полный вид массива. Интересно, numpyпредоставляет ли встроенный способ сделать частичную сортировку; до сих пор я не смог найти один.

Если это решение оказывается слишком медленным (особенно для небольших n), возможно, стоит взглянуть на кодирование чего-либо в Cython .

NPE
источник
1
Может ли строка 3 быть записана эквивалентно как arr.argsort()[-1:-4:-1]? Я пробовал это в интерпретаторе, и это дает тот же результат, но мне интересно, не нарушено ли это каким-то примером.
abroekhof
44
@abroekhof Да, это должно быть эквивалентно для любого списка или массива. В качестве альтернативы, это может быть сделано без обращения с помощью np.argsort(-arr)[:3], который я считаю более читабельным и по существу.
Askewchan
6
что означает [:: - 1]? @NPE
1a1a11a
@ 1a1a11a это означает инвертирование массива (буквально, принимает копию массива от неограниченной минимальной до неограниченной максимальной в обратном порядке)
FizBack
15
arr.argsort()[::-1][:n]лучше, потому что он возвращает пустой n=0вместо вместо полного массива
abora
600

Более новые версии NumPy (1.8 и выше) имеют функцию, вызываемую argpartitionдля этого. Чтобы получить индексы четырех крупнейших элементов, сделайте

>>> a = np.array([9, 4, 4, 3, 3, 9, 0, 4, 6, 0])
>>> a
array([9, 4, 4, 3, 3, 9, 0, 4, 6, 0])
>>> ind = np.argpartition(a, -4)[-4:]
>>> ind
array([1, 5, 8, 0])
>>> a[ind]
array([4, 9, 6, 9])

В отличие от argsortэтого, в худшем случае эта функция выполняется за линейное время, но возвращаемые индексы не сортируются, как видно из результата оценки a[ind]. Если вам это тоже нужно, рассортируйте их потом:

>>> ind[np.argsort(a[ind])]
array([1, 8, 5, 0])

Таким образом, чтобы получить топ- k элементов в отсортированном порядке, требуется O ( n + k log k ) времени.

Fred Foo
источник
27
@varela argpartitionвыполняется за линейное время O (n) с использованием алгоритма интроселекции . Последующая сортировка обрабатывает только k элементов, поэтому выполняется в O (k log k).
Фред Фу
2
Если кому-то интересно, как именно np.argpartitionи как работает его родственный алгоритм, np.partitionв связанном вопросе есть более подробное объяснение: stackoverflow.com/questions/10337533/…
Рамон Мартинес,
7
@FredFoo: почему вы использовали -4? ты сделал это, чтобы начать задом наперед? (так как k, будучи положительным или отрицательным, работает для меня одинаково! сначала он печатает только самые маленькие числа!
Rika
2
@LKT используют, a=np.array([9, 4, 4, 3, 3, 9, 0, 4, 6, 0])потому что обычные списки Python не поддерживают индексацию по спискам, в отличие отnp.array
Marawan Okasha
2
@Umangsinghal np.argpartitionпринимает необязательный axisаргумент. Чтобы найти индексы верхних значений n для каждой строки:np.argpartition(a, -n, axis=1)[-n:]
jwalton
48

Еще проще:

idx = (-arr).argsort()[:n]

где n - количество максимальных значений.

Ketan
источник
7
Можно ли это сделать для 2d массива? Если нет, то знаете ли вы, как?
Эндрю Хандт
2
@AndrewHundt: просто используйте (-arr) .argsort (axis = -1) [:,: n]
MiniQuark,
2
подобное было бы arr[arr.argsort()[-n:]]вместо отрицания массива, просто взять кусочек последних n элементов
loganjones16
35

Использование:

>>> import heapq
>>> import numpy
>>> a = numpy.array([1, 3, 2, 4, 5])
>>> heapq.nlargest(3, range(len(a)), a.take)
[4, 3, 1]

Для обычных списков Python:

>>> a = [1, 3, 2, 4, 5]
>>> heapq.nlargest(3, range(len(a)), a.__getitem__)
[4, 3, 1]

Если вы используете Python 2, используйте xrangeвместо range.

Источник: heapq - алгоритм очереди кучи

anishpatel
источник
2
Там нет необходимости в цикле вообще здесь: heapq.nlargest(3, xrange(len(a)), a.take). Для списков Python мы можем использовать .__getitem__вместо .take.
Ашвини Чаудхари
Для п-мерных массивов Aв целом: heapq.nlargest(3, range(len(A.ravel())), A.ravel().take). (Я надеюсь, что это работает только для представлений, см. Также ( ravel vs flatten] ( stackoverflow.com/a/28930580/603003 )).
ComFreek
31

Если вы работаете с многомерным массивом, вам нужно сгладить и распутать индексы:

def largest_indices(ary, n):
    """Returns the n largest indices from a numpy array."""
    flat = ary.flatten()
    indices = np.argpartition(flat, -n)[-n:]
    indices = indices[np.argsort(-flat[indices])]
    return np.unravel_index(indices, ary.shape)

Например:

>>> xs = np.sin(np.arange(9)).reshape((3, 3))
>>> xs
array([[ 0.        ,  0.84147098,  0.90929743],
       [ 0.14112001, -0.7568025 , -0.95892427],
       [-0.2794155 ,  0.6569866 ,  0.98935825]])
>>> largest_indices(xs, 3)
(array([2, 0, 0]), array([2, 2, 1]))
>>> xs[largest_indices(xs, 3)]
array([ 0.98935825,  0.90929743,  0.84147098])
danvk
источник
9

Если вас не интересует порядок K-го по величине элемента, который вы можете использовать argpartition, который должен работать лучше, чем полная сортировка argsort.

K = 4 # We want the indices of the four largest values
a = np.array([0, 8, 0, 4, 5, 8, 8, 0, 4, 2])
np.argpartition(a,-K)[-K:]
array([4, 1, 5, 6])

Кредиты идут на этот вопрос .

Я провел несколько тестов, и это выглядит argpartitionлучше, argsortчем размер массива и значение K увеличивается.

синий
источник
7

Для многомерных массивов вы можете использовать axisключевое слово, чтобы применить разбиение вдоль ожидаемой оси.

# For a 2D array
indices = np.argpartition(arr, -N, axis=1)[:, -N:]

И для захвата предметов:

x = arr.shape[0]
arr[np.repeat(np.arange(x), N), indices.ravel()].reshape(x, N)

Но учтите, что это не вернет отсортированный результат. В этом случае вы можете использовать np.argsort()вдоль намеченной оси:

indices = np.argsort(arr, axis=1)[:, -N:]

# Result
x = arr.shape[0]
arr[np.repeat(np.arange(x), N), indices.ravel()].reshape(x, N)

Вот пример:

In [42]: a = np.random.randint(0, 20, (10, 10))

In [44]: a
Out[44]:
array([[ 7, 11, 12,  0,  2,  3,  4, 10,  6, 10],
       [16, 16,  4,  3, 18,  5, 10,  4, 14,  9],
       [ 2,  9, 15, 12, 18,  3, 13, 11,  5, 10],
       [14,  0,  9, 11,  1,  4,  9, 19, 18, 12],
       [ 0, 10,  5, 15,  9, 18,  5,  2, 16, 19],
       [14, 19,  3, 11, 13, 11, 13, 11,  1, 14],
       [ 7, 15, 18,  6,  5, 13,  1,  7,  9, 19],
       [11, 17, 11, 16, 14,  3, 16,  1, 12, 19],
       [ 2,  4, 14,  8,  6,  9, 14,  9,  1,  5],
       [ 1, 10, 15,  0,  1,  9, 18,  2,  2, 12]])

In [45]: np.argpartition(a, np.argmin(a, axis=0))[:, 1:] # 1 is because the first item is the minimum one.
Out[45]:
array([[4, 5, 6, 8, 0, 7, 9, 1, 2],
       [2, 7, 5, 9, 6, 8, 1, 0, 4],
       [5, 8, 1, 9, 7, 3, 6, 2, 4],
       [4, 5, 2, 6, 3, 9, 0, 8, 7],
       [7, 2, 6, 4, 1, 3, 8, 5, 9],
       [2, 3, 5, 7, 6, 4, 0, 9, 1],
       [4, 3, 0, 7, 8, 5, 1, 2, 9],
       [5, 2, 0, 8, 4, 6, 3, 1, 9],
       [0, 1, 9, 4, 3, 7, 5, 2, 6],
       [0, 4, 7, 8, 5, 1, 9, 2, 6]])

In [46]: np.argpartition(a, np.argmin(a, axis=0))[:, -3:]
Out[46]:
array([[9, 1, 2],
       [1, 0, 4],
       [6, 2, 4],
       [0, 8, 7],
       [8, 5, 9],
       [0, 9, 1],
       [1, 2, 9],
       [3, 1, 9],
       [5, 2, 6],
       [9, 2, 6]])

In [89]: a[np.repeat(np.arange(x), 3), ind.ravel()].reshape(x, 3)
Out[89]:
array([[10, 11, 12],
       [16, 16, 18],
       [13, 15, 18],
       [14, 18, 19],
       [16, 18, 19],
       [14, 14, 19],
       [15, 18, 19],
       [16, 17, 19],
       [ 9, 14, 14],
       [12, 15, 18]])
Kasramvd
источник
Я думаю, что вы можете упростить индексирование здесь с помощью np.take_along_axis(который, вероятно, не существовал, когда вы ответили на этот вопрос)
Эрик
4

Это будет быстрее, чем полная сортировка, в зависимости от размера вашего исходного массива и размера вашего выбора:

>>> A = np.random.randint(0,10,10)
>>> A
array([5, 1, 5, 5, 2, 3, 2, 4, 1, 0])
>>> B = np.zeros(3, int)
>>> for i in xrange(3):
...     idx = np.argmax(A)
...     B[i]=idx; A[idx]=0 #something smaller than A.min()
...     
>>> B
array([0, 2, 3])

Это, конечно, включает в себя вмешательство в ваш исходный массив. Что вы можете исправить (если необходимо), сделав копию или заменив исходные значения. ... в зависимости от того, что дешевле для вашего случая использования.

Павел
источник
Впрочем, ваше решение не даст однозначного решения во всех ситуациях. ОП должен описать, как обращаться с этими однозначными случаями. Спасибо
ешь
@eat Вопрос ОП немного двусмысленный. Однако реализация не совсем открыта для интерпретации. :) ОП должен просто обратиться к определению np.argmax docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html, чтобы убедиться, что это конкретное решение соответствует требованиям. Вполне возможно, что любое решение, соответствующее заявленному требованию ФП, является приемлемым ..
Пол
Что ж, можно также считать реализацию argmax(.)однозначной. (ИМХО он пытается следовать какой-то логике короткого замыкания, но, к сожалению, не обеспечивает универсально приемлемого поведения). Спасибо
ешь
3

Метод np.argpartitionвозвращает только k самых больших индексов, выполняет локальную сортировку и работает быстрее, чем np.argsort(при выполнении полной сортировки), когда массив довольно большой. Но возвращенные индексы НЕ находятся в порядке возрастания / убывания . Давайте скажем с примером:

Введите описание изображения здесь

Мы можем видеть, что если вы хотите строгие индексы top k в порядке возрастания, np.argpartitionвы не получите то, что хотите.

Помимо выполнения сортировки вручную после np.argpartition, мое решение состоит в том, чтобы использовать PyTorch, torch.topkинструмент для построения нейронных сетей, предоставляющий API-интерфейсы, подобные NumPy, с поддержкой как CPU, так и GPU. Это так же быстро, как NumPy с MKL, и предлагает повышение GPU, если вам нужны большие матричные / векторные вычисления.

Строгое кодирование индексов восходящих и нисходящих верхних k будет:

Введите описание изображения здесь

Обратите внимание, что torch.topkпринимает тензор факела и возвращает как верхние значения k, так и верхние k индексы по типу torch.Tensor. Как и в случае с np, torch.topk также принимает аргумент оси, так что вы можете обрабатывать многомерные массивы / тензоры.

futureer
источник
2

Использование:

from operator import itemgetter
from heapq import nlargest
result = nlargest(N, enumerate(your_list), itemgetter(1))

Теперь resultсписок будет содержать N кортежей ( index, value), где valueразвернуто.

off99555
источник
2

Использование:

def max_indices(arr, k):
    '''
    Returns the indices of the k first largest elements of arr
    (in descending order in values)
    '''
    assert k <= arr.size, 'k should be smaller or equal to the array size'
    arr_ = arr.astype(float)  # make a copy of arr
    max_idxs = []
    for _ in range(k):
        max_element = np.max(arr_)
        if np.isinf(max_element):
            break
        else:
            idx = np.where(arr_ == max_element)
        max_idxs.append(idx)
        arr_[idx] = -np.inf
    return max_idxs

Это также работает с 2D массивами. Например,

In [0]: A = np.array([[ 0.51845014,  0.72528114],
                     [ 0.88421561,  0.18798661],
                     [ 0.89832036,  0.19448609],
                     [ 0.89832036,  0.19448609]])
In [1]: max_indices(A, 8)
Out[1]:
    [(array([2, 3], dtype=int64), array([0, 0], dtype=int64)),
     (array([1], dtype=int64), array([0], dtype=int64)),
     (array([0], dtype=int64), array([1], dtype=int64)),
     (array([0], dtype=int64), array([0], dtype=int64)),
     (array([2, 3], dtype=int64), array([1, 1], dtype=int64)),
     (array([1], dtype=int64), array([1], dtype=int64))]

In [2]: A[max_indices(A, 8)[0]][0]
Out[2]: array([ 0.89832036])
X Æ A-12
источник
Работает хорошо, но дает больше результатов, если у вас есть повторяющиеся (максимальные) значения в вашем массиве A. Я ожидал бы ровно k результатов, но в случае дублированных значений вы получите более k результатов.
Гвидо,
Я немного изменил код. Список возвращаемых индексов имеет длину, равную ровно k. Если у вас есть дубликаты, они сгруппированы в один кортеж.
X Æ A-12
1

bottleneck имеет функцию частичной сортировки, если затраты на сортировку всего массива просто для получения N самых больших значений слишком велики.

Я ничего не знаю об этом модуле; Я просто погуглил numpy partial sort.

Katriel
источник
Я не нахожу частичной функции сортировки в узком месте, есть функция разбиения, но она не сортируется
nbecker
1

Ниже приведен очень простой способ увидеть максимальные элементы и их позиции. Здесь axisдомен; axis= 0 означает максимальное число по столбцам, а axis= 1 означает максимальное число по строкам для 2D-случая. А для более высоких измерений это зависит от вас.

M = np.random.random((3, 4))
print(M)
print(M.max(axis=1), M.argmax(axis=1))
либеральный
источник
Я использовал эту ссылку jakevdp.github.io/PythonDataScienceHandbook/…
либеральный
0

Я нашел это наиболее интуитивно понятным в использовании np.unique.

Идея состоит в том, что уникальный метод возвращает индексы входных значений. Затем из максимального уникального значения и признаков можно воссоздать положение исходных значений.

multi_max = [1,1,2,2,4,0,0,4]
uniques, idx = np.unique(multi_max, return_inverse=True)
print np.squeeze(np.argwhere(idx == np.argmax(uniques)))
>> [4 7]
фита
источник
0

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

И я также придумала подход грубой силы:

top_k_index_list = [ ]
for i in range(k):
    top_k_index_list.append(np.argmax(my_array))
    my_array[top_k_index_list[-1]] = -float('inf')

Установите для наибольшего элемента большое отрицательное значение после того, как вы используете argmax для получения его индекса. И тогда следующий вызов argmax вернет второй по величине элемент. И вы можете записать первоначальное значение этих элементов и восстановить их, если хотите.

Чжэнхао Чжао
источник
0

Этот код работает для матричного массива:

mat = np.array([[1, 3], [2, 5]]) # numpy matrix

n = 2  # n
n_largest_mat = np.sort(mat, axis=None)[-n:] # n_largest 
tf_n_largest = np.zeros((2,2), dtype=bool) # all false matrix
for x in n_largest_mat: 
  tf_n_largest = (tf_n_largest) | (mat == x) # true-false  

n_largest_elems = mat[tf_n_largest] # true-false indexing 

Это приводит к матричной индексации true-false n_largest, которая также работает для извлечения элементов n_largest из матричного массива

И Сян Чонг
источник