使用numpy.where()遍历矩阵

3

我不是很明白numpy.where()的用法:

假设我有一个二维的numpy ndarray:

import numpy as np
twodim =  np.array([[1, 2, 3, 4],  [1, 6, 7, 8], [1, 1, 1, 12],  [17, 3, 15, 16], [17, 3, 18, 18]])

现在,我想创建一个函数,用于“检查”这个Numpy数组的各种条件。

array([[ 1,  2,  3,  4],
       [ 1,  6,  7,  8],
       [ 1,  1,  1, 12],
       [17,  3, 15, 16],
       [17,  3, 18, 18]])

例如,这个数组中哪些元素是 (A) 偶数 (B) 大于7 (C) 可被3整除?
我想使用 numpy.where() 来实现,并遍历该数组的每个元素,最终找到符合所有条件的元素(如果存在这样的元素)。
   even_entries = np.where(twodim % 2 == 0)
   greater_seven = np.where(twodim > 7 )
   divisible_three = np.where(twodim % 3 == 0)

如何做到这一点?我不确定如何迭代布尔值... 我可以通过以下方式访问矩阵的索引(i,j):
np.argwhere(even_entries)

我们可以做类似这样的事情:
import numpy as np
twodim =  np.array([[1, 2, 3, 4],  [1, 6, 7, 8], [1, 1, 1, 12],  [17, 3, 15, 16], [17, 3, 18, 18]])
even_entries = np.where(twodim % 2 == 0)
greater_seven = np.where(twodim > 7 )
divisible_three = np.where(twodim % 3 == 0)
for row in even_entries:
    for item in row:
        if item: #equivalent to `if item == True`
                for row in greater_seven:
                    for item in row:
                        if item: #equivalent to `if item == True`
                            for row in divisible_three:
                                for item in row:
                                    if item: #equivalent to `if item == True`
                                        # something like print(np.argwhere())

有什么建议吗?

编辑1:下面有很好的想法。正如@hpaulj所提到的,“您的测试生成与twodim形状相同的布尔矩阵”。 这是我在摆弄中遇到的问题——并非所有条件语句都会生成与我的起始矩阵形状相同的矩阵。例如,假设我正在比较数组元素是否具有与左侧或右侧匹配的数组(即水平比较)。

twodim[:, :-1] == twodim[:, 1:]

这导致产生一个(5,3)的布尔数组,而我们原始的矩阵是一个(5,4)的数组。
array([[False, False, False],
       [False, False, False],
       [ True,  True, False],
       [False, False, False],
       [False, False,  True]], dtype=bool)

如果我们沿着垂直方向进行相同的操作,那么得到的是一个(4,4)的布尔数组,而原始矩阵的形状是(5,4)。
twodim[:-1] == twodim[1:]

array([[ True, False, False, False],
       [ True, False, False, False],
       [False, False, False, False],
       [ True,  True, False, False]], dtype=bool) 

如果我们想知道哪些条目具有垂直水平对,很难确定我们在哪个维度上。


不要使用 where。我不知道为什么新的NumPy用户会继续使用它,但这很少是一个好主意。直接使用布尔掩码可以更轻松地完成此操作。 - user2357112
3个回答

1
你的测试会产生一个与 twodim 相同形状的布尔矩阵:
In [487]: mask3 = twodim%3==0
In [488]: mask3
Out[488]: 
array([[False, False,  True, False],
       [False,  True, False, False],
       [False, False, False,  True],
       [False,  True,  True, False],
       [False,  True,  True,  True]], dtype=bool)

如其他答案所述,您可以逻辑地组合测试 - 使用and和or。 np.wherenp.nonzero相同(在此用法中),只返回True值的坐标 - 作为2个数组的元组。
In [489]: np.nonzero(mask3)
Out[489]: 
(array([0, 1, 2, 3, 3, 4, 4, 4], dtype=int32),
 array([2, 1, 3, 1, 2, 1, 2, 3], dtype=int32))

argwhere 返回相同的值,但作为转置的二维数组。

In [490]: np.argwhere(mask3)
Out[490]: 
array([[0, 2],
       [1, 1],
       [2, 3],
       [3, 1],
       [3, 2],
       [4, 1],
       [4, 2],
       [4, 3]], dtype=int32)

无论是mask还是tuple都可以直接用来索引您的数组:

In [494]: twodim[mask3]
Out[494]: array([ 3,  6, 12,  3, 15,  3, 18, 18])
In [495]: twodim[np.nonzero(mask3)]
Out[495]: array([ 3,  6, 12,  3, 15,  3, 18, 18])
< p > argwhere 不能直接用于索引,但在迭代方面可能更合适,特别是如果您想要索引以及值:

