关于python:是否有NumPy函数返回数组中某些内容的第一个索引?

Is there a NumPy function to return the first index of something in an array?

我知道有一种方法可以让python列表返回某个内容的第一个索引:

1
2
3
>>> l = [1, 2, 3]
>>> l.index(2)
1

对于numpy数组有类似的东西吗?


是的,这里给出了一个numpy数组array和一个值item来搜索:

1
itemindex = numpy.where(array==item)

结果是一个元组,首先是所有的行索引,然后是所有的列索引。

例如,如果一个数组是二维的,并且它在两个位置包含您的项,那么

1
array[itemindex[0][0]][itemindex[1][0]]

和你的物品一样

1
array[itemindex[0][1]][itemindex[1][1]]

麻木的地方


如果您只需要一个值的第一次出现的索引,您可以使用nonzero(或where,在这种情况下等于相同的内容):

1
2
3
4
5
>>> t = array([1, 1, 1, 2, 2, 3, 8, 3, 8, 8])
>>> nonzero(t == 8)
(array([6, 8, 9]),)
>>> nonzero(t == 8)[0][0]
6

如果需要对多个值中的每一个进行第一个索引,显然可以重复执行上述操作,但有一个技巧可能更快。下面查找每个子序列的第一个元素的索引:

1
2
>>> nonzero(r_[1, diff(t)[:-1]])
(array([0, 3, 5, 6, 7, 8]),)

请注意,它找到3s子序列和8s子序列的开头:

[1、1、1、2、2、3、8、3、8、8]

所以它与查找每个值的第一次出现略有不同。在您的程序中,您可以使用经过排序的t版本来获得您想要的:

1
2
3
>>> st = sorted(t)
>>> nonzero(r_[1, diff(st)[:-1]])
(array([0, 3, 5, 7]),)


还可以将numpy数组转换为空气中的列表并获取其索引。例如,

1
2
3
4
l = [1,2,3,4,5] # Python list
a = numpy.array(l) # NumPy array
i = a.tolist().index(2) # i will return index of 2
print i

它将打印1。


如果要将它用作其他对象的索引,那么如果数组是可广播的,则可以使用布尔索引;不需要显式索引。要做到这一点,最简单的方法就是简单地基于真值进行索引。

1
other_array[first_array == item]

任何布尔运算都有效:

1
2
a = numpy.arange(100)
other_array[first_array > 50]

非零方法也采用布尔值:

1
index = numpy.nonzero(first_array == item)[0][0]

这两个0表示索引的元组(假设第一个_数组是1d),然后是索引数组中的第一个项。


只需在np.ndenumerate的基础上添加一个性能非常好且方便的numba选项,即可找到第一个索引:

1
2
3
4
5
6
7
8
9
10
from numba import njit
import numpy as np

@njit
def index(array, item):
    for idx, val in np.ndenumerate(array):
        if val == item:
            return idx
    # If no item was found return None, other return types might be a problem due to
    # numbas type inference.

这非常快,可以很自然地处理多维数组:

1
2
3
4
5
6
7
8
9
10
11
>>> arr1 = np.ones((100, 100, 100))
>>> arr1[2, 2, 2] = 2

>>> index(arr1, 2)
(2, 2, 2)

>>> arr2 = np.ones(20)
>>> arr2[5] = 2

>>> index(arr2, 2)
(5,)

这可能比使用np.wherenp.nonzero的任何方法更快(因为它会使操作短路)。

然而,np.argwhere也可以很好地处理多维数组(您需要手动将其转换为元组,并且它不会短路),但如果找不到匹配,它将失败:

1
2
3
4
>>> tuple(np.argwhere(arr1 == 2)[0])
(2, 2, 2)
>>> tuple(np.argwhere(arr2 == 2)[0])
(5,)


l.index(x)返回最小的i,因此i是列表中第一个出现x的索引。

我们可以安全地假设在python中实现了index()函数,以便在找到第一个匹配之后停止,从而获得最佳的平均性能。

要查找numpy数组中第一个匹配后停止的元素,请使用迭代器(ndenumerate)。

1
2
3
4
In [67]: l=range(100)

In [68]: l.index(2)
Out[68]: 2

numpy数组:

1
2
3
4
In [69]: a = np.arange(100)

In [70]: next((idx for idx, val in np.ndenumerate(a) if val==2))
Out[70]: (2L,)

注意,如果找不到元素,两个方法index()next都返回错误。使用next时,如果找不到元素,可以使用第二个参数返回一个特殊值,例如。

1
In [77]: next((idx for idx, val in np.ndenumerate(a) if val==400),None)

