NumPy多维数组索引交换轴顺序。

13

我正在使用多维Numpy数组。 我发现在使用其他索引数组访问这些数组时会出现一些不一致的行为。 例如:

import numpy as np
start = np.zeros((7,5,3))
a     = start[:,:,np.arange(2)]
b     = start[0,:,np.arange(2)]
c     = start[0,:,:2]
print 'a:', a.shape
print 'b:', b.shape
print 'c:', c.shape
在这个例子中,我得到了结果:
a: (7, 5, 2)
b: (2, 5)
c: (5, 2)

我感到困惑。为什么“b”和“c”没有相同的维度?为什么“b”会交换轴的顺序,而“a”不会呢?

通过大量的单元测试,我已经能够设计出适应这些不一致性的代码,但是理解发生了什么将会受到赞赏。

供参考,我正在使用Python 2.7.3以及通过MacPorts安装的Numpy 1.6.2。

1个回答

13

从语法上看,这似乎是一种不一致的情况,但从语义上讲,你在这里做了两件非常不同的事情。 在定义ab时,你正在进行高级索引,有时被称为花式索引,它返回数据的副本。 在定义c时,你正在进行基本切片,它返回数据的视图。

要区分它们的区别,有助于理解如何将索引传递给python对象。这里有一些例子:

>>> class ShowIndex(object):
...     def __getitem__(self, index):
...         print index
... 
>>> ShowIndex()[:,:]
(slice(None, None, None), slice(None, None, None))
>>> ShowIndex()[...,:]
(Ellipsis, slice(None, None, None))
>>> ShowIndex()[0:5:2,::-1]
(slice(0, 5, 2), slice(None, None, -1))
>>> ShowIndex()[0:5:2,np.arange(3)]
(slice(0, 5, 2), array([0, 1, 2]))
>>> ShowIndex()[0:5:2]
slice(0, 5, 2)
>>> ShowIndex()[5, 5]
(5, 5)
>>> ShowIndex()[5]
5
>>> ShowIndex()[np.arange(3)]
[0 1 2]

可以看到,有很多不同的可能配置。首先,可以传递单个项或元组。其次,元组可以包含切片对象、省略号对象、普通整数或numpy数组。

只有当您传递诸如int、slice或Ellipsis对象或None(与numpy.newaxis相同)等对象时,才会激活基本切片。这些可以单独或作为元组传递。以下是文档对如何激活基本切片的说明:

当obj是切片对象(在括号内构造start:stop:step符号),整数或切片对象和整数的元组时触发基本切片。省略号和newaxis对象也可以与它们交替使用。为了保持向后兼容Numeric中的常见用法,如果选择对象是包含切片对象、省略号对象或newaxis对象但没有整数数组或其他嵌入序列的任何序列(例如列表),则基本切片也会启动。

当您传递numpy数组、仅包含整数或包含任何类型子序列的非元组序列或包含数组或子序列的元组时,将激活高级索引。

有关高级索引和基本切片之间差异的详细信息,请参阅文档(链接如上)。但在这种情况下,对于使用部分索引时发生的以下行为,我很清楚。它与使用部分索引时的以下行为有关:

部分索引的规则是结果的形状(或用于设置的解释形状)是x的形状,其中索引子空间被替换为广播的索引子空间。如果索引子空间紧挨在一起,则广播的索引空间直接替换x中所有索引的子空间。如果索引子空间被切片对象分开,则先是广播的索引空间,然后是x的切片子空间。

在使用高级索引的定义中,您有效地将序列[0, 1]作为元组的第三个项传递,并且由于没有进行广播(因为没有其他序列),因此一切都按预期发生。

在使用高级索引的b的定义中,您实际上传递了两个序列,即第一个项[0](它被转换为intp数组)和第三个项[0, 1]。这两个项会进行广播,并且结果具有与第三个项相同的形状。但是,由于已经发生了广播,我们面临一个问题:在新形状元组中的哪个位置插入广播形状?正如文档所说:

没有明确的放置索引子空间的位置,因此它被添加到开头。

因此,广播所得的2移动到形状元组的开头,产生了表面上的转置。


感谢您提供详细的解释,这非常有帮助。由于切片和广播索引导致的奇怪行为仍然出乎意料,因此很难编写代码。例如:start [0,:,np.arange(2)] = np.ones((5,2))似乎是合法的,但由于轴重新排序,它实际上并不是。 - gbarter
@gbarter 为了保持原始形状,您必须使用切片。例如,这将起作用:start[:1,:,np.arange(2)] = np.ones((5,2)) - jorgeca

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