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

时间:2010-11-23 15:05:08

标签: python numpy slice

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

7 个答案:

答案 0 :(得分:107)

要回答这个问题,我们必须看看如何在Numpy中对多维数组进行索引编制。我们首先要说你的问题是数组x。分配给x的缓冲区将包含16个从0到15的升序整数。如果访问一个元素,比如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只会等于x。)新的数组对象将具有与x不同的形状,并且可能是缓冲区中的不同起始偏移量,但将与x共享步幅(在这种情况下)至少):

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

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

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

the NumPy documentation on indexing详细介绍了这一点。

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

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

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

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

答案 1 :(得分:54)

正如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])]执行与第一个示例相同的操作。这可以使您不必输入所有这些额外的括号。

答案 2 :(得分:11)

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

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

我不是切片方面的专家,但通常情况下,如果您尝试切片到数组并且值是连续的,则会返回一个视图,其中步幅值会发生变化。

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

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

答案 3 :(得分: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]之类的内容,其中rowscolumns是序列。这当然会为您提供原始数组的副本,而不是视图。这是人们应该期待的,因为numpy数组使用连续的内存(使用常量strides),并且无法生成具有任意行和列的视图(因为这将需要非常量的步幅)。

答案 4 :(得分: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列。

答案 5 :(得分:3)

我在这里有一个类似的问题: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]])

答案 6 :(得分:0)

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

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