In [25]: m = np.arange(6, dtype=np.uint8).reshape(2, 3)
As you note the strides get reversed in the transpose:
In [26]: m.strides, m.T.strides
Out[26]: ((3, 1), (1, 3))
In [27]: m
Out[27]:
array([[0, 1, 2],
[3, 4, 5]], dtype=uint8)
In [28]: m.T
Out[28]:
array([[0, 3],
[1, 4],
[2, 5]], dtype=uint8)
We can picture that from the display. For m
, to go down rows we step by 3. For m.T
to go across columns we step by 3.
expand_dims
uses reshape
:
In [31]: m1=m.reshape(1,2,3);m1.strides,m1
Out[31]:
((6, 3, 1),
array([[[0, 1, 2],
[3, 4, 5]]], dtype=uint8))
The meaning of the 6
becomes more obvious when we do a repeat:
In [32]: m1.repeat(2,0) # (6,3,1) strides
Out[32]:
array([[[0, 1, 2],
[3, 4, 5]],
[[0, 1, 2],
[3, 4, 5]]], dtype=uint8)
To go down one plane or block, we step by 6. (m1
is a view
, but the repeat is a copy, with its own data.)
Adding a dimension in the middle, we just have to "repeat" the 3
:
In [36]: m2=m.reshape(2,1,3).repeat(2,axis=1);m2.strides, m2
Out[36]:
((6, 3, 1),
array([[[0, 1, 2],
[0, 1, 2]],
[[3, 4, 5],
[3, 4, 5]]], dtype=uint8))
Doing the same to the transpose doesn't add any complications.
Your x
case, adding a leading dimension to the transpose (with repeat for clarity):
In [38]: m.T.reshape(1,3,2).repeat(2,axis=0)
Out[38]:
array([[[0, 3],
[1, 4],
[2, 5]],
[[0, 3],
[1, 4],
[2, 5]]], dtype=uint8)
The new first dimension steps by a block of 6.
While the contiguity flags make some sense when dealing with 2d array, and sometimes are relevant when passing arrays to specialized compiled code, they aren't as meaningful when looking a 3d+ arrays. It's easy to make arrays, especially views, that aren't contiguous in either sense.
In recent Indexing on ndarrays result in wrong shape, I explore what happens to strides when we apply advanced indexing to columns.
edit
Your case where you get a 6:
In [61]: np.expand_dims(m.T,1)
Out[61]:
array([[[0, 3]],
[[1, 4]],
[[2, 5]]], dtype=uint8)
In [62]: _.strides # 1st dim steps by 1, last by 3
Out[62]: (1, 6, 3)
It's harder to visualize the blocks of 6 for the middle dimension, but it's doing the same thing as with the non-transpose. The strides of the repeat don't help, because it's a copy:
In [63]: np.expand_dims(m.T,1).repeat(2,1)
Out[63]:
array([[[0, 3],
[0, 3]],
[[1, 4],
[1, 4]],
[[2, 5],
[2, 5]]], dtype=uint8)
In [64]: _.strides
Out[64]: (4, 2, 1)
We could get the same [61] by first expanding, and then transposing:
In [66]: np.expand_dims(m,0).transpose(2,0,1)
Out[66]:
array([[[0, 3]],
[[1, 4]],
[[2, 5]]], dtype=uint8)
In [67]: _.shape,_.strides
Out[67]: ((3, 1, 2), (1, 6, 3))