如何获取NumPy数组中N个最大值的索引?


481

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]...将是正确的呢?请详细说明您的特定要求。谢谢

@进餐,我不太关心在这种情况下应该退还哪一个。即使返回遇到的第一个逻辑似乎很合理,但这对我也不是必需的。
亚历克西斯·梅特拉(AlexisMétaireau)2011年

argsort如果您不关心返回索引的顺序,则可能是一个可行的选择。请参阅下面的答案。
蓝色

Answers:


347

我想出的最简单的方法是:

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编写代码


1
第三行可以等效地写成arr.argsort()[-1:-4:-1]吗?我已经在解释器中对其进行了尝试,并且得出了相同的结果,但是我想知道是否没有被某些示例破坏。
abroekhof 2012年

44
@abroekhof是的,任何列表或数组都应该等效。或者,可以通过使用来完成此操作而不会逆转np.argsort(-arr)[:3],我发现它更具可读性,并且很重要。
askewchan

6
[::-1]是什么意思?@NPE
1a1a11a 16-10-17

@ 1a1a11a表示反转数组(从字面上看,以相反的顺序从不受约束的最小值到不受约束的最大值获取数组的副本)
FizBack

15
arr.argsort()[::-1][:n]更好,因为它为n=0而不是整个数组返回空
abora

598

较新的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)时间。


27
argpartition使用introselect算法,@ varela 以线性时间O(n)运行。后续排序仅处理k个元素,因此以O(k log k)运行。
Fred Foo 2014年

2
如果有人想知道究竟如何np.argpartition和它的姊妹算法np.partition的工作出现了在链接的问题更详细的解释:stackoverflow.com/questions/10337533/...
拉蒙·马丁内斯

7
@FredFoo:为什么使用-4?您这样做是为了向后开始吗?(因为k为正数或负数对我来说都是相同的!它只打印最小的数字!
Rika 2016年

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:]
Ralph

48

更简单了:

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

其中,n是最大值的数量。


7
二维数组可以做到吗?如果没有,您也许知道如何?
Andrew Hundt 2015年

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 —堆队列算法


2
此处完全不需要循环:heapq.nlargest(3, xrange(len(a)), a.take)。对于Python列表,我们可以使用.__getitem__代替.take
Ashwini Chaudhary 2014年

对于n维阵列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])

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

学分到这个问题

我进行了一些测试,随着数组的大小和K值的增加,它的argpartition表现似乎都胜过argsort了。


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

我认为您可以通过使用np.take_along_axis(在您回答此问题时可能不存在)简化此处的索引
Eric Eric

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

当然,它涉及篡改原始阵列。您可以通过复制或替换原始值来解决(如果需要)的问题。...以您的使用案例中较便宜的价格为准


FWIW,您的解决方案在所有情况下都不会提供明确的解决方案。OP应该描述如何处理这些明确的情况。谢谢

@eat OP的问题有点模棱两可。但是,一个实现并没有真正接受解释。:) OP应该仅引用np.argmax的定义docs.scipy.org/doc/numpy/reference/generation/numpy.argmax.html,以确保此特定解决方案满足要求。任何符合OP规定要求的解决方案都是可以接受的。.–
Paul,

好吧,人们可能会认为的实现也是argmax(.)明确的。(恕我直言,它试图遵循某种短路逻辑,但不幸的是未能提供普遍接受的行为)。谢谢

3

方法np.argpartition仅返回k个最大的索引,执行局部排序,并且比np.argsort数组很大时要快(执行完整排序)。但是返回的索引不是按升序/降序排列的。让我们举一个例子:

在此处输入图片说明

我们可以看到,如果您要对前k个索引使用严格的升序,np.argpartition则不会返回您想要的结果。

除了在np.argpartition之后手动进行排序之外,我的解决方案是使用PyTorch(torch.topk一种用于神经网络构建的工具),为类似NumPy的API提供CPU和GPU支持。它与带有MKL的NumPy一样快,并且如果需要大型矩阵/矢量计算,则可以提供GPU增强。

严格的上升/下降前k个索引代码将是:

在此处输入图片说明

请注意,它torch.topk接受火炬张量,并返回type中的前k个值和前k个索引torch.Tensor。与np相似,torch.topk也接受轴参数,以便您可以处理多维数组/张量。


2

采用:

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

现在,result列表将包含N个元组(indexvalue),其中value已最大化。


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

效果很好,但是如果数组A中有重复的(最大)值,则会给出更多结果。我希望恰好有k个结果,但是如果有重复值,则得到的结果会多于k个。
Guido

我稍微修改了代码。返回的索引列表的长度恰好等于k。如果您有重复项,则将它们分组为一个元组。
XÆA-12

1

bottleneck 如果仅为了获得N个最大值而对整个数组进行排序的开销太大,则具有部分排序函数。

我对这个模块一无所知。我只是谷歌搜索numpy partial sort


我在瓶颈中找不到部分排序函数,没有分区函数,但是无法排序
nbecker


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

这段代码适用于numpy矩阵数组:

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 

这会产生一个真假n_largest矩阵索引,该索引也可以从矩阵数组中提取n_largest个元素

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.