NumPy 2d 数组的切片,或者如何从 nxn 数组 (n>m) 中提取 mxm 子矩阵?

Posted

技术标签:

【中文标题】NumPy 2d 数组的切片,或者如何从 nxn 数组 (n>m) 中提取 mxm 子矩阵?【英文标题】:Slicing of a NumPy 2d array, or how do I extract an mxm submatrix from an nxn array (n>m)? 【发布时间】:2011-05-14 12:45:17 【问题描述】:

我想对 NumPy nxn 数组进行切片。我想提取该数组的 m 行和列的 任意 选择(即行/列数中没有任何模式),使其成为一个新的 mxm 数组。对于这个例子,假设数组是 4x4,我想从中提取一个 2x2 数组。

这是我们的数组:

from numpy import *
x = range(16)
x = reshape(x,(4,4))

print x
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

要删除的行和列是相同的。最简单的情况是当我想提取一个位于开头或结尾的 2x2 子矩阵时,即:

In [33]: x[0:2,0:2]
Out[33]: 
array([[0, 1],
       [4, 5]])

In [34]: x[2:,2:]
Out[34]: 
array([[10, 11],
       [14, 15]])

但是,如果我需要删除另一种混合行/列怎么办?如果我需要删除第一行和第三行/行,从而提取子矩阵[[5,7],[13,15]],该怎么办?行/行可以有任何组合。我在某处读到,我只需要使用数组/行和列的索引列表来索引我的数组,但这似乎不起作用:

In [35]: x[[1,3],[1,3]]
Out[35]: array([ 5, 15])

我找到了一种方法,就是:

    In [61]: x[[1,3]][:,[1,3]]
Out[61]: 
array([[ 5,  7],
       [13, 15]])

第一个问题是它很难阅读,尽管我可以忍受。如果有人有更好的解决方案,我当然想听听。

另一件事是我读到on a forum 用数组索引数组会强制 NumPy 制作所需数组的副本,因此在处理大型数组时这可能会成为一个问题。为什么会这样/这种机制是如何工作的?

【问题讨论】:

【参考方案1】:

要回答这个问题,我们必须看看在 Numpy 中索引多维数组是如何工作的。我们首先假设您的问题中有数组x。分配给x 的缓冲区将包含从 0 到 15 的 16 个升序整数。如果您访问一个元素,例如 x[i,j],NumPy 必须计算出该元素相对于缓冲区开头的内存位置。这是通过实际计算 i*x.shape[1]+j 来完成的(并乘以 int 的大小以获得实际的内存偏移量)。

如果您通过y = x[0:2,0:2] 等基本切片提取子数组,则生成的对象将与x 共享底层缓冲区。但是如果你访问y[i,j] 会发生什么? NumPy 不能使用i*y.shape[1]+j 计算到数组的偏移量,因为属于y 的数据在内存中是不连续的。

NumPy 通过引入strides 解决了这个问题。在计算访问x[i,j]的内存偏移量时,实际计算的是i*x.strides[0]+j*x.strides[1](这已经包含了一个int大小的因素):

x.strides
(16, 4)

y 像上面那样被提取时,NumPy 不会创建一个新的缓冲区,但它确实创建一个引用相同缓冲区的新数组对象(否则 y 将等于 @ 987654337@。)新的数组对象将具有与x不同的形状,并且可能具有不同的缓冲区起始偏移量,但将与x共享步幅(至少在这种情况下):

y.shape
(2,2)
y.strides
(16, 4)

这样,计算y[i,j] 的内存偏移量将产生正确的结果。

但是 NumPy 应该为z=x[[1,3]] 之类的东西做些什么呢?如果原始缓冲区用于z,strides 机制将不允许正确索引。 NumPy 理论上可以添加一些比 strides 更复杂的机制,但这会使元素访问相对昂贵,以某种方式违背了数组的整体理念。此外,视图不再是一个真正轻量级的对象。

the NumPy documentation on indexing 对此进行了深入介绍。

哦,差点忘了您的实际问题:以下是如何使具有多个列表的索引按预期工作:

x[[[1],[3]],[1,3]]

这是因为索引数组是broadcasted 到一个共同的形状。 当然,对于这个特定的示例,您也可以使用基本切片:

x[1::2, 1::2]

【讨论】:

应该可以对数组进行子类化,这样就可以有一个“slcie-view”对象,它将索引重新映射到原始数组。这可能可以满足 OP 的需求 @jsbueno:这适用于 Python 代码,但不适用于 Scipy/Numpy 环绕的 C/Fortran 例程。这些封装的例程是 Numpy 的强大之处。 Soo.. x[[[1],[3]],[1,3]] 和 x[[1,3],:][:,[1, 3]]?我的意思是有比其他变体更好用的变体吗? @JC: x[[[1],[3]],[1,3]] 只创建一个新数组,而x[[1,3],:][:,[1,3]] 复制两次,所以使用第一个。 @JC:或者使用贾斯汀回答中的方法。【参考方案2】:

