使用 numpy 进行奇怪的索引

Pau*_*tti 30 python numpy

我有一个变量 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)
Run Code Online (Sandbox Code Playgroud)

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

Jam*_*mes 23

这就是 numpy 如何使用高级索引来广播数组形状。当您0为第一个索引和y最后一个索引传递 a 时,numpy 会将 the 广播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
Run Code Online (Sandbox Code Playgroud)

现在,因为您有效地传递了两组索引,所以您正在使用高级索引 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)
]
Run Code Online (Sandbox Code Playgroud)

它的第一个维度与 的长度相同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]]]])
Run Code Online (Sandbox Code Playgroud)

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]
Run Code Online (Sandbox Code Playgroud)

但是现在有了高级索引,我们可以认为x[(0,0,0),:,:,y]“在第一个工作簿中,对于每个工作表,对于每一行,给我第yth 个值/列。好的,现在为每个值做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]]])
Run Code Online (Sandbox Code Playgroud)

疯狂的地方是 numpy 将广播以匹配索引数组的外部维度。因此,如果您想执行与上述相同的操作,但对于两个“Excel 工作簿”,您不必循环和连接。您可以将数组传递给第一维,但它必须具有兼容的形状。

传递一个整数会被广播到y.shape == (3,). 如果要将数组作为第一个索引传递,则只有数组的最后一个维度必须与y.shape. 即,第一个索引的最后一个维度必须是 3 或 1。

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
Run Code Online (Sandbox Code Playgroud)

在文档中找到了一个简短的解释: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)
Run Code Online (Sandbox Code Playgroud)

但是,如果您尝试分配给这些子切片,则必须非常小心,查看原始数组的共享内存视图。否则分配将不是原始数组,而是一个副本。

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

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)
]
Run Code Online (Sandbox Code Playgroud)

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

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

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]]]])
Run Code Online (Sandbox Code Playgroud)

在这里,我们使用切片0:21:10来获取range(0,21,10). 我们必须使用21and not20因为停止点被排除在切片之外,就像在range函数中一样。

所以基本上,如果您可以构建一个符合您的细分标准的切片,您就可以进行分配。


And*_* L. 5

它被称为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) 形子空间已被来自的 (2,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,)
Run Code Online (Sandbox Code Playgroud)

(3,)贴在第 2 维和第 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,)
Run Code Online (Sandbox Code Playgroud)