操作numpy数组(连接内部子数组)

3

我有一个操作numpy数组的问题。给定一个三维数组np.array([[[1,2],[3,4]], [[5,6],[7,8]]]),它是一个(2,2,2)的数组。我想将它操作成一个(2,4)的数组,使得a = np.array([[1,2,5,6],[3,4,7,8]])。我想知道是否有任何numpy内置方法特别处理这类问题,并且可以轻松推广。

编辑:感谢大家的回答。它们都很好!我认为我应该澄清一下原来帖子中的“易于推广”的含义。假设现在有一个(6,3,2,3)的数组(这是我实际面临的挑战)。

a = array([[[[ 10,  20,  30],
         [ 40,  40,  20]],

        [[ 22,  44,  66],
         [ 88,  88,  44]],

        [[ 33,  66,  99],
         [132, 132,  66]]],


       [[[ 22,  44,  66],
         [ 88,  88,  44]],

        [[ 54, 108, 162],
         [216, 216, 108]],

        [[ 23,  46,  69],
         [ 92,  92,  46]]],


       [[[ 14,  28,  42],
         [ 56,  56,  28]],

        [[ 25,  50,  75],
         [100, 100,  50]],

        [[ 33,  66,  99],
         [132, 132,  66]]],


       [[[ 20,  40,  60],
         [ 80,  80,  40]],

        [[ 44,  88, 132],
         [176, 176,  88]],

        [[ 66, 132, 198],
         [264, 264, 132]]],


       [[[ 44,  88, 132],
         [176, 176,  88]],

        [[108, 216, 324],
         [432, 432, 216]],

        [[ 46,  92, 138],
         [184, 184,  92]]],


       [[[ 28,  56,  84],
         [112, 112,  56]],

        [[ 50, 100, 150],
         [200, 200, 100]],

        [[ 66, 132, 198],
         [264, 264, 132]]]])

我希望将其转换为一个(3,3,2,2,3)的数组,并对a[0,:,:,:,:]进行操作。

a[0,0,0,:,:] = np.array([[10,20,30],[40,40,20]]);
a[0,1,0,:,:] = np.array([[22,44,66],[88,88,44]]);
a[0,2,0,:,:] = np.array([[33,66,99],[132,132,66]]);
a[0,0,1,:,:] = np.array([[20,40,60],[80,80,40]]);
a[0,1,1,:,:] = np.array([[44,88,132],[176,176,88]]);
a[0,2,1,:,:] = np.array([[66,132,198],[264,264,132]]).

简言之,最后3个最大块应该与前3个最大块合并,形成3个(3,2)的块。其余2个块即 (a[1,:,:,:,:], a[2,:,:,:,:]) 遵循相同的模式。

哦,不用管了,你正在搞混那些值的顺序。嗯,这有点微妙。也许你需要先对数组的某些部分进行转置?但是你的转置肯定不是一个标准的,所以我不确定该怎么完成它。 - joanis
找到了一个使用 zip 的解决方案,但我更喜欢 Albin Paul 使用 swapaxes 的答案。 - joanis
看看 a。形状为 (2,2,2),有三种不同的方法可以组合值来创建一个 (2,4) 的数组。reshape 连接最后两个维度。reshape(4,2) 连接前两个。你想要连接第一个和最后一个,这需要重新排序元素。重新阅读 reshape 文档,并注意 "您可以将重塑视为首先展平数组..." 这一行。 - hpaulj
“易于泛化”:以哪些方式?在我的回答中,我提供了这些问题的权威解释链接。 - Pierre D
我猜你陷入了一个XY问题 - AcaNg
显示剩余5条评论
6个回答

4
你的主题行已经回答了你的问题:
In [813]: a
Out[813]: 
array([[[1, 2],
        [3, 4]],

       [[5, 6],
        [7, 8]]])
