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

Posted

技术标签:

【中文标题】在 numpy 数组中查找多个值的行索引【英文标题】:Find the row indexes of several values in a numpy array 【发布时间】:2016-12-05 01:39:06 【问题描述】:

我有一个数组 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)

我发现this answer 有一个类似的问题,但它只适用于一维数组。

有没有办法以更简单的方式做我想做的事?

【问题讨论】:

这没那么复杂!如果您使用列表推导而不是 for 循环和 append,则更多。 【参考方案1】:

方法#1

一种方法是使用NumPy broadcasting,就像这样 -

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]

方法#3

另一种使用 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 是如何工作的?

此函数为我们提供线性索引等效数。它接受 n-dimensional indices2D 数组,设置为列,以及要映射这些索引和计算等效线性索引的 n 维网格本身的形状。

让我们使用现有的输入来解决手头的问题。以输入X 为例,注意它的第一行。因为,我们试图将X 的每一行转换为其线性索引等效项,并且由于np.ravel_multi_index 假定每一列都是一个索引元组,我们需要在输入函数之前转置X。因为在这种情况下,X 中每行的元素数是 2,所以要映射到的 n 维网格将是 2DX 中每行有 3 个元素,它应该是 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-dim 网格的第一个轴,第二个元素将是网格的第二个轴,依此类推,直到X 中每一行的最后一个元素。本质上,每一列将代表网格的一个维度或轴。如果我们要将X 中的所有元素映射到同一个 n-dim 网格上,我们需要考虑这种提议的 n-dim 网格的每个轴的最大伸展。假设我们正在处理X 中的正数,这样的拉伸将是X + 1 中每一列的最大值。+ 1 是因为 Python 遵循0-based 索引。因此,例如 X[1,0] == 9 将映射到建议网格的第 10 行。同样,X[4,1] == 6 将转到该网格的7th

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

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 中有负数,我们需要在X 的每一列中添加适当的偏移量,以使这些索引元组在使用@987654382 之前为正数@。

【讨论】:

这很聪明!您能否举一个小例子,简要说明np.ravel_multi_index() 的工作原理 - 很遗憾,但我不明白this example,也许您可​​以在该示例中添加几句话,解释他们是如何得到这个结果集的.非常感谢! @MaxU 看看np.ravel_multi_index 上添加的部分是否有意义! :) @Divakar,太完美了!非常感谢!最后我明白了为什么他们在文档的示例中使用(7,6) @Divakar,谢谢!在你用零做了一个例子之后已经很清楚了,但现在它很清楚......;)很遗憾我不能第二次支持它 如果 X 中有负数或浮点元素似乎不起作用,因为它将 X 的元素视为 np.ravel_multi_index 技巧中的索引。我的理解对吗?【参考方案2】:
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()]

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

【讨论】:

【参考方案3】:

另一种选择是使用asvoid(下)到view 每行作为一个 void dtype 的值。这会将 2D 数组缩减为 1D 数组,因此您可以像往常一样使用np.in1d

import numpy as np

def asvoid(arr):
    """
    Based on http://***.com/a/16973510/190597 (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,我得在某个时候使用它们!【参考方案4】:

numpy_indexed 包(免责声明:我是它的作者)包含有效执行此类操作的功能(也使用了搜索排序)。在功能方面,它相当于 list.index 的矢量化:

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

请注意,使用 'missing' kwarg,您可以完全控制丢失项目的行为,并且它也适用于 nd 数组(fi;图像堆栈)。

更新:使用与@Rik X=[520000,28,28]searched_values=[20000,28,28] 相同的形状,它在0.8064 secs 中运行,使用missing=-1 来检测和表示X 中不存在的条目。

【讨论】:

【参考方案5】:

这是一个非常快速的解决方案,可以使用 numpy 和 hashlib 很好地扩展。它可以在几秒钟内处理大维矩阵或图像。我在 CPU 上 2 秒内在 520000 X (28 X 28) 阵列和 20000 X (28 X 28) 上使用了它

代码:

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

【讨论】:

请注意,基于散列的方法需要额外的过滤步骤来消除错误的散列冲突,才能证明是正确的。【参考方案6】:

另一种方法是使用来自scipy.spatial.distancecdist 函数,如下所示:

np.nonzero(cdist(X, searched_values) == 0)[0]

基本上,我们得到X 的行号,它们与searched_values 中的一行的距离为零,这意味着它们是相等的。如果您将行视为坐标,这是有道理的。

【讨论】:

以上是关于在 numpy 数组中查找多个值的行索引的主要内容,如果未能解决你的问题,请参考以下文章

在 1D NumPy 数组中查找值的索引/位置(具有相同的值)[重复]

使用索引同时从 numpy 2D 数组的行中减去多个值

在二维 numpy 数组中查找匹配的行

在javascript中的数组中查找多个最小值的索引

在Python中查找给定数组中最小值的索引

遍历隐藏那些不包含数组中列出的值的行