Manipulating numpy arrays (concatenating inner sub-arrays)

I have a question of manipulating numpy arrays. Say, given a 3-d array in the form np.array([[[1,2],[3,4]], [[5,6],[7,8]]]) which is a (2,2,2) array. I want to manipulate it into a (2,4) array such that a = np.array([[1,2,5,6],[3,4,7,8]]). I want to know is there any built-in methods of numpy particularly dealing with problems like this and can be easily generalized.

EDITED: Thank you all guys’ answers. They all rock! I thought I should clarify what I mean by “easily generalized” in the original post. Suppose given a (6,3,2,3) array (this is the actual challenge I am facing)

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

I want to massage it into a (3,3,2,2,3) array such that fora[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]]).

In short, the last 3 biggest blocks should “merge” with first 3 biggest blocks to form 3 (3,2) blocks. The rest of 2 blocks i.e., (a[1,:,:,:,:], a[2,:,:,:,:]) follow the same pattern.

Answer

From your new update, you can do the following using 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]]]]])

Explanation:

Take another example: a small array q and our desired output after changing 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]]])

Here we are using numpy strides to achieve this. Let’s check for q‘s strides:

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

In our output, all strides should remain the same, except the third stride, because in the third dimension we need to stack with the values from bottom half of q, ie: 6 is put next to 0, 7 next to 1 and so on…

So, how “far” is it from 0 to 6 ? Or in another word, how far is it from q[0,0,0] to 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

Okay then new_strides = (12, 4, 24) and hence we got:

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

Back to your question:

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)