在numpy数组中查找多个值的行索引

33

我有一个数组X:

X = np.array([[4,  2],
              [9,  3],
              [8,  5],
              [3,  3],
              [5,  6]])

我希望能够在这个数组中找到多个值所在行的索引:

searched_values = np.array([[4, 2],
                            [3, 3],
                            [5, 6]])

对于这个例子,我希望得到以下结果:

[0,3,4]

我有一段代码可以实现这个功能,但我认为它过于复杂:

X = np.array([[4,  2],
              [9,  3],
              [8,  5],
              [3,  3],
              [5,  6]])

searched_values = np.array([[4, 2],
                            [3, 3],
                            [5, 6]])

result = []

for s in searched_values:
    idx = np.argwhere([np.all((X-s)==0, axis=1)])[0][1]
    result.append(idx)

print(result)

我找到了这个答案,用于类似的问题,但仅适用于1D数组。

有没有更简单的方法实现我想要的功能?


这并不是很复杂!如果你使用列表推导式而不是带有appendfor循环,那就更简单了。 - Julien
8个回答

45

方法一

一种方法是使用NumPy广播,代码如下 -

np.where((X==searched_values[:,None]).all(-1))[1]

方法 #2

一种内存有效的方法是将每一行转换为线性索引等效形式,然后使用np.in1d函数,如下所示-

dims = X.max(0)+1
out = np.where(np.in1d(np.ravel_multi_index(X.T,dims),\
                    np.ravel_multi_index(searched_values.T,dims)))[0]

第三种方法

另一种使用np.searchsorted的内存高效方法,并采用相同的转换为线性索引等价物的思想,可以像这样实现 -

dims = X.max(0)+1
X1D = np.ravel_multi_index(X.T,dims)
searched_valuesID = np.ravel_multi_index(searched_values.T,dims)
sidx = X1D.argsort()
out = sidx[np.searchsorted(X1D,searched_valuesID,sorter=sidx)]
请注意,此np.searchsorted方法假设在X中每一行都存在与searched_values匹配的值。

np.ravel_multi_index如何工作?

该函数给出线性索引等效的数值。它接受一个 2D 数组的n维索引,设置为列,并将该 n 维网格的形状本身映射到这些索引上,计算相应的线性索引。

让我们使用手头的问题输入。以输入X为例,注意它的第一行。由于我们要将X的每一行转换为其线性索引等效值,并且由于np.ravel_multi_index假设每列为一个索引元组,因此在将其馈入函数之前需要对X进行转置。在这种情况下,X每行的元素数量为2,要映射的n维网格将是2D。对于每行3个元素的X,它将成为3D网格进行映射等等。

为了看到这个函数如何计算线性索引,请考虑X的第一行-

In [77]: X
Out[77]: 
array([[4, 2],
       [9, 3],
       [8, 5],
       [3, 3],
       [5, 6]])

我们将n维网格的形状表示为dims -

In [78]: dims
Out[78]: array([10,  7])

让我们创建一个二维网格,看看这种映射是如何工作的,并使用np.ravel_multi_index计算线性索引 -

In [79]: out = np.zeros(dims,dtype=int)

In [80]: out
Out[80]: 
array([[0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0]])

让我们将 X 中的第一个索引元组,即来自 X 的第一行放入网格中 -

In [81]: out[4,2] = 1

In [82]: out
Out[82]: 
array([[0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0]])

现在,要查看刚才设置的元素的线性索引等效值,让我们展平并使用np.where来检测那个1

In [83]: np.where(out.ravel())[0]
Out[83]: array([30])

如果考虑行优先顺序,也可以计算这个。

使用np.ravel_multi_index来验证这些线性索引:

In [84]: np.ravel_multi_index(X.T,dims)
Out[84]: array([30, 66, 61, 24, 41])

因此,我们将为来自X的每个索引元组具有相应的线性索引,即X的每行。

