关于python:如何在NumPy数组中获得N个最大值的索引?

How do I get indices of N maximum values in a NumPy array?

numpy提出了一种通过np.argmax获取数组最大值索引的方法。

我想要一个类似的东西,但是返回N最大值的索引。

例如,如果我有一个数组,[1, 3, 2, 4, 5]function(array, n=3)将返回对应于元素[5, 4, 3]的索引[4, 3, 1]


新的numpy版本(1.8及更高版本)有一个称为argpartition的函数。要获得四个最大元素的索引,请执行以下操作:

1
2
3
4
5
6
7
8
>>> 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]的结果可以看出。如果您也需要,请稍后对其进行排序:

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

以这种方式获得排序顺序的前k个元素需要O(n+k logk)时间。


我能想到的最简单的方法是:

1
2
3
4
5
6
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
idx = (-arr).argsort()[:n]

其中n是最大值的数目。


用途:

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

对于常规的python列表:

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

如果使用python 2,请使用xrange而不是range

来源:heapq-堆队列算法


如果您碰巧使用多维数组,那么您将需要展平并展开索引:

1
2
3
4
5
6
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)

例如:

1
2
3
4
5
6
7
8
9
>>> 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])


如果您不关心第k个最大元素的顺序,可以使用argpartition,它的性能应该比通过argsort进行完全排序要好。

1
2
3
4
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


对于多维数组,可以使用axis关键字沿预期轴应用分区。

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

抓取物品:

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

但请注意,这不会返回已排序的结果。在这种情况下,您可以沿预期轴使用np.argsort()

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

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

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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]])


这将比完全排序更快,具体取决于原始数组的大小和所选内容的大小:

1
2
3
4
5
6
7
8
9
10
>>> 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])

当然,它涉及篡改原始数组。您可以通过复制或替换原始值来修复(如果需要)。…以您的用例便宜的为准。


如果仅仅为了得到n个最大值而对整个数组进行排序的开销太大,那么bottleneck具有部分排序功能。

我对这个模块一无所知;我只是在google上搜索numpy partial sort.


用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

它也适用于二维阵列。例如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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])


方法np.argpartition只返回k个最大的索引,执行局部排序,数组较大时比np.argsort执行完全排序快。但返回的索引不是升序/降序。举个例子来说:

Enter image description here

我们可以看到,如果你想要一个严格的K指数升序,那么np.argpartition不会返回你想要的。

除了在np.argpartition之后手动排序之外,我的解决方案是使用pytorch,torch.topk,一种用于神经网络构建的工具,提供numpy-like API,同时支持CPU和GPU。使用mkl的速度和numpy一样快,如果需要大量的矩阵/向量计算,它可以提供GPU增强。

严格的上升/下降顶部K索引代码将是:

氧化镁

注意,torch.topk接受torch张量,并返回torch.Tensor类型中的前k值和前k指数。与np类似,torch.topk也接受一个轴参数,以便处理多维数组/张量。


用途:

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

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


下面是一个很容易看到最大元素及其位置的方法。这里,axis是域;axis=0是列最大数,axis=1是行最大数。对于更高的维度,它取决于你。

1
2
3
M = np.random.random((3, 4))
print(M)
print(M.max(axis=1), M.argmax(axis=1))


我认为最省时的方法是手动遍历数组并保留k大小的最小堆,正如其他人提到的那样。

我还想出了一个蛮力的方法:

1
2
3
4
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的下一个调用将返回第二大元素。您可以记录这些元素的原始值并在需要时恢复它们。


我发现使用np.unique是最直观的。

其思想是,unique方法返回输入值的索引。然后根据最大唯一值和指标,重新创建原始值的位置。

1
2
3
4
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]