首页 > 解决方案 > NumPy 索引:使用布尔数组广播

问题描述

这个问题相关,我通过布尔数组和广播遇到了我不理解的索引行为。我们知道可以使用整数索引和广播在二维中索引 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]])

所以我的问题是:为什么广播适用于整数,而不适用于布尔数组?这种行为是否记录在案?还是我误解了一个更根本的问题?

标签: pythonarraysnumpyindexingarray-broadcasting

解决方案


正如@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()],它将在 中放入一个长度为 3 的元组a,对应于 3d 数组索引。因此,您会看到有关“索引过多”的错误。

文档中提到的惊喜与您的示例非常接近:如果您没有注入该单例维度怎么办?从长度为 3 和长度为 4 的布尔数组开始,您最终会得到长度为 2 的高级索引,即 size 的一维数组(2,)。这绝不是您想要的,这将我们引向该主题的另一个琐事。

关于改进高级索引的计划已经进行了很多讨论,请参阅进行中的NEP 21草案。问题的要点是 numpy 中的精美索引虽然有明确的记录,但有一些非常古怪的功能,这些功能实际上对任何事情都没有用,但如果你犯了错误,会产生令人惊讶的结果而不是错误,这可能会咬你一口。

NEP的相关引用:

涉及多个数组索引的混合情况也令人惊讶,而且问题更少,因为当前的行为是如此无用,以至于在实践中很少遇到。当一个布尔数组索引与另一个布尔数组或整数数组混合时,布尔数组被转换为整数数组索引(相当于np.nonzero())然后广播。例如,索引大小为(2, 2)like 的 2D 数组x[[True, False], [True, False]]会生成具有 shape 的 1D 向量(1,),而不是具有 shape 的 2D 子矩阵(1, 1)

现在,我要强调 NEP 的工作正在进行中,但 NEP 当前状态的建议之一是在上述高级索引情况下禁止布尔数组,只允许它们在“外部索引" 场景,即确切地np.ix_可以帮助您处理布尔数组:

布尔索引在概念上是外部索引。以传统索引[即当前行为]的方式与其他高级索引一起广播通常没有帮助或没有明确定义。因此可以期望希望“非零”加广播行为的用户手动执行此操作。

我的观点是,布尔高级索引的行为及其弃用状态(或缺乏)可能会在不远的将来发生变化。


推荐阅读