In [496]: for i,j in np.argwhere(mask3):
   .....:     print(i,j,twodim[i,j])
   .....:     
0 2 3
1 1 6
2 3 12
3 1 3
3 2 15
4 1 3
4 2 18
4 3 18

使用where关键字同样需要一个zip

for i,j in zip(*np.nonzero(mask3)): print(i,j,twodim[i,j])

一般而言,在 numpy 中我们尽量避免迭代。如果可以直接使用 twodim[mask],你的代码将会更快。

布尔掩码的逻辑组合比 where 索引的组合更容易实现。要使用索引,我可能会采用 set 操作(并集、交集、差集)。


关于缩小尺寸的测试,您需要决定其如何映射到原始数组(和其他测试)。例如:
一个 (5,3) 的掩码(列之间的差异):
In [505]: dmask=np.diff(twodim, 1).astype(bool)
In [506]: dmask
Out[506]: 
array([[ True,  True,  True],
       [ True,  True,  True],
       [False, False,  True],
       [ True,  True,  True],
       [ True,  True, False]], dtype=bool)

它可以索引原始数组的3列。
In [507]: twodim[:,:-1][dmask]
Out[507]: array([ 1,  2,  3,  1,  6,  7,  1, 17,  3, 15, 17,  3])
In [508]: twodim[:,1:][dmask]
Out[508]: array([ 2,  3,  4,  6,  7,  8, 12,  3, 15, 16,  3, 18])

它也可以与另一种面罩的3列组合使用:
In [509]: dmask & mask3[:,:-1]
Out[509]: 
array([[False, False,  True],
       [False,  True, False],
       [False, False, False],
       [False,  True,  True],
       [False,  True, False]], dtype=bool)

使用布尔数组形式组合测试仍然比使用 where 索引更容易。


@ hpaulj 你的测试生成一个与twodim形状相同的布尔矩阵。 这是我在玩耍时遇到的问题——并非所有条件语句都会生成与我的起始矩阵形状相同的矩阵。请参见上面编辑中的注释。 - ShanZhengYang
我添加了你的示例。 - hpaulj
谢谢。还有一些我不太明白的地方。我们的原始矩阵是(5,4),而“dmask”是(5,3)。我们最终合并的掩码难道不应该是形状为(5,4)吗?那么行之间的差异呢,它的形状是(4,4)。因此,要知道哪些条目具有水平伙伴和垂直伙伴,需要什么样的“最终掩码”? - ShanZhengYang
现在我想了想,你的方法是正确的。最终矩阵'mask'应该是(4,3)形状的。我仍然有点困惑,在这个设置中对于这些配对是在哪里考虑的... - ShanZhengYang
argwhere对应的是缩小尺寸后的掩码。将它们映射到原始数组可能需要使用索引调整,例如twodim[i, j+1] - hpaulj
嗯,我不确定那样做会起作用。dmask & mask3[:,:-1] 不同于 dmask & mask3[:,1:]。一个是从右边比较,另一个是从左边比较。对于对角线成对的情况,这会变得更加混乱。 - ShanZhengYang

0

如果你想找到满足所有三个条件的地方:

import numpy as np
twodim =  np.array([[1, 2, 3, 4],  [1, 6, 7, 8], [1, 1, 1, 12],  [17, 3, 15, 16], [17, 3, 18, 18]])

mask = (twodim % 2 == 0) & (twodim > 7) & (twodim % 3 =0)

print(twodim[mask])

[12 18 18]

不确定您最终想要什么,是所有行中的所有元素都必须满足条件并找到这些行,还是您只想要单个元素。


我很想这样做。不幸的是,正如我在上面编辑的那样,对于某些条件创建这样的掩码很困难。维度无法正确地一起广播。 - ShanZhengYang
1
@ShanZhengYang:然后只需切掉重叠的部分以&连接,或在创建掩码时进行切片(因此twodim[appropriate:piece]>7)。尝试处理where输出将会更慢且更笨拙。 - user2357112

0
import numpy as np
twodim =  np.array([[1, 2, 3, 4],  [1, 6, 7, 8], [1, 1, 1, 12],  [17, 3, 15, 16], [17, 3, 18, 18]])
condition = (twodim % 2. == 0.) & (twodim > 7.) & (twodim % 3. ==0.)
location = np.argwhere(condition == True) 


for i in location: 
     print i, twodim[i[0],i[1]],

>>> [2 3] 12 [4 2] 18 [4 3] 18

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