ChatGPT解决这个技术问题 Extra ChatGPT

是否有一个 NumPy 函数来返回数组中某物的第一个索引?

我知道 Python 列表有一种方法可以返回某物的第一个索引:

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

NumPy 数组有类似的东西吗?


M
Mateen Ulhaq

是的,给定一个要搜索的数组 array 和一个值 item,您可以将 np.where 用作:

itemindex = numpy.where(array == item)

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

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

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

将等于您的项目,因此将是:

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

如果您正在查找第一列中存在项目的第一行,则此方法有效(尽管如果不存在则会引发索引错误)rows, columns = np.where(array==item); first_idx = sorted([r for r, c in zip(rows, columns) if c == 0])[0]
如果您希望它在找到第一个值后停止搜索怎么办?我不认为 where() 可以与 find() 相提并论
啊!如果您对性能感兴趣,请查看此问题的答案:stackoverflow.com/questions/7632963/…
np.argwhere 在这里会稍微有用:itemindex = np.argwhere(array==item)[0]; array[tuple(itemindex)]
值得注意的是,这个答案假设数组是二维的。 where 适用于任何数组,当用于 3D 数组等时,将返回长度为 3 的元组。
V
Vebjorn Ljosa

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

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

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

>>> 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 的排序版本来获得您想要的:

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

您能解释一下 r_ 是什么吗?
@Geoff,r_ 连接;或者,更准确地说,它将切片对象转换为沿每个轴的连接。我可以改用 hstack;这可能不那么令人困惑。有关 r_ 的更多信息,请参阅 the documentation。还有一个c_
+1,不错! (与 NP.where 相比)在我们需要的一维数组中给定值仅第一次出现的情况下,您的解决方案要简单得多(并且可能更快)
后一种情况(查找所有值的第一个索引)由 vals, locs = np.unique(t, return_index=True) 给出
@askewchan 您的版本在功能上是等效的,但是要慢得多,慢得多
P
Peter Mortensen

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

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。


自从第一次编写以来,库可能已经发生了变化。但这是第一个对我有用的解决方案。
我很好地利用了它来使用列表理解在列表中查找多个值:[find_list.index(index_list[i]) for i in range(len(index_list))]
@MattWenham 如果它足够大,您可以将 find_list 转换为 object 的 NumPy 数组(或任何更具体的合适的),然后执行 find_arr[index_list]
完全跑题了,但这是我第一次看到“空中”这个词——我看到最多的可能是“在飞行中”。
简单&可读性规则,但如果您使用 Numpy,性能必须对您很重要。这个 python .index() 方法不必要地重复数据最多两次!
M
MSeifert

只是为了在 np.ndenumerate 的基础上添加一个非常高效且方便的 替代项来查找第一个索引:

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.

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

>>> 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 也可以优雅地 处理多维数组(您需要手动将其转换为元组 并且 它不会短路)但如果不匹配则会失败被发现:

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

@njitjit(nopython=True) 的简写,即该函数将在第一次运行时即时完全编译,以便完全删除 Python 解释器调用。
由于版本至少为 0.20.0,您还可以将其编写为生成器,以便可以按需找到所有出现的特定值。
P
Peter Mortensen

l.index(x) 返回最小的 i,使得 i 是 x 在列表中第一次出现的索引。

可以安全地假设 Python 中的 index() 函数已实现,因此它会在找到第一个匹配项后停止,这会产生最佳的平均性能。

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

In [67]: l=range(100)

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

NumPy 数组:

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,可以使用第二个参数返回一个特殊值,以防找不到元素,例如

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

NumPy 中还有其他函数(argmaxwherenonzero)可用于查找数组中的元素,但它们都存在遍历整个数组查找 all 的缺点 次出现,因此未针对查找第一个元素进行优化。另请注意,wherenonzero 返回数组,因此您需要选择第一个元素来获取索引。

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

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

这是一个开放的 NumPy GitHub issue

另请参阅:Numpy: find first index of value fast


我认为您还应该包括最坏情况(最后一个元素)的时间,以便读者知道在最坏情况下他们使用您的方法时会发生什么。
@MSeifert 我无法获得最坏情况迭代器解决方案的合理时机——我将删除这个答案,直到我发现它有什么问题
%timeit next((idx for idx, val in np.ndenumerate(a) if val==99999)) 不起作用?如果你想知道为什么它慢了 1000 倍——那是因为 numpy 数组上的 python 循环非常慢。
@MSeifert 不,我不知道,但我也对在这种情况下 argmaxwhere 更快的事实感到困惑(在数组末尾搜索元素)
它们应该和元素在开始时一样快。他们总是处理整个数组,所以他们总是花费相同的时间(至少他们应该这样做)。
M
Matt

