Using reshape and transpose:
a.reshape(-1, 2).T.reshape(-1, 3, 4)
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])
Timings on your sample array:
%timeit np.rollaxis(a.reshape(3, 4, 2), 2)
2.92 µs ± 10.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit a.reshape(2,4,3, order="F").swapaxes(1, 2)
1.1 µs ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit a.reshape(-1, 2).T.reshape(-1, 3, 4)
1.08 µs ± 7.36 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
I haven't timed this answer on massive arrays because I haven't figured out a way to generalize either of your solutions. One benefit of my solution is that it scales without any altering of the code:
a = np.zeros(48)
a[::2] = np.arange(24)
a[1::2] = np.arange(24, 48)
a.reshape(-1, 2).T.reshape(-1, 3, 4)
array([[[ 0., 1., 2., 3.],
[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.]],
[[12., 13., 14., 15.],
[16., 17., 18., 19.],
[20., 21., 22., 23.]],
[[24., 25., 26., 27.],
[28., 29., 30., 31.],
[32., 33., 34., 35.]],
[[36., 37., 38., 39.],
[40., 41., 42., 43.],
[44., 45., 46., 47.]]])
a[::2]anda[1::2]and then reshape that. I don’t know if it’ll be much shorter. Ultimately, you’re doing two things: un-interleaving and array, and reshaping an array—and any readable solution is going to involve two steps like your existing ones.