首页 文章

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

提问于
浏览
155

我想切片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 回答

  • 10

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

  • 54

    要回答这个问题,我们必须看看如何在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 ,则strides机制将不允许正确索引 . 理论上,NumPy可以添加一些比步幅更复杂的机制,但是这会使元素访问相对昂贵,从某种程度上违背了数组的整个想法 . 此外,视图不再是一个非常轻量级的对象 .

    这在the NumPy documentation on indexing中有详细介绍 .

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

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

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

    x[1::2, 1::2]
    
  • 107

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

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

    我不是切片方面的专家,但通常情况下,如果您尝试切片到数组并且值是连续的,那么您将返回一个视图,其中步幅值已更改 .

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

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

  • 5

    如果要跳过每隔一行和每隔一列,那么可以使用基本切片来执行此操作:

    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数组使用连续的内存(具有常量步幅),并且无法生成具有任意行和列的视图(因为这将需要非常量的步幅) .

  • 0

    使用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列 .

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

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

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

相关问题