numpy 在ndarray上建立索引导致形状错误

llmtgqce  于 2023-03-12  发布在  其他
关注(0)|答案(2)|浏览(104)

以下代码段

x = np.ones((10,10,10))
x = x[2,:,[2,3,7]]
print(x.shape)

结果是x.shape = (3,10)而不是(10,3)。如何使用列表索引第三维以获得形状(10,3)

rlcwz9us

rlcwz9us1#

我认为stridesbase给予了一些额外的见解,如果不是一个实际的解释。
制作3D阵列:

In [77]: x = np.arange(24).reshape(2,3,4); x.shape, x.strides
Out[77]: ((2, 3, 4), (48, 16, 4))

中间的薄片使“怪异”的形状:

In [78]: y = x[1,:,[1,2]]; y.shape, y.strides
Out[78]: ((2, 3), (12, 4))

In [79]: y
Out[79]: 
array([[13, 17, 21],
       [14, 18, 22]])

y是一个副本(自己的基础),并且步幅对于该形状是正常的。
现在尝试两步索引,给出预期的形状:

In [80]: z = x[1][:,[1,2]]; z.shape, z.strides
Out[80]: ((3, 2), (4, 12))

步幅是“反向的”,我们从转置中得到的。

In [81]: z
Out[81]: 
array([[13, 14],
       [17, 18],
       [21, 22]])

事实上,它的base是相同的y数组。

In [82]: z.base
Out[82]: 
array([[13, 17, 21],
       [14, 18, 22]])

列的高级索引(对于2d数组)产生这个转置view
y的情况看起来很像[:,[1,2]]的索引,但不能(出于某种原因)执行我们在z中看到的转置。
完整播出版本:

In [87]: w = x[1,np.arange(3)[:,None],[1,2]]; w.shape, w.strides
Out[87]: ((3, 2), (8, 4))

In [88]: w
Out[88]: 
array([[13, 14],
       [17, 18],
       [21, 22]])

其形状和值为z,但跨距不同。
如果我们将第一个标量索引替换为一个切片:

In [103]: u = x[1:2,:,[1,2]][0]; u.shape, u.strides
Out[103]: ((3, 2), (4, 12))

In [104]: u.base
Out[104]: 
array([[[13, 17, 21]],

       [[14, 18, 22]]])

In [105]: u.base.shape
Out[105]: (2, 1, 3)

注意,第三维[1,2]索引是第一个,正如在“中间切片”的情况中一样;它把基数(2,1,3)转换成(1,3,2),我把它简化成(3,2)

y3bcpkx1

y3bcpkx12#

NB:我将您的数组更改为x = np.arange(125).reshape((5, 5, 5)),并将索引/切片调整为y = x[2, :, [0, 2, 4]],以便更容易地看到它被选中。

TL;DR您可以转置结果,或者将第一个轴上的索引表示为一个切片,然后挤压该结果:

>>> x[2, :, [0, 2, 4]].T
array([[50, 52, 54],
       [55, 57, 59],
       [60, 62, 64],
       [65, 67, 69],
       [70, 72, 74]])

>>> x[2:3, :, [0, 2, 4]].squeeze()
array([[50, 52, 54],
       [55, 57, 59],
       [60, 62, 64],
       [65, 67, 69],
       [70, 72, 74]])

我认为一个更有趣的问题是“为什么”会发生这种情况。很长一段时间以来,我把它内化为“numpy有时候会做一些奇怪的事情,记住它就行了”,但它确实有一个解释,你可以应用到任何一般情况下。从链接@Chrysophylaxs共享,结合高级和基本索引是通过第一个切片,然后高级索引来处理的。如果你在这里这样做,你会得到:

>>> x[2, :]
array([[50, 51, 52, 53, 54],
       [55, 56, 57, 58, 59],
       [60, 61, 62, 63, 64],
       [65, 66, 67, 68, 69],
       [70, 71, 72, 73, 74]])

>>> x[2, :][:, [0, 2, 4]]
array([[50, 52, 54],
       [55, 57, 59],
       [60, 62, 64],
       [65, 67, 69],
       [70, 72, 74]])

它的形状是(5, 3),正如我们所料。
然而,这并不是numpy正在做的事情,正如那个链接中提到的,当一个维度上的单个索引被请求为另一个维度上的高级索引时,这个单个索引被视为高级索引,而不是一个切片。
指标组合有两种情况需要区分:

  • 高级索引由切片、省略号或新轴分隔。例如x[arr 1,:,arr 2]。
  • 高级索引都是彼此相邻的。例如x[...,arr 1,arr 2,:]但不是x[arr 1,:,1],因为1在这方面是高级索引

当情况是这样时,
由高级索引操作产生的维在结果数组中排在的前面,子空间维在其后。
由高级索引操作得到的维度是(1, 3)(3,)(标量索引2不提供维度,列表索引[0, 2, 4]提供一个维度,大小为3)感谢@Mad Physicist和@Chrysophylaxs的澄清,这就是为什么我们得到(3, 5)数组(你得到(3, 10)数组)

>>> x[2, :, [0, 2, 4]]
array([[50, 55, 60, 65, 70],
       [52, 57, 62, 67, 72],
       [54, 59, 64, 69, 74]])

如果使用类似的高级索引操作,在第一维上使用 * 两个 * 索引,则会得到形状为(2, 3, 5)的结果:

>>> x[[[2], [3]], :, [0, 2, 4]]
array([[[50, 55, 60, 65, 70],
        [52, 57, 62, 67, 72],
        [54, 59, 64, 69, 74]],

       [[75, 80, 85, 90, 95],
        [77, 82, 87, 92, 97],
        [79, 84, 89, 94, 99]]])

>>> x[[[2], [3]], :, [0, 2, 4]].shape
(2, 3, 5)

要得到一个(5, 3)数组(或者在您的例子中是(10, 3)数组),将第一个索引改为一个 slice,然后挤压结果,这允许numpy切换回第一个例子,在这里切片是在高级索引之前完成的。

>>> x[2:3, :, [0, 2, 4]]      # (1, 5, 3)
array([[[50, 52, 54],
        [55, 57, 59],
        [60, 62, 64],
        [65, 67, 69],
        [70, 72, 74]]])

>>> x[2:3, :, [0, 2, 4]].squeeze()
array([[50, 52, 54],
       [55, 57, 59],
       [60, 62, 64],
       [65, 67, 69],
       [70, 72, 74]])

或者,只是转置(或移动/交换轴)的结果,你从你的常规索引:

>>> x[2, :, [0, 2, 4]].T
array([[50, 52, 54],
       [55, 57, 59],
       [60, 62, 64],
       [65, 67, 69],
       [70, 72, 74]])

相关问题