使用numpy奇怪的索引


27

我有一个变量x,其形状为(2,2,50,100)。

我也有一个数组y,它等于np.array([0,10,20])。当我索引x [0,:,:,y]时,发生了一件奇怪的事情。

x = np.full((2,2,50,100),np.nan)
y = np.array([0,10,20])
print(x.shape)
(2,2,50,100)
print(x[:,:,:,y].shape)
(2,2,50,3)
print(x[0,:,:,:].shape)
(2,50,100)
print(x[0,:,:,y].shape)
(3,2,50)

为什么最后一个输出(3,2,50)而不是(2,50,3)?


我是numpy的新手,所以您的问题没有答案。为了进一步对此进行研究,我建议找到一个较小的示例,该示例只有2D或3D,并且在任何轴上最多只能包含10个元素。
代码学徒

Answers:


21

这是numpy如何使用高级索引来广播数组形状的方法。当您0为第一个索引和y最后一个索引传递a时,numpy将广播0与相同的形状y。下列等价成立:x[0,:,:,y] == x[(0, 0, 0),:,:,y]。这是一个例子

import numpy as np

x = np.arange(120).reshape(2,3,4,5)
y = np.array([0,2,4])

np.equal(x[0,:,:,y], x[(0, 0, 0),:,:,y]).all()
# returns:
True

现在,由于您有效地传递了两组索引,因此您正在使用高级索引API来形成(在这种情况下)成对的索引。

x[(0, 0, 0),:,:,y])

# equivalent to
[
  x[0,:,:,y[0]], 
  x[0,:,:,y[1]], 
  x[0,:,:,y[2]]
]

# equivalent to
rows = np.array([0, 0, 0])
cols = y
x[rows,:,:,cols]

# equivalent to
[
  x[r,:,:,c] for r, c in zip(rows, columns)
]

其第一维度与的长度相同y。这就是您所看到的。

例如,看一下具有4个维的数组,这些数组在下一个块中进行了描述:

x = np.arange(120).reshape(2,3,4,5)
y = np.array([0,2,4])

# x looks like:
array([[[[  0,   1,   2,   3,   4],    -+      =+
         [  5,   6,   7,   8,   9],     Sheet1  |
         [ 10,  11,  12,  13,  14],     |       |
         [ 15,  16,  17,  18,  19]],   -+       |
                                                Workbook1
        [[ 20,  21,  22,  23,  24],    -+       |
         [ 25,  26,  27,  28,  29],     Sheet2  |
         [ 30,  31,  32,  33,  34],     |       |
         [ 35,  36,  37,  38,  39]],   -+       |
                                                |
        [[ 40,  41,  42,  43,  44],    -+       |
         [ 45,  46,  47,  48,  49],     Sheet3  |
         [ 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],
         [ 75,  76,  77,  78,  79]],

        [[ 80,  81,  82,  83,  84],
         [ 85,  86,  87,  88,  89],
         [ 90,  91,  92,  93,  94],
         [ 95,  96,  97,  98,  99]],

        [[100, 101, 102, 103, 104],
         [105, 106, 107, 108, 109],
         [110, 111, 112, 113, 114],
         [115, 116, 117, 118, 119]]]])

x 有一个非常容易理解的顺序形式,我们现在可以使用它来显示正在发生的事情...

第一个维度就像有2个Excel工作簿,第二个维度就像每个工作簿中有3个工作表,第三个维度就像每张工作表有4行,最后一个维度是每行5个值(或每张工作表的列)。

这样看待,就是问x[0,:,:,0]:“在第一个工作簿中,对于每一页,每一行,给我第一个值/列。”

x[0,:,:,y[0]]
# returns:
array([[ 0,  5, 10, 15],
       [20, 25, 30, 35],
       [40, 45, 50, 55]])

# this is in the same as the first element in:
x[(0,0,0),:,:,y]

但是,现在有了高级索引,我们可以将其x[(0,0,0),:,:,y]视为“在第一个工作簿中,对于每张工作表,每一行,给我y第一个值/列。好吧,现在针对每个值y