In [818]: np.concatenate(a, axis=1)    # aka np.hstack
Out[818]: 
array([[1, 2, 5, 6],
       [3, 4, 7, 8]])

这将数组视为2个(2,2)的子数组。

另一个连接选项:

In [819]: np.concatenate(a, axis=0)
Out[819]: 
array([[1, 2],
       [3, 4],
       [5, 6],
       [7, 8]])

我认为转置后跟重塑更好,而且更容易泛化。但这需要一些关于数组存储、维度含义及其转置方式的知识。
普通的reshape无法工作的原因是您想要重新排列数组的元素。
如文档所述,reshape实际上将数组展平,然后应用新形状:
In [823]: a.ravel()
Out[823]: array([1, 2, 3, 4, 5, 6, 7, 8])

但是你的新数组顺序不同:

In [824]: np.concatenate(a, axis=1).ravel()
Out[824]: array([1, 2, 5, 6, 3, 4, 7, 8])

2
首先交换轴,使用np.swapaxes,然后进行重塑以得到输出。
import numpy as np
a = np.array([[[1,2],[3,4]], [[5,6],[7,8]]])
a = np.swapaxes(a, 0, 1)
a = np.reshape(a, (2, 4))
print(a)

输出
[[1 2 5 6]
 [3 4 7 8]]

您还可以使用np.transpose,例如np.transpose(a, (1, 0, 2))来交换轴从(0, 1, 2)(1, 0, 2),正如MadPhysicist所指出的那样。

你也可以使用.transpose(1, 0, 2) - Mad Physicist
@MadPhysicist 我已经更新了答案并提出了建议。 - Albin Paul

1
我认为在这种情况下(第一个例子),简单地说:
>>> a.swapaxes(0, 1).reshape(2, -1)
array([[1, 2, 5, 6],
       [3, 4, 7, 8]])

一般而言,我认为@Divakar mini tutorial是这类操作的权威来源。

编辑 在问题被更新后(包含一个更大的数组示例),我编写了一个小型求解器(实际上非常快)来解决这类问题。

以下任何一种都会产生符合约束条件的相同结果:

np.moveaxis(a.reshape(2, 3, 3, 2, 3), 0, 2)
np.rollaxis(a.reshape(2, 3, 3, 2, 3), 0, 3)
a.reshape(2, 3, 3, 2, 3).transpose(1, 2, 0, 3, 4)
a.reshape(2, 9, 6).swapaxes(0, 1).reshape(3, 3, 2, 2, 3)
np.rollaxis(a.reshape(2, 9, 6), 1).reshape(3, 3, 2, 2, 3)
a.reshape(2, 9, 2, 3).swapaxes(0, 1).reshape(3, 3, 2, 2, 3)
a.reshape(2, 9, 3, 2).swapaxes(0, 1).reshape(3, 3, 2, 2, 3)
a.reshape(2, 9, 6).transpose(1, 0, 2).reshape(3, 3, 2, 2, 3)
# ...

当然,您可以决定将.reshape()中的任何单个值更改为-1,以使其更加通用或直观。例如:
np.rollaxis(a.reshape(2, 3, 3, 2, -1), 0, 3)

0

numpy.reshape 结合 zip 可以实现你想要的功能,但有点棘手:

>>> a = np.array([[[1,2],[3,4]], [[5,6],[7,8]]])
>>> b = np.array(list(zip(a[0], a[1])))
>>> np.reshape(b, (2,4))
array([[1, 2, 5, 6],
       [3, 4, 7, 8]])

挑战在于你正在转置a的第一和第二维度,这不是np.transpose的作用。然而,zip可以有效地实现这一点。

0
你可以使用hstack和vstack。
    a= np.array([[[1,2],[3,4]], [[5,6],[7,8]]])
    s0, s1 = range(a.shape[0])
    
    x= np.vstack(np.hstack((a[s0], a[s1])))
    print(x)

输出

[[1 2 5 6]
 [3 4 7 8]]