正如 Sven 提到的,x[[[0],[2]],[1,3]] 将返回与第 1 列和第 3 列匹配的第 0 行和第 2 行,而x[[0,2],[1,3]] 将返回数组中的值 x[0,1] 和 x[2,3] .

在我给出的第一个示例中,有一个有用的函数numpy.ix_。您可以使用x[numpy.ix_([0,2],[1,3])] 执行与我的第一个示例相同的操作。这可以使您不必输入所有这些额外的括号。

【讨论】:

【参考方案3】:

我不认为x[[1,3]][:,[1,3]] 难以阅读。如果你想更清楚你的意图,你可以这样做:

a[[1,3],:][:,[1,3]]

我不是切片方面的专家,但通常情况下,如果您尝试切片成一个数组并且值是连续的,您会返回一个更改了步幅值的视图。

例如在您的输入 33 和 34 中,虽然您得到一个 2x2 数组,但步幅为 4。因此,当您索引下一行时,指针会移动到内存中的正确位置。

显然,这种机制不适用于索引数组的情况。因此,numpy 将不得不制作副本。毕竟,许多其他矩阵数学函数依赖于大小、步幅和连续内存分配。

【讨论】:

【参考方案4】:

如果您想跳过每隔一行和每隔一列,那么您可以使用基本切片来做到这一点:

In [49]: x=np.arange(16).reshape((4,4))
In [50]: x[1:4:2,1:4:2]
Out[50]: 
array([[ 5,  7],
       [13, 15]])

这会返回一个视图,而不是数组的副本。

In [51]: y=x[1:4:2,1:4:2]

In [52]: y[0,0]=100

In [53]: x   # <---- Notice x[1,1] has changed
Out[53]: 
array([[  0,   1,   2,   3],
       [  4, 100,   6,   7],
       [  8,   9,  10,  11],
       [ 12,  13,  14,  15]])

z=x[(1,3),:][:,(1,3)] 使用高级索引并因此返回一个副本:

In [58]: x=np.arange(16).reshape((4,4))
In [59]: z=x[(1,3),:][:,(1,3)]

In [60]: z
Out[60]: 
array([[ 5,  7],
       [13, 15]])

In [61]: z[0,0]=0

注意x 没有改变:

In [62]: x
Out[62]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

如果您希望选择任意行和列,则不能使用基本切片。您必须使用高级索引,例如x[rows,:][:,columns],其中rowscolumns 是序列。这当然会给您原始数组的副本,而不是视图。正如人们所期望的那样,因为 numpy 数组使用连续内存(具有恒定步幅),并且无法生成具有任意行和列的视图(因为这需要非常量步幅)。

【讨论】:

【参考方案5】:

使用 numpy,您可以为索引的每个组件传递一个切片 - 因此,您上面的 x[0:2,0:2] 示例有效。

如果你只是想均匀地跳过列或行,你可以传递包含三个组件的切片 (即开始、停止、步进)。

同样,对于您上面的示例:

>>> x[1:4:2, 1:4:2]
array([[ 5,  7],
       [13, 15]])

这基本上是:在第一维中切片,从索引 1 开始,当索引等于或大于 4 时停止,并在每次遍历中将索引添加 2。第二个维度也是如此。再说一遍:这只适用于恒定的步骤。

您必须在内部做一些完全不同的事情的语法 - x[[1,3]][:,[1,3]] 实际上所做的是创建一个新数组,其中仅包含原始数组中的第 1 行和第 3 行(使用 x[[1,3]] 部分完成),然后重新切片那 - 创建第三个数组 - 仅包括前一个数组的第 1 列和第 3 列。

【讨论】:

此解决方案不起作用,因为它特定于我试图提取的行/列。想象一下在 50x50 矩阵中相同的情况,当我想提取行/列 5、11、12、32、39、45 时,没有办法用简单的切片来做到这一点。抱歉,如果我的问题不清楚。【参考方案6】:

我在这里也有类似的问题:Writting in sub-ndarray of a ndarray in the most pythonian way. Python 2 。

按照您的案例的上一篇文章的解决方案,解决方案如下所示:

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

使用 ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

这是:

array([[ 5,  7],
       [13, 15]])

【讨论】:

【参考方案7】:

我不确定这有多有效,但您可以使用 range() 在两个轴上切片

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 

【讨论】:

以上是关于NumPy 2d 数组的切片,或者如何从 nxn 数组 (n>m) 中提取 mxm 子矩阵?的主要内容,如果未能解决你的问题,请参考以下文章

Python NumPy - 3D 数组的角度切片

基于 2D 数组的 3D numpy 切片的平均值

如何在 NumPy 中将 HDF5 2D 数组转换为 1D?

NumPy:在 3D 切片中使用来自 argmin 的 2D 索引数组

在numpy中获取3D数组的2D切片的平均值

基于唯一值对 2D Numpy/CuPy 数组进行更快的迭代