x[(0,0,0),:,:,y]
# returns:
array([[[ 0,  5, 10, 15],
        [20, 25, 30, 35],
        [40, 45, 50, 55]],

       [[ 2,  7, 12, 17],
        [22, 27, 32, 37],
        [42, 47, 52, 57]],

       [[ 4,  9, 14, 19],
        [24, 29, 34, 39],
        [44, 49, 54, 59]]])

令人发疯的是,numpy将广播以匹配索引数组的外部尺寸。因此,如果您要执行与上述相同的操作,但对于“ Excel工作簿”都不需要循环和连接。您可以只将数组传递给第一维,但它必须具有兼容的形状。

传递整数将广播到y.shape == (3,)。如果您想将数组作为第一个索引传递,则仅数组的最后一个维度必须与兼容y.shape。即,第一个索引的最后一个维度必须为3或1。

ix = np.array([[0], [1]])
x[ix,:,:,y].shape
# each row of ix is broadcast to length 3:
(2, 3, 3, 4)

ix = np.array([[0,0,0], [1,1,1]])
x[ix,:,:,y].shape
# this is identical to above:
(2, 3, 3, 4)

ix = np.array([[0], [1], [0], [1], [0]])
x[ix,:,:,y].shape
# ix is broadcast so each row of ix has 3 columns, the length of y
(5, 3, 3, 4)

在文档中找到了简短的解释:https : //docs.scipy.org/doc/numpy/reference/arrays.indexing.html#combining-advanced-and-basic-indexing


编辑:

从最初的问题开始,要获得所需的单线定价,可以使用x[0][:,:,y]

x[0][:,:,y].shape
# returns
(2, 50, 3)

但是,如果要分配给这些子切片,则必须非常小心,因为它正在查看原始阵列的共享内存视图。否则,分配将不是原始数组,而是副本。

仅当使用整数或切片对数组进行子集化(即x[:,0:3,:,:]或)时,才会发生共享内存x[0,:,:,1:-1]

np.shares_memory(x, x[0])
# returns:
True

np.shares_memory(x, x[:,:,:,y])
# returns:
False

在您的原始问题和我的示例y中,都不是int或slice,因此最终总是会分配给原始副本。

但!因为你的阵列y可以表示为一个切片,你CAN实际上得到通过您的阵列的分配图:

x[0,:,:,0:21:10].shape
# returns:
(2, 50, 3)

np.shares_memory(x, x[0,:,:,0:21:10])
# returns:
True

# actually assigns to the original array
x[0,:,:,0:21:10] = 100

在这里,我们使用切片0:21:10来获取其中的每个索引range(0,21,10)。我们必须使用,21而不是20因为像在range函数中那样,从切片中排除了停止点。

因此,基本上,如果您可以构造适合您的细分条件的切片,则可以进行分配。


4

它被称为combining advanced and basic indexing。在中combining advanced and basic indexing,numpy首先在高级索引中进行索引,然后将结果细分/连接到基本索引的维度。

来自文档的示例:

令x.shape为(10,20,30,40,50)并假定ind_1和ind_2可以广播为形状(2,3,4)。然后,x [:,ind_1,ind_2]的形状为(10,2,3,4,40,50),因为X的(20,30)形子空间已被替换为X(:3,4)的子空间索引。但是,x [:,ind_1,:,ind_2]的形状为(2,3,4,10,30,50),因为在索引子空间中没有明确的放置位置,因此将其固定在开始位置。始终可以使用.transpose()将子空间移动到所需的任何位置。请注意,此示例无法使用take复制。

所以,上x[0,:,:,y]0y有提前索引。它们一起广播以产生尺寸(3,)

In [239]: np.broadcast(0,y).shape
Out[239]: (3,)

(3,)使第二维和第三维的开始成为(3, 2, 50)

要看到,第一个和最后一个维度是真正广播在一起,你可以尝试改变0,以[0,1]看广播的错误

print(x[[0,1],:,:,y])

Output:
IndexError                                Traceback (most recent call last)
<ipython-input-232-5d10156346f5> in <module>
----> 1 x[[0,1],:,:,y]

IndexError: shape mismatch: indexing arrays could not be broadcast together with
 shapes (2,) (3,)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.