numpy(argmaxwherenonzero中还有其他可以用来在数组中查找元素的函数,但它们都有缺点,即通过整个数组查找所有出现的情况,因此无法优化查找第一个元素。还要注意,wherenonzero返回数组,因此需要选择第一个元素来获取索引。

1
2
3
4
5
6
7
8
In [71]: np.argmax(a==2)
Out[71]: 2

In [72]: np.where(a==2)
Out[72]: (array([2], dtype=int64),)

In [73]: np.nonzero(a==2)
Out[73]: (array([2], dtype=int64),)

。时间比较

只需检查对于大型数组,当搜索项位于数组的开头时,使用迭代器的解决方案更快(在ipython shell中使用%timeit):

1
2
3
4
5
6
7
8
9
10
In [285]: a = np.arange(100000)

In [286]: %timeit next((idx for idx, val in np.ndenumerate(a) if val==0))
100000 loops, best of 3: 17.6 μs per loop

In [287]: %timeit np.argmax(a==0)
1000 loops, best of 3: 254 μs per loop

In [288]: %timeit np.where(a==0)[0][0]
1000 loops, best of 3: 314 μs per loop

这是一个开放的麻木的Github问题。

另请参见:numpy:快速查找第一个值索引


要对任何条件进行索引,可以执行如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
In [1]: from numpy import *
In [2]: x = arange(125).reshape((5,5,5))
In [3]: y = indices(x.shape)
In [4]: locs = y[:,x >= 120] # put whatever you want in place of x >= 120
In [5]: pts = hsplit(locs, len(locs[0]))
In [6]: for pt in pts:
   .....:         print(', '.join(str(p[0]) for p in pt))
4, 4, 0
4, 4, 1
4, 4, 2
4, 4, 3
4, 4, 4

这里有一个快速的函数来做list.index()所做的,除非没有找到异常就不会引发异常。小心——这在大型阵列上可能非常慢。如果您愿意将它用作一个方法,那么您可能需要将它修补到数组上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def ndindex(ndarray, item):
    if len(ndarray.shape) == 1:
        try:
            return [ndarray.tolist().index(item)]
        except:
            pass
    else:
        for i, subarray in enumerate(ndarray):
            try:
                return [i] + ndindex(subarray, item)
            except:
                pass

In [1]: ndindex(x, 103)
Out[1]: [4, 0, 3]

对于一维数组,我建议使用np.flatnonzero(array == value)[0],它相当于np.nonzero(array == value)[0][0]np.where(array == value)[0][0],但避免了取消绑定一个元素元组的丑陋。


对于一维排序数组,使用numpy.searchsorted将更简单和高效,它返回numpy整数(位置)。例如,

1
2
arr = np.array([1, 1, 1, 2, 3, 3, 4])
i = np.searchsorted(arr, 3)

只需确保数组已经排序

还要检查返回的索引i是否实际包含搜索的元素,因为SearchSorted的主要目标是找到应该在其中插入元素以保持顺序的索引。

1
2
3
4
if arr[i] == 3:
    print("present")
else:
    print("not present")


在numpy中有许多操作可以组合在一起完成这一点。这将返回等于项的元素索引:

1
numpy.nonzero(array - item)

然后,您可以使用列表的第一个元素来获取单个元素。


从np.where()中选择第一个元素的替代方法是将生成器表达式与枚举一起使用,例如:

1
2
3
4
>>> import numpy as np
>>> x = np.arange(100)   # x = array([0, 1, 2, 3, ... 99])
>>> next(i for i, x_i in enumerate(x) if x_i == 2)
2

对于二维数组,可以执行以下操作:

1
2
3
4
>>> x = np.arange(100).reshape(10,10)   # x = array([[0, 1, 2,... 9], [10,..19],])
>>> next((i,j) for i, x_i in enumerate(x)
...            for j, x_ij in enumerate(x_i) if x_ij == 2)
(0, 2)

这种方法的优点是,它在找到第一个匹配项后停止检查数组元素,而np.where检查所有元素是否匹配。如果在数组的早期有匹配,生成器表达式将更快。


numpy_索引包(免责声明,我是其作者)包含numpy.ndarray的list.index的矢量化等价物;即:

1
2
3
4
5
6
sequence_of_arrays = [[0, 1], [1, 2], [-5, 0]]
arrays_to_query = [[-5, 0], [1, 0]]

import numpy_indexed as npi
idx = npi.indices(sequence_of_arrays, arrays_to_query, missing=-1)
print(idx)   # [2, -1]

此解决方案具有矢量化的性能,可归纳为ndarrays,并具有处理缺失值的各种方法。


注意:这是针对python 2.7版本的

您可以使用lambda函数来处理这个问题,它可以在numpy数组和list上工作。

1
2
3
4
5
6
7
8
your_list = [11, 22, 23, 44, 55]
result = filter(lambda x:your_list[x]>30, range(len(your_list)))
#result: [3, 4]

import numpy as np
your_numpy_array = np.array([11, 22, 23, 44, 55])
result = filter(lambda x:your_numpy_array [x]>30, range(len(your_list)))
#result: [3, 4]

你可以用

1
result[0]

以获取筛选元素的第一个索引。

对于python 3.6,使用

1
list(result)

而不是

1
result