65

I would like to do the following:

for i in dimension1:
  for j in dimension2:
    for k in dimension3:
      for l in dimension4:
        B[k,l,i,j] = A[i,j,k,l]

without the use of loops. In the end both A and B contain the same information but indexed differently.

I must point out that the dimension 1,2,3 and 4 can be the same or different. So a numpy.reshape() seems difficult.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
sponce
  • 1,279
  • 2
  • 11
  • 17

5 Answers5

127

The canonical way of doing this in numpy would be to use np.transpose's optional permutation argument. In your case, to go from ijkl to klij, the permutation is (2, 3, 0, 1), e.g.:

In [16]: a = np.empty((2, 3, 4, 5))

In [17]: b = np.transpose(a, (2, 3, 0, 1))

In [18]: b.shape
Out[18]: (4, 5, 2, 3)
Jaime
  • 65,696
  • 17
  • 124
  • 159
42

Please note: Jaime's answer is better. NumPy provides np.transpose precisely for this purpose.


Or use np.einsum; this is perhaps a perversion of its intended purpose, but the syntax is quite nice:

In [195]: A = np.random.random((2,4,3,5))

In [196]: B = np.einsum('klij->ijkl', A)

In [197]: A.shape
Out[197]: (2, 4, 3, 5)

In [198]: B.shape
Out[198]: (3, 5, 2, 4)

In [199]: import itertools as IT    
In [200]: all(B[k,l,i,j] == A[i,j,k,l] for i,j,k,l in IT.product(*map(range, A.shape)))
Out[200]: True
Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 2
    @DSM - I think you get the "bad pun of the day" award! – Joe Kington May 29 '14 at 21:54
  • 3
    This is the best solution for me as I have in fact 7 dimensions and multiple swap or roll is difficult. I actually though about einsum but somehow used it with the wrong syntax and could not do it. Thanks a lot !!! I love einsum so much <3 – sponce May 29 '14 at 22:01
16

You could rollaxis twice:

>>> A = np.random.random((2,4,3,5))
>>> B = np.rollaxis(np.rollaxis(A, 2), 3, 1)
>>> A.shape
(2, 4, 3, 5)
>>> B.shape
(3, 5, 2, 4)
>>> from itertools import product
>>> all(B[k,l,i,j] == A[i,j,k,l] for i,j,k,l in product(*map(range, A.shape)))
True

or maybe swapaxes twice is easier to follow:

>>> A = np.random.random((2,4,3,5))
>>> C = A.swapaxes(0, 2).swapaxes(1,3)
>>> C.shape
(3, 5, 2, 4)
>>> all(C[k,l,i,j] == A[i,j,k,l] for i,j,k,l in product(*map(range, A.shape)))
True
DSM
  • 342,061
  • 65
  • 592
  • 494
  • 1
    +1 On a side note, `product(*map(range, A.shape))` can be written a bit more cleanly as `np.ndindex(*A.shape)`. – Joe Kington May 29 '14 at 21:53
5

One can also leverage numpy.moveaxis() for moving the required axes to desired locations. Here is an illustration, stealing the example from Jaime's answer:

In [160]: a = np.empty((2, 3, 4, 5))

# move the axes that are originally at positions [0, 1] to [2, 3]
In [161]: np.moveaxis(a, [0, 1], [2, 3]).shape 
Out[161]: (4, 5, 2, 3)
kmario23
  • 57,311
  • 13
  • 161
  • 150
  • can you look at my question here: https://datascience.stackexchange.com/questions/58123/np-shape-1-reversal-of-dims-and-np-moveaxis-splitting-array How is `np.moveaxis()` splitting the array into r, b, g? My instructor told me it's `np.split()` but is that implicitly called with `np.movieaxis`? – mLstudent33 Aug 25 '19 at 08:46
2

I would look at numpy.ndarray.shape and itertools.product:

import numpy, itertools
A = numpy.ones((10,10,10,10))
B = numpy.zeros((10,10,10,10))

for i, j, k, l in itertools.product(*map(xrange, A.shape)):
    B[k,l,i,j] = A[i,j,k,l]

By "without the use of loops" I'm assuming you mean "without the use of nested loops", of course. Unless there's some numpy built-in that does this, I think this is your best bet.

metaperture
  • 2,393
  • 1
  • 18
  • 19
  • 1
    In NumPy, one of the general goals of avoiding loops is to get the C part of the code to do the heavy lifting. That means nested loops or `itertools.product` are both undesirable. – user2357112 May 29 '14 at 21:43
  • Thank you metaperture ! What are you suggesting user2357112 ? Which C routine can do that? – sponce May 29 '14 at 21:51