NumPy索引:使用布尔数组进行广播
问题内容:
与此问题相关,我通过布尔数组遇到了索引行为,并且广播不了解。我们知道可以使用整数索引和广播在2维中为NumPy数组建立索引。这是在docs中指定的:
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]])
所以我的问题是:为什么广播只能使用整数,而不能使用布尔数组?是否记录了这种行为?还是我误解了一个更根本的问题?
问题答案:
正如@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数组索引。因此,您会看到有关“索引过多”的错误。
文档中提到的惊喜与您的示例非常接近:如果您未注入该单例维度该怎么办?从length-3和length-4布尔数组开始,您将得到一个length-2高级索引,即size的1d数组(2,)
。这从来都不是您想要的,这将导致我们在该主题中遇到另一个琐事。
在计划改进高级索引方面进行了很多讨论,请参阅正在进行的草案NEP
21。问题的要点是,尽管已明确记录,但numpy中的花式索引具有一些非常古怪的功能,这些功能几乎对任何事物都没有用,但是如果您通过产生令人惊讶的结果而不是错误而犯了错误,那么它们可能会咬人。
NEP的相关报价:
涉及多个数组索引的混合情况也令人惊讶,并且问题也较少,因为当前行为是如此无用,以致于在实践中很少遇到。当布尔数组索引与另一个布尔或整数数组混合时,布尔数组将转换为整数数组索引(等效于
np.nonzero()
),然后进行广播。例如,索引尺寸的2D阵列(2, 2)
像x[[True, False], [True, False]]
产生一个1D向量,其具有的形状(1,)
,不是2D子矩阵与形状(1, 1)
。
现在,我强调NEP正在进行中,但是NEP当前状态的建议之一是在上述高级索引情况下禁止布尔数组,而仅允许它们在“外部索引”中使用”场景,即究竟是什么np.ix_
可以帮助您使用布尔数组:
布尔索引在概念上是外部索引。以传统索引(即当前行为)的方式与其他高级索引一起广播通常没有帮助或没有明确定义。因此,希望“非零”加广播行为的用户可以手动执行此操作。
我的观点是,布尔高级索引的行为及其不赞成使用的状态(或不存在)可能会在不久的将来发生变化。