如果您打算将其用作其他内容的索引,则如果数组是可广播的,则可以使用布尔索引;您不需要显式索引。最简单的方法是简单地基于真值进行索引。

other_array[first_array == item]

任何布尔运算都有效:

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

非零方法也接受布尔值:

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

两个零用于索引元组(假设 first_array 是 1D),然后是索引数组中的第一项。


A
Alok Nayak

对于一维 排序 数组,使用返回 NumPy 整数(位置)的 numpy.searchsorted 会更加简单和高效 O(log(n))。例如,

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

只要确保数组已经排序

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

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

searchsorted 不是 nlog(n) 因为它在搜索之前不对数组进行排序,它假定参数数组已经排序。查看 numpy.searchsorted 的文档(上面的链接)
它是 mlog(n): 在长度为 n 的列表中进行 m 次二进制搜索。
如果要搜索 m 个元素,则它的 mlog(n),当传递一个形状数组而不是像 3 这样的单个元素时。对于这个问题的要求,它是 log(n),即查找一个元素。
1
1''

对于一维数组,我建议使用 np.flatnonzero(array == value)[0],它等效于 np.nonzero(array == value)[0][0]np.where(array == value)[0][0],但避免了拆箱 1 元素元组的丑陋。


P
Peter Mortensen

要根据任何标准编制索引,您可以执行以下操作:

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() 所做的事情,除非没有找到它不会引发异常。当心——这在大型阵列上可能非常慢。如果您愿意将其用作方法,您可能可以将其修补到数组上。

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]

N
Noyer282

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

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

对于二维数组,可以这样做:

>>> 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 检查所有元素是否匹配。如果数组早期有匹配,生成器表达式会更快。


如果数组中可能根本没有匹配项,此方法还可以让您方便地指定后备值。如果第一个示例返回 None 作为回退,它将变为 next((i for i, x_i in enumerate(x) if x_i == 2), None)
P
Peter Mortensen

NumPy 中有很多操作可以放在一起来实现这一点。这将返回等于 item 的元素索引:

numpy.nonzero(array - item)

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


那不会给出所有不等于 item 的元素的索引吗?
E
Eelco Hoogendoorn

numpy_indexed 包(免责声明,我是它的作者)包含 numpy.ndarray 的 list.index 的矢量化等效项;那是:

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]

该解决方案具有矢量化性能,可推广到 ndarray,并具有各种处理缺失值的方法。


n
njp

之前没有提到的另一个选项是 bisect 模块,它也适用于列表,但需要预先排序的列表/数组:

import bisect
import numpy as np
z = np.array([104,113,120,122,126,138])
bisect.bisect_left(z, 122)

产量

3

当数组中不存在您要查找的数字时,bisect 也会返回结果,以便可以将数字插入正确的位置。


D
Dmitriy Work

8种方法比较

TL;博士:

(注:适用于 100M 元素以下的一维数组。)

为了获得最佳性能,请使用 index_of__v5(numba + numpy.enumerate + for 循环;请参见下面的代码)。如果 numba 不可用:如果希望在前 100k 个元素中找到目标值,则使用 index_of__v7(for 循环 + 枚举)。否则使用 index_of__v2/v3/v4(基于 numpy.argmax 或 numpy.flatnonzero)。

https://i.stack.imgur.com/VH8n8.png

perfplot 提供支持

import numpy as np
from numba import njit

# Based on: numpy.argmax()
# Proposed by: John Haberstroh (https://stackoverflow.com/a/67497472/7204581)
def index_of__v1(arr: np.array, v):
    is_v = (arr == v)
    return is_v.argmax() if is_v.any() else -1


# Based on: numpy.argmax()
def index_of__v2(arr: np.array, v):
    return (arr == v).argmax() if v in arr else -1


# Based on: numpy.flatnonzero()
# Proposed by: 1'' (https://stackoverflow.com/a/42049655/7204581)
def index_of__v3(arr: np.array, v):
    idxs = np.flatnonzero(arr == v)
    return idxs[0] if len(idxs) > 0 else -1


# Based on: numpy.argmax()
def index_of__v4(arr: np.array, v):
    return np.r_[False, (arr == v)].argmax() - 1


