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

我想切割一个 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]])

第一个问题是它几乎不可读,尽管我可以接受这一点。如果有人有更好的解决办法,我当然想听听。

另外,我读到 在论坛上说,用数组索引数组会迫使 NumPy 复制所需的数组,因此在处理大型数组时,这可能会成为一个问题。为什么会这样/这个机制是如何工作的?

242092 次浏览

使用 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行。

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

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

我不是切片专家,但通常情况下,如果你试图切片到一个数组和值是连续的,你会得到一个视图,其中的步长值发生了变化。

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

显然,这种机制不能很好地应用于索引数组的情况。因此,numpy 必须进行复制。毕竟,许多其他矩阵数学函数依赖于大小、步长和连续的内存分配。

要回答这个问题,我们必须看看索引多维数组在 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 通过引入 大步流星解决了这个问题。在计算访问 x[i,j]的内存偏移量时,实际计算的是 i*x.strides[0]+j*x.strides[1](这已经包括 int 大小的因子) :

x.strides
(16, 4)

当像上面那样提取 y时,NumPy 不会创建一个新的缓冲区,但是它会创建一个引用相同缓冲区的新数组对象(否则 y就等于 x)新的数组对象将有一个不同的形状然后 x和可能不同的起始偏移到缓冲区,但将共享的步长与 x(在这种情况下至少) :

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

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

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

这是在 关于索引的 NumPy 文档深度覆盖。

哦,差点忘了你的实际问题: 下面是如何使多列表索引按预期工作:

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

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

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

如果希望跳过其他每一行和每一列,那么可以使用基本的切片:

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是序列。这当然会给你一个副本,而不是一个视图,你的原始数组。这是人们应该预料到的,因为一个数字数组使用连续内存(具有不变的步长) ,而且没有办法生成具有任意行和列的视图(因为这将需要非常变的步长)。

正如 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])]做与我的第一个示例相同的事情。这可以使您避免输入所有那些额外的括号。

我在这里有一个类似的问题: 用最蟒蛇的方式写作。 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]])

我不确定这样做的效率如何,但是您可以使用 range ()在两个轴上切片

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