从NumPy二维数组中切片,或者如何从一个nxn数组(n>m)中提取一个mxm子矩阵?

194

我想要切分一个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复制所需的数组,因此当处理大型数组时,这可能会成为一个问题。为什么会这样 / 这个机制是如何工作的呢?


类型错误:切片索引必须是整数、无或具有 index 方法。 - CS QGB
7个回答

119
要回答这个问题,我们需要了解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]的内存偏移量将产生正确的结果。

但是对于像z=x[[1,3]]这样的情况,NumPy应该怎么做呢?如果原始缓冲区用于z,则步幅机制将不允许正确索引。理论上,NumPy可以添加比步幅更复杂的机制,但这会使元素访问变得相对昂贵,从而违背数组的整个思想。此外,视图将不再是真正轻量级的对象。

这在NumPy索引文档中有详细介绍。

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

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

这是因为索引数组被广播到一个共同的形状。当然,对于这个特定的例子,您也可以使用基本切片:
x[1::2, 1::2]

应该可以对数组进行子类化,以便创建一个“切片视图”对象,该对象将重新映射索引到原始数组。这可能可以满足OP的需求。 - jsbueno
@jsbueno:这对于Python代码可以起作用,但不适用于Scipy/Numpy封装的C/Fortran例程。这些封装的例程是Numpy强大之处所在。 - Dat Chu
那么,x[[[1],[3]],[1,3]]和x[[1,3],:][:,[1,3]]有什么区别呢?我的意思是,是否有一种变体比另一种更好用? - levesque
1
@JC: x[[[1],[3]],[1,3]] 只创建一个新的数组,而 x[[1,3],:][:,[1,3]] 则会复制两次,因此建议使用第一种方法。 - Sven Marnach
@JC:或者使用Justin答案中的方法。 - Sven Marnach
显示剩余2条评论

68

正如 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])] 来完成与第一个示例相同的操作。这可以避免输入所有额外的方括号。


15

我认为x[[1,3]][:,[1,3]]的可读性很差。如果你想更清楚地表达你的意图,你可以这样做:

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

我不是切片的专家,但通常情况下,如果尝试对数组进行切片并且值是连续的,那么你会得到一个视图,其中步幅值已更改。

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

显然,这种机制无法很好地适用于索引数组的情况。因此,NumPy将不得不进行复制。毕竟,许多其他矩阵数学函数依赖于大小、步幅和连续内存分配。


11

如果你想跳过每隔一行和每隔一列,那么你可以使用基本切片来实现:

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列。


1
这个解决方案不可行,因为它只适用于我尝试提取的行/列。想象一下在一个50x50的矩阵中,当我想要提取第5、11、12、32、39、45行/列时,简单的切片无法实现。如果我的问题表述不够清晰,那我很抱歉。 - levesque

4

2

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

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

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