Heberto Mayorquin
Heberto Mayorquin

Reputation: 11071

numpy reshaping multdimensional array through arbitrary axis

so this is a question regarding the use of reshape and how this functions uses each axis on a multidimensional scale.

Suppose I have the following array that contains matrices indexed by the first index. What I want to achieve is to instead index the columns of each matrix with the first index. In order to illustrate this problem, consider the following example where the given numpy array that indexes matrices with its first index is z.

x = np.arange(9).reshape((3, 3))
y = np.arange(9, 18).reshape((3, 3))
z = np.dstack((x, y)).T

Where z looks like:

array([[[ 0,  3,  6],
    [ 1,  4,  7],
    [ 2,  5,  8]],

   [[ 9, 12, 15],
    [10, 13, 16],
    [11, 14, 17]]])

And its shape is (2, 3, 3). Here, the first index are the two images and the three x three is a matrix.

The question more specifically phrased then, is how to use reshape to obtain the following desired output:

array([[ 0,  1,  2],
   [ 3,  4,  5],
   [ 6,  7,  8],
   [ 9, 10, 11],
   [12, 13, 14],
   [15, 16, 17]])

Whose shape is (6, 3). This achieves that the dimension of the array indexes the columns of the matrix x and y as presented above. My natural inclination was to use reshape directly on z in the following way:

out = z.reshape(2 * 3, 3)

But its output is the following which indexes the rows of the matrices and not the columns:

array([[ 0,  3,  6],
   [ 1,  4,  7],
   [ 2,  5,  8],
   [ 9, 12, 15],
   [10, 13, 16],
   [11, 14, 17]]

Could reshape be used to obtain the desired output above? Or more general, can you control how each axis is used when you use the reshape function?

Two things:

Upvotes: 11

Views: 7957

Answers (2)

hpaulj
hpaulj

Reputation: 231395

It you look at dstack code you'll discover that

np.dstack((x, y)).T

is effectively:

np.concatenate([i[:,:,None] for i in (x,y)],axis=2).transpose([2,1,0])

It reshapes each component array and then joins them along this new axis. Finally it transposes axes.

Your target is the same as (row stack)

np.concatenate((x,y),axis=0)

So with a bit of reverse engineering we can create it from z with

np.concatenate([i[...,0] for i in np.split(z.T,2,axis=2)],axis=0)
np.concatenate([i.T[:,:,0] for i in np.split(z,2,axis=0)],axis=0)

or

np.concatenate(np.split(z.T,2,axis=2),axis=0)[...,0]

or with a partial transpose we can keep the split-and-rejoin axis first, and just use concatenate:

np.concatenate(z.transpose(0,2,1),axis=0)

or its reshape equivalent

(z.transpose(0,2,1).reshape(-1,3))

Upvotes: 1

unutbu
unutbu

Reputation: 879591

Every array has a natural (1D flattened) order to its elements. When you reshape an array, it is as though it were flattened first (thus obtaining the natural order), and then reshaped:

In [54]: z.ravel()
Out[54]: 
array([ 0,  3,  6,  1,  4,  7,  2,  5,  8,  9, 12, 15, 10, 13, 16, 11, 14,
       17])

In [55]: z.ravel().reshape(2*3, 3)
Out[55]: 
array([[ 0,  3,  6],
       [ 1,  4,  7],
       [ 2,  5,  8],
       [ 9, 12, 15],
       [10, 13, 16],
       [11, 14, 17]])

Notice that in the "natural order", 0 and 1 are far apart. However you reshape it, 0 and 1 will not be next to each other along the last axis, which is what you want in the desired array:

desired = np.array([[ 0,  1,  2],
                    [ 3,  4,  5],
                    [ 6,  7,  8],
                    [ 9, 10, 11],
                    [12, 13, 14],
                    [15, 16, 17]])

This requires some reordering, which in this case can be done by swapaxes:

In [53]: z.swapaxes(1,2).reshape(2*3, 3)
Out[53]: 
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11],
       [12, 13, 14],
       [15, 16, 17]])

because swapaxes(1,2) places the values in the desired order

In [56]: z.swapaxes(1,2).ravel()
Out[56]: 
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17])

In [57]: desired.ravel()
Out[57]: 
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17])

Note that the reshape method also has a order parameter which can be used to control the (C- or F-) order with which the elements are read from the array and placed in the reshaped array. However, I don't think this helps in your case.


Another way to think about the limits of reshape is to say that all reshapes followed by ravel are the same:

In [71]: z.reshape(3,3,2).ravel()
Out[71]: 
array([ 0,  3,  6,  1,  4,  7,  2,  5,  8,  9, 12, 15, 10, 13, 16, 11, 14,
       17])

In [72]: z.reshape(3,2,3).ravel()
Out[72]: 
array([ 0,  3,  6,  1,  4,  7,  2,  5,  8,  9, 12, 15, 10, 13, 16, 11, 14,
       17])

In [73]: z.reshape(3*2,3).ravel()
Out[73]: 
array([ 0,  3,  6,  1,  4,  7,  2,  5,  8,  9, 12, 15, 10, 13, 16, 11, 14,
       17])

In [74]: z.reshape(3*3,2).ravel()
Out[74]: 
array([ 0,  3,  6,  1,  4,  7,  2,  5,  8,  9, 12, 15, 10, 13, 16, 11, 14,
       17])

So if the ravel of the desired array is different, there is no way to obtain it only be reshaping.


The same goes for reshaping with order='F', provided you also ravel with order='F':

In [109]: z.reshape(2,3,3, order='F').ravel(order='F')
Out[109]: 
array([ 0,  9,  1, 10,  2, 11,  3, 12,  4, 13,  5, 14,  6, 15,  7, 16,  8,
       17])

In [110]: z.reshape(2*3*3, order='F').ravel(order='F')
Out[110]: 
array([ 0,  9,  1, 10,  2, 11,  3, 12,  4, 13,  5, 14,  6, 15,  7, 16,  8,
       17])

In [111]: z.reshape(2*3,3, order='F').ravel(order='F')
Out[111]: 
array([ 0,  9,  1, 10,  2, 11,  3, 12,  4, 13,  5, 14,  6, 15,  7, 16,  8,
       17])

It is possible to obtain the desired array using two reshapes:

In [83]: z.reshape(2, 3*3, order='F').reshape(2*3, 3)
Out[83]: 
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11],
       [12, 13, 14],
       [15, 16, 17]])

but I stumbled upon this serendipidously.


If I've totally misunderstood your question and x and y are the givens (not z) then you could obtain the desired array using row_stack instead of dstack:

In [88]: z = np.row_stack([x, y])

In [89]: z
Out[89]: 
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11],
       [12, 13, 14],
       [15, 16, 17]])

Upvotes: 15

Related Questions