# Based on: numba, for loop
# Proposed by: MSeifert (https://stackoverflow.com/a/41578614/7204581)
@njit
def index_of__v5(arr: np.array, v):
    for idx, val in np.ndenumerate(arr):
        if val == v:
            return idx[0]
    return -1


# Based on: numpy.ndenumerate(), for loop
def index_of__v6(arr: np.array, v):
    return next((idx[0] for idx, val in np.ndenumerate(arr) if val == v), -1)


# Based on: enumerate(), for loop
# Proposed by: Noyer282 (https://stackoverflow.com/a/40426159/7204581)
def index_of__v7(arr: np.array, v):
    return next((idx for idx, val in enumerate(arr) if val == v), -1)


# Based on: list.index()
# Proposed by: Hima (https://stackoverflow.com/a/23994923/7204581)
def index_of__v8(arr: np.array, v):
    l = list(arr)
    try:
        return l.index(v)
    except ValueError:
        return -1

Go to Colab


J
John Haberstroh

numpy 内置了一种相当惯用和矢量化的方法来执行此操作。它使用 np.argmax() 函数的一个怪癖来实现这一点——如果许多值匹配,它返回第一个匹配的索引。诀窍在于,对于布尔值,只有两个值:True (1) 和 False (0)。因此,返回的索引将是第一个 True 的索引。

对于提供的简单示例,您可以看到它与以下内容一起使用

>>> np.argmax(np.array([1,2,3]) == 2)
1

一个很好的例子是计算桶,例如用于分类。假设您有一个切点数组,并且您想要与数组的每个元素对应的“桶”。该算法是计算 cuts 的第一个索引,其中 x < cuts(在用 np.Infitnity 填充 cuts 之后)。我可以使用广播来广播比较,然后沿 cuts 广播轴应用 argmax。

>>> cuts = np.array([10, 50, 100])
>>> cuts_pad = np.array([*cuts, np.Infinity])
>>> x   = np.array([7, 11, 80, 443])
>>> bins = np.argmax( x[:, np.newaxis] < cuts_pad[np.newaxis, :], axis = 1)
>>> print(bins)
[0, 1, 2, 3]

正如预期的那样,来自 x 的每个值都落入顺序箱之一,具有明确定义且易于指定的边缘情况行为。


S
Statham

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

您可以使用 lambda 函数来处理该问题,它适用于 NumPy 数组和列表。

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]

你可以使用

result[0]

获取过滤元素的第一个索引。

对于 python 3.6,使用

list(result)

代替

result

这会在 Python 3 上产生 <filter object at 0x0000027535294D30>(在 Python 3.6.3 上测试)。也许更新 Python 3?
S
Sangavi Loganathan

使用 ndindex

样本数组

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

...[[1,4],
    [2,3]]
 

创建一个空列表来存储索引和元素元组

 index_elements = []
 for i in np.ndindex(arr.shape):
     index_elements.append((arr[i],i))

 

将元组列表转换为字典

 index_elements = dict(index_elements)

键是元素,值是它们的索引 - 使用键来访问索引

 index_elements[4] 
  
  ... (0,1)
  

M
Matt Raymond

对于我的用例,我无法提前对数组进行排序,因为元素的顺序很重要。这是我的全 NumPy 实现:

import numpy as np

# The array in question
arr = np.array([1,2,1,2,1,5,5,3,5,9]) 

# Find all of the present values
vals=np.unique(arr)
# Make all indices up-to and including the desired index positive
cum_sum=np.cumsum(arr==vals.reshape(-1,1),axis=1)
# Add zeros to account for the n-1 shape of diff and the all-positive array of the first index
bl_mask=np.concatenate([np.zeros((cum_sum.shape[0],1)),cum_sum],axis=1)>=1
# The desired indices
idx=np.where(np.diff(bl_mask))[1]

# Show results
print(list(zip(vals,idx)))

>>> [(1, 0), (2, 1), (3, 7), (5, 5), (9, 9)]

我相信它可以解释具有重复值的未排序数组。


b
ben othman zied
index_lst_form_numpy = pd.DataFrame(df).reset_index()["index"].tolist()

P
Pobaranchuk

找到另一个带有循环的解决方案:

new_array_of_indicies = []

for i in range(len(some_array)):
  if some_array[i] == some_value:
    new_array_of_indicies.append(i)
    

python 中的循环非常慢,如果有其他解决方案,应该避免它们
应该避免这种解决方案,因为它太慢了。