【问题标题】:Numpy multi-dimensional array indexing swaps axis orderNumpy多维数组索引交换轴顺序
【发布时间】:2012-08-13 21:41:39
【问题描述】:

我正在使用多维 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”?

由于大量的单元测试,我已经能够围绕这些不一致来设计我的代码,但如果能理解正在发生的事情,我们将不胜感激。

作为参考,我通过 MacPorts 使用 Python 2.7.3 和 Numpy 1.6.2。

【问题讨论】:

    标签: python numpy


    【解决方案1】:

    从句法上看,这看起来是不一致的,但从语义上看,你在这里做了两件非常不同的事情。在ab 的定义中,您正在执行advanced indexing,有时称为fancy indexing,它返回数据的副本。在c 的定义中,您正在执行basic slicing,它返回数据视图。

    为了区分,它有助于理解如何将索引传递给 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]
    

    如您所见,有许多种不同的可能配置。首先,可以传递单个项目,或者可以传递项目的元组。其次,元组可能包含slice 对象、Ellipsis 对象、纯整数或numpy 数组。

    基本切片在您传递 对象时激活,例如intslice、或Ellipsis 对象或None(与numpy.newaxis 相同)。这些可以单独传递,也可以在一个元组中传递。以下是文档关于如何激活基本切片的内容:

    当 obj 是切片对象(由括号内的 start:stop:step 表示法构造)、整数或切片对象和整数的元组时,就会发生基本切片。 Ellipsis 和 newaxis 对象也可以穿插其中。为了保持向后兼容 Numeric 中的常见用法,如果选择对象是包含切片对象、Ellipsis 对象或 newaxis 对象的任何序列(例如列表),但没有整数数组或其他嵌入序列。

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

    有关高级索引和基本切片有何不同的详细信息,请参阅文档(链接到上面)。但在这种特殊情况下,我很清楚发生了什么。使用部分索引时,它与以下行为有关:

    部分索引的规则是结果的形状(或要在设置中使用的对象的解释形状)是 x 的形状,索引子空间替换为广播索引子空间。如果索引子空间彼此相邻,则广播索引空间直接替换 x 中的所有索引子空间。如果索引子空间是分开的(通过切片对象),那么首先广播的索引空间,然后是 x 的切片子空间。

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

    在您对b 的定义中,同样使用高级索引,您有效地传递了两个 序列,[0]、第一项(转换为intp 数组)和@ 987654342@,第三项。这两项一起广播,结果与第三项具有相同的形状。然而,由于广播已经发生,我们面临一个问题:我们在新的形状元组中的什么地方插入广播的形状?正如文档所说,

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

    因此,广播产生的2 被移动到形状元组的开头,从而产生明显的转置。

    【讨论】:

    • 感谢您的详细解释。这很有帮助。切片 + 广播索引导致的奇怪行为仍然出乎意料,以至于难以编码。例如: start[0,:,np.arange(2)] = np.ones((5,2)) 似乎是合法的,但由于轴重新排序,它不是
    • @gbarter 要保持原始形状,您必须使用切片。即,这将起作用:start[:1,:,np.arange(2)] = np.ones((5,2))
    猜你喜欢
    • 2015-02-20
    • 2021-10-24
    • 2015-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-25
    • 2018-01-22
    • 1970-01-01
    相关资源
    最近更新 更多