使用numpy进行奇怪的索引

31

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

我还有一个等于np.array([0,10,20])的数组y。当我使用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个元素。 - Code-Apprentice
2个回答

24
这是 numpy 如何使用高级索引来广播数组形状。当您将第一个索引传递为 0,并将最后一个索引传递为 y 时,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有一个非常易于理解的顺序形式,我们现在可以使用它来展示正在发生的事情...

第一个维度就像有两个Excel工作簿,第二个维度就像每个工作簿中有三个工作表,第三个维度就像每个工作表中有四行,最后一个维度是每行(或每个工作表的列)有五个值。

用这种方式看待,请求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 的数组可以表示为一个切片,所以你实际上可以通过以下方式获得可分配的数组视图:

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函数中一样。

所以基本上,如果您可以构造符合子切片条件的切片,您就可以进行赋值。


5
它被称为“高级索引与基本索引的组合”。在“高级索引与基本索引的组合”中,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] 上,0 和 y 是高级索引。它们一起广播以产生维度 (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,)

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