We could generate all the shuffled indices along the first axis and then simply use advanced-indexing
to get the randomized version. Now, to get those all shuffled indices, we could generate a random array of the same shape as the input array and get the argsort indices along the first axis. This has been explored before, as here
.
Hence, we would have a vectorized implementation like so -
m,n,r,p = a.shape # a is the input array
idx = np.random.rand(*a.shape).argsort(0)
out = a[idx, np.arange(n)[:,None,None], np.arange(r)[:,None], np.arange(p)]
Just to explain to the readers on what exactly is the problem, here's a sample run -
1) Input 4D array :
In [711]: a
Out[711]:
array([[[[60, 22, 34],
[29, 18, 79]],
[[11, 69, 41],
[75, 30, 30]]],
[[[63, 61, 42],
[70, 56, 57]],
[[70, 98, 71],
[29, 93, 96]]]])
2) Random indices generated with proposed method for indexing along the first axis :
In [712]: idx
Out[712]:
array([[[[1, 0, 1],
[0, 1, 1]],
[[0, 0, 1],
[1, 0, 1]]],
[[[0, 1, 0],
[1, 0, 0]],
[[1, 1, 0],
[0, 1, 0]]]])
3) Finally index into input array for shuffled output :
In [713]: out
Out[713]:
array([[[[63, 22, 42],
[29, 56, 57]],
[[11, 69, 71],
[29, 30, 96]]],
[[[60, 61, 34],
[70, 18, 79]],
[[70, 98, 41],
[75, 93, 30]]]])
Looking closely, we will see that 63
at a[0,0,0,0]
and 60
at a[1,0,0,0]
are swapped on account of the idx
values being 1
and 0
respectively at those corresponding places in idx
. Next up, 22
and 61
stay at their places, since idx
values are 0
and 1
and so on.
Runtime test
In [726]: timeseries = np.random.rand(10,10,10,10)
In [727]: %timeit org_app(timeseries)
100 loops, best of 3: 5.24 ms per loop
In [728]: %timeit proposed_app(timeseries)
1000 loops, best of 3: 289 µs per loop
In [729]: timeseries = np.random.rand(50,50,50,50)
In [730]: %timeit org_app(timeseries)
1 loop, best of 3: 720 ms per loop
In [731]: %timeit proposed_app(timeseries)
1 loop, best of 3: 426 ms per loop
At large sizes, the cost of creating random array is proving to be the bottleneck with the proposed method, but still shows a good speedup over the original loopy version.