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

我想分割一个NumPy nxn数组。 我想提取该数组的m行和列的任意select(即在行数/列数中没有任何模式),使其成为一个新的mxm数组。 对于这个例子,让我们说数组是4×4,我想从中提取一个2×2数组。

这是我们的arrays:

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]] 

要删除的行和列是相同的。 最简单的情况是当我想提取一个在开始或结束的2×2子matrix,即:

 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]]) 

但是如果我需要删除另一个行/列的混合呢? 如果我需要删除第一行和第三行,从而提取子matrix[[5,7],[13,15]] ? 可以有任何行/行的组合。 我读了一些地方,我只需要索引我的数组使用数组/行列索引列表,但这似乎并没有工作:

 In [35]: x[[1,3],[1,3]] Out[35]: array([ 5, 15]) 

我find了一个办法,那就是:

  In [61]: x[[1,3]][:,[1,3]] Out[61]: array([[ 5, 7], [13, 15]]) 

第一个问题是,这是不可读的,虽然我可以忍受。 如果有人有更好的解决scheme,我一定会喜欢听的。

其他的事情是我读了一个论坛索引arrays数组强制NumPy复制所需的数组,因此,当处理大型数组这可能成为一个问题。 为什么这个/这个机制是如何工作的?

要回答这个问题,我们必须看看如何索引一个multidimensional array在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不会创build一个新的缓冲区,但是它创build了一个引用相同缓冲区的新的数组对象(否则y就等于x )。新的数组对象将有不同的形状,然后x和也许是一个不同的起始偏移到缓冲区,但将与x (在这种情况下,至less)共享步伐:

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

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

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

这在编制索引的NumPy文档中有深入的介绍 。

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

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

这是因为索引数组被广播到一个普通的形状。 当然,对于这个特定的例子,你也可以做基本的切片:

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

正如Sven提到的, x[[[0],[2]],[1,3]]将返回与1和3列匹配的0和2行,而x[[0,2],[1,3]]将返回数组中的值x [0,1]和x [2,3]。

对于我给出的第一个例子, numpy.ix_有一个有用的function。 你可以用x[numpy.ix_([0,2],[1,3])]来做同样的事情。 这可以使您不必input所有这些额外的括号。

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

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

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

例如在input33和34中,尽pipe你得到了一个2×2的数组,但是步幅是4.因此,当你索引下一行时,指针移动到内存中的正确位置。

显然,这种机制并不适用于一系列指数的情况。 因此,numpy将不得不复制。 毕竟,许多其他的matrixmath函数依赖于大小,步幅和连续的内存分配。

如果你想跳过每一行和其他列,那么你可以用基本的切片来完成:

 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]]) 

如果你想select任意的行和列,那么你不能使用基本的切片。 您必须使用高级索引,使用像x[rows,:][:,columns] ,其中rowscolumns是序列。 这当然会给你一个原始数组的副本,而不是视图。 这是人们应该期待的,因为numpy数组使用连续内存(具有常量步长),并且将无法生成具有任意行和列的视图(因为这将需要非常量步长)。

有了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]]实际上做的是创build一个新的数组,包括原始数组中的第1行和第3行x[[1,3]]部分),然后重新切片 – 创build第三个数组 – 仅包括前一个数组的第1列和第3列。