你只需要使用np.hstack(a)。它将数组视为一个包含2个(2,2)数组的列表。 - hpaulj

0

从您的新更新中,您可以使用 np.lib.stride_tricks.as_strided 执行以下操作:

>>> np.lib.stride_tricks.as_strided(a, shape=(3,3,2,2,3), strides=(72,24,216,12,4))
array([[[[[ 10,  20,  30],
          [ 40,  40,  20]],

         [[ 20,  40,  60],
          [ 80,  80,  40]]],


        [[[ 22,  44,  66],
          [ 88,  88,  44]],

         [[ 44,  88, 132],
          [176, 176,  88]]],


        [[[ 33,  66,  99],
          [132, 132,  66]],

         [[ 66, 132, 198],
          [264, 264, 132]]]],



       [[[[ 22,  44,  66],
          [ 88,  88,  44]],

         [[ 44,  88, 132],
          [176, 176,  88]]],


        [[[ 54, 108, 162],
          [216, 216, 108]],

         [[108, 216, 324],
          [432, 432, 216]]],


        [[[ 23,  46,  69],
          [ 92,  92,  46]],

         [[ 46,  92, 138],
          [184, 184,  92]]]],



       [[[[ 14,  28,  42],
          [ 56,  56,  28]],

         [[ 28,  56,  84],
          [112, 112,  56]]],


        [[[ 25,  50,  75],
          [100, 100,  50]],

         [[ 50, 100, 150],
          [200, 200, 100]]],


        [[[ 33,  66,  99],
          [132, 132,  66]],

         [[ 66, 132, 198],
          [264, 264, 132]]]]])

解释:

再举一个例子:一个小数组q和我们想要改变q后的输出:

>>> q = np.arange(12).reshape(4,3,-1)
>>> q
array([[[ 0],
        [ 1],
        [ 2]],

       [[ 3],
        [ 4],
        [ 5]],

       [[ 6],
        [ 7],
        [ 8]],

       [[ 9],
        [10],
        [11]]])
# desired output:
# shape = (2, 3, 2)
array([[[ 0,  6],
        [ 1,  7],
        [ 2,  8]],

       [[ 3,  9],
        [ 4, 10],
        [ 5, 11]]])

在这里,我们使用numpy strides来实现这一点。让我们检查一下q的步幅:

>>> q.strides
(12, 4, 4)

在我们的输出中,所有步幅都应保持不变,除了第三个步幅,因为在第三个维度中,我们需要将底半部分的q值与上半部分堆叠,即:6放在0旁边,7放在1旁边,依此类推...

那么,从06有多远?或者换句话说,从q[0,0,0]q[2,0,0]有多远?

# obviously, distance = [2,0,0] - [0,0,0] = [2,0,0]
bytedistance = np.sum(np.array([2,0,0])*q.strides)
# 2*12 + 0*4 + 0*4 = 24 bytes

好的,那么new_strides = (12, 4, 24),因此我们得到:
>>> np.lib.stride_tricks.as_strided(q, shape=(2,3,2), strides=new_strides)
array([[[ 0,  6],
        [ 1,  7],
        [ 2,  8]],

       [[ 3,  9],
        [ 4, 10],
        [ 5, 11]]])

返回您的问题:
a.strides = (72,24,12,4)
new_strides = (72,24,216,12,4)     # why is 216 here ? it's a homework :)
new_a = np.lib.stride_tricks.as_strided(a, shape=(3,3,2,2,3), strides=new_strides)

你的答案很棒。我会把它作为在我的情况下最具有普适性的解决方案。 - CoolGas
@CoolGas 我建议你阅读上面链接中的文档:如果使用不当,as_strided被认为是危险的。你需要仔细计算步幅,如果不这样做,你的程序可能会崩溃。 - AcaNg
是的,完全同意。Numpy页面确实强调了只有在极端情况下才应该使用这种方法。感谢您指出。 - CoolGas

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