NumPy索引:使用布尔数组进行广播

6

这个问题相关,我遇到了一个关于布尔数组和广播的索引行为,我不理解。我们知道可以使用整数索引和广播在2维中索引NumPy数组。这在文档中有说明:

a = np.array([[ 0,  1,  2,  3],
              [ 4,  5,  6,  7],
              [ 8,  9, 10, 11]])

b1 = np.array([False, True, True])
b2 = np.array([True, False, True, False])

c1 = np.where(b1)[0]  # i.e. [1, 2]
c2 = np.where(b2)[0]  # i.e. [0, 2]

a[c1[:, np.newaxis], c2]  # or a[c1[:, None], c2]

array([[ 4,  6],
       [ 8, 10]])

然而,对于布尔数组不适用同样的方法。

a[b1[:, None], b2]

IndexError: too many indices for array

替代方案numpy.ix_适用于整数数组和布尔数组。这似乎是因为ix_对布尔数组执行特定的操作,以确保一致的处理。

assert np.array_equal(a[np.ix_(b1, b2)], a[np.ix_(c1, c2)])

array([[ 4,  6],
       [ 8, 10]])

所以我的问题是:为什么使用整数可以实现广播,而使用布尔数组却不能呢?这种行为是否有文档记录?还是我误解了更基本的问题?

据我所知,在内部使用布尔数组时会将其转换为整数等效数组:np.flatnonzero(ar),然后再用于索引。因此,np.flatnonzero(b1[:, np.newaxis])np.flatnonzero(b1)[:, None]不等价。 - Divakar
2
我认为问题在于np.nonzero(b1[:,None])np.nonzero(b2)在它们各自的元组的每个元素上与对方不可广播,而不是直接使用整数数组进行索引,这些整数数组可以与np.ix_(c1, c2)广播。 - Divakar
1
谢谢您的接受,但是@Divakar才是回答您实际问题的人,我只是想添加额外的观点。我不想夺取他的功劳,如果您不接受我的答案,我会更加舒适。 - Andras Deak -- Слава Україні
2
@AndrasDeak 我认为回答问题最重要的是能够让提问者满意,而不是谁来回答。如果可以的话,我会鼓励提问者接受答案。 - Divakar
1
@Divakar 我一直努力保持公正。在这种情况下,谢谢你,我会编辑并使用你的解决方案来完善答案(下次请等待你自己发布答案;))。 - Andras Deak -- Слава Україні
显示剩余2条评论
1个回答

7

正如@Divakar在评论中指出的那样,布尔型高级索引的行为就好像它们首先通过np.nonzero然后进行广播操作。请参阅相关文档以获得详细说明。引用文档所说,

一般来说,如果一个索引包含一个布尔数组,则结果将与在相同位置插入obj.nonzero()并使用上述整数数组索引机制的方式相同。 x[ind_1, boolean_array, ind_2] 等同于 x[(ind_1,) + boolean_array.nonzero() + (ind_2,)]
[...]
多个布尔索引数组或布尔索引数组与整数索引数组的组合最好使用obj.nonzero()类比来理解。函数ix_还支持布尔数组,并且可以正常工作,没有任何意外情况。
在您的情况下,广播可能不是问题,因为两个数组只有两个非零元素。问题在于结果中的维数数量:
>>> len(b1[:,None].nonzero())
2
>>> len(b2.nonzero())
1

因此,索引表达式a[b1 [:,None],b2]等效于a[b1 [:,None] .nonzero()+ b2.nonzero()] ,这将在a中放置长度为3的元组,对应于三维数组索引。因此,你看到了关于“索引太多”的错误。

文档中提到的惊喜非常接近你的例子:如果你没有注入该单例维度呢?从长度为3和长度为4的布尔数组开始,你最终会得到一个长度为2的高级索引,即大小为(2,)的1d数组。这永远不是你想要的,这导致我们了解主题中的另一件琐事。

在规划改进高级索引方面进行了大量讨论,请参见正在进行的草案NEP 21。问题的要点是,尽管numpy中的高级索引已经被明确记录,但其一些非常奇特的功能并不实用,但如果您犯了错误,这些功能可能会咬您,而不是产生错误。

来自NEP的相关引用:

涉及多个数组索引的混合情况也很令人惊讶,只不过因为当前行为如此无用,所以问题不太常见。当布尔数组索引与另一个布尔或整数数组混合时,布尔数组会转换为整数数组索引(相当于np.nonzero()),然后进行广播。例如,像x[[True, False], [True, False]]这样索引大小为(2, 2)的2D数组将产生一个形状为(1,)的1D向量,而不是形状为(1, 1)的2D子矩阵。
现在,我强调NEP仍处于进展中,但在NEP的当前状态中,建议禁止在高级索引案例(如上述情况)中使用布尔数组,并仅允许在“外部索引”场景中使用它们,即正是np.ix_可以帮助您使用布尔数组的地方:

布尔索引概念上是外部索引。与遗留索引(即当前行为)中的其他高级索引一起进行广播通常不是有益的或者定义不清的。因此,希望实现“非零”加广播行为的用户可以手动完成。

我的观点是,布尔高级索引的行为以及它们的废弃状态(或缺乏状态)可能在不久的将来发生变化。


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