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


173

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

Answers:


62

如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])]。这样可以避免您必须输入所有这些多余的括号。


111

为了回答这个问题,我们必须研究如何在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]

应该有可能对数组进行子类化,以便使一个“ slcie-view”对象可以将索引重新映射到原始数组。这可能可以满足OP的需求
jsbueno

@jsbueno:适用于Python代码,但不适用于Scipy / Numpy环绕的C / Fortran例程。这些包裹的例程是Numpy的力量所在。
Dat Chu 2010年

那么.. x [[[[1],[3]],[1,3]]和x [[1,3],:] [:,[1,3]]有什么区别?我的意思是说有没有比其他更好用的变体了?
levesque 2010年

1
@JC:x[[[1],[3]],[1,3]]仅创建一个新数组,而x[[1,3],:][:,[1,3]]复制两次,因此请使用第一个数组。
Sven Marnach 2010年

@JC:或使用贾斯汀的答案中的方法。
Sven Marnach 2010年

13

我认为这x[[1,3]][:,[1,3]]很难理解。如果您想更加清楚自己的意图,可以执行以下操作:

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

我不是切片专家,但是通常情况下,如果尝试切片为数组并且值是连续的,则会返回一个视图,其中步幅值已更改。

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

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


10

如果要跳过每隔一行和每隔一列,则可以使用基本切片:

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],where rowscolumnsare sequence的方法。当然,这将为您提供原始阵列的副本,而不是视图。正如人们所期望的那样,因为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 2010年


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.