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]
,其中rows
和columns
是序列。这当然会给您原始数组的副本,而不是视图。正如人们所期望的那样,因为 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 子矩阵?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 NumPy 中将 HDF5 2D 数组转换为 1D?