选择np.ravel_multi_index的维度以形成唯一的线性索引

现在,将X的每一行视为n维网格的索引元组,并将每个这样的元组转换为标量的背后思想是使唯一的标量与唯一的元组(即X中的唯一行)对应。

让我们再看一下X -

In [77]: X
Out[77]: 
array([[4, 2],
       [9, 3],
       [8, 5],
       [3, 3],
       [5, 6]])

现在,正如前面讨论的那样,我们将每一行视为索引元组。在每个这样的索引元组中,第一个元素表示n维网格的第一轴,第二个元素是网格的第二轴,以此类推,直到X中每一行的最后一个元素。实质上,每一列表示网格的一个维度或轴。如果我们要将X中的所有元素映射到相同的n维网格上,就需要考虑这样一个拟议的n维网格中每个轴的最大伸展。假设我们正在处理X中的正数,这样一个伸展就是X中每列的最大值+1。这里的+1是因为Python遵循基于0的索引。因此,例如X [1,0] == 9会被映射到建议网格的第10行。同样,X [4,1] == 6会进入该网格的第7

因此,对于我们的示例情况,我们有 -

In [7]: dims = X.max(axis=0) + 1 # Or simply X.max(0) + 1

In [8]: dims
Out[8]: array([10,  7])

因此,针对我们的样例情况,我们至少需要一个形状为(10,7)的网格。如果在维度方向上增加长度,不但不会影响结果,还能给我们提供唯一的线性索引。

总结:这里需要注意的一点是,如果X中存在负数,我们需要在每列中添加适当的偏移量,使得这些索引元组变成正数,然后再使用np.ravel_multi_index


@MaxU 哦,(7,6) 只是一个安全的示例,考虑到输入索引的最大范围。甚至可以采用 (20,20) 网格并在该较大网格上获得线性等价物,只是这些等价物会是更大的数字。我添加的说明实际上并没有太关注决定网格的维度。 - Divakar
@Divakar 为什么你在 dims 中使用了 array([10, 7])?我知道 10 是因为 X 中有 10 个元素,但是你为什么选择了 7? - Octoplus
@Octoplus 不不不! X 的第一列将表示我们正在映射元素从 X 到的 n 维(这里是二维,因为 X 每行有两个元素)网格的行,而第二列将是网格的列。现在,X 的第一列一直到 9,因此由于 Python 中基于 0 的索引,我们需要在这样的网格中有 10(=9+1)行。同样,X 的第二列一直到 6,因此我们需要在这样的网格中有 7(=6+1)列。希望这讲得通!我认为我应该添加一个关于这个网格维度问题的部分。 - Divakar
1
如果X中存在负数或浮点数元素,则似乎不起作用,因为它将X的元素视为np.ravel_multi_index技巧中的索引。我的理解正确吗? - Baoquan Feng
我总是使用每列的 max - min + 1 而不是 max +1 来计算维度的大小。 - mathfux
我有一个非常大的矩阵X和多个searched_values集合。最快的方法是什么? - seralouk

8

numpy_indexed 包(声明:我是它的作者)包含了高效执行此类操作的功能(在内部也使用了 searchsorted)。就功能而言,它相当于列表 index 的向量化等效形式:

import numpy_indexed as npi
result = npi.indices(X, searched_values)

请注意,使用“missing”kwarg,您可以完全控制缺失项目的行为,并且它也适用于nd-arrays(例如;图像堆栈)。
更新:使用与@Rik相同的形状 X=[520000,28,28]searched_values=[20000,28,28],运行时间为 0.8064秒,使用 missing=-1 检测和表示X中不存在的条目。

8

另一种选择是使用下面的asvoid将每行视为void数据类型的单个值,从而将2D数组减少为1D数组,因此您可以像往常一样使用np.in1d:

import numpy as np

def asvoid(arr):
    """
    Based on https://dev59.com/mmQn5IYBdhLWcg3wRVRs#16973510 (Jaime, 2013-06)
    View the array as dtype np.void (bytes). The items along the last axis are
    viewed as one value. This allows comparisons to be performed which treat
    entire rows as one value.
    """
    arr = np.ascontiguousarray(arr)
    if np.issubdtype(arr.dtype, np.floating):
        """ Care needs to be taken here since
        np.array([-0.]).view(np.void) != np.array([0.]).view(np.void)
        Adding 0. converts -0. to 0.
        """
        arr += 0.
    return arr.view(np.dtype((np.void, arr.dtype.itemsize * arr.shape[-1])))

X = np.array([[4,  2],
              [9,  3],
              [8,  5],
              [3,  3],
              [5,  6]])

searched_values = np.array([[4, 2],
                            [3, 3],
                            [5, 6]])

idx = np.flatnonzero(np.in1d(asvoid(X), asvoid(searched_values)))
print(idx)
# [0 3 4]

好的使用那个视图概念和 np.flatnonzero,我得有时候用一下! - Divakar

2
这里有一个使用 numpy 和 hashlib 的非常快速的解决方案,可以很好地扩展。它可以在几秒钟内处理大维度矩阵或图像。我在我的 CPU 上将其用于 520000 X(28 X 28)数组和 20000 X(28 X 28),只需 2 秒即可完成。
代码:
import numpy as np
import hashlib


X = np.array([[4,  2],
              [9,  3],
              [8,  5],
              [3,  3],
              [5,  6]])

searched_values = np.array([[4, 2],
                            [3, 3],
                            [5, 6]])

#hash using sha1 appears to be efficient
xhash=[hashlib.sha1(row).digest() for row in X]
yhash=[hashlib.sha1(row).digest() for row in searched_values]

z=np.in1d(xhash,yhash)  

##Use unique to get unique indices to ind1 results
_,unique=np.unique(np.array(xhash)[z],return_index=True)

##Compute unique indices by indexing an array of indices
idx=np.array(range(len(xhash)))
unique_idx=idx[z][unique]

print('unique_idx=',unique_idx)
print('X[unique_idx]=',X[unique_idx])

输出:

unique_idx= [4 3 0]
X[unique_idx]= [[5 6]
 [3 3]
 [4 2]]

1
请注意,基于哈希的方法需要另外进行过滤步骤来消除错误的哈希冲突,以确保其正确性。 - Eelco Hoogendoorn

1
X = np.array([[4,  2],
              [9,  3],
              [8,  5],
              [3,  3],
              [5,  6]])

S = np.array([[4, 2],
              [3, 3],
              [5, 6]])

result = [[i for i,row in enumerate(X) if (s==row).all()] for s in S]

或者

result = [i for s in S for i,row in enumerate(X) if (s==row).all()]

如果您想要一个平面列表(假设每个搜索值只有一个匹配项)。

1

我有类似的需求,以下方法适用于我:

np.argwhere(np.isin(X, searched_values).all(axis=1))

0

这是对我有效的方法:

def find_points(orig: np.ndarray, search: np.ndarray) -> np.ndarray:
    equals = [np.equal(orig, p).all(1) for p in search]
    exists = np.max(equals, axis=1)
    indices = np.argmax(equals, axis=1)
    indices[exists == False] = -1
    return indices

测试:

X = np.array([[4,  2],
              [9,  3],
              [8,  5],
              [3,  3],
              [5,  6]])

searched_values = np.array([[4, 2],
                            [3, 3],
                            [5, 6],
                            [0, 0]])

find_points(X, searched_values)

输出:

[0,3,4,-1]

0
另一种方法是使用scipy.spatial.distance中的cdist函数,如下所示:
np.nonzero(cdist(X, searched_values) == 0)[0]

基本上,我们获取X的行号,它们与searched_values中的一行距离为零,这意味着它们是相等的。如果将行视为坐标,则具有意义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接