Advanced NumPy: Master stride tricks with 25 illustrated exercises
By: Raimi Karim
Tags: Towards Data Science, Numpy, Data Science

Answer

strides = (2,)

shape = (25,)

💡 Explanation

Adjacent elements in the output (eg. 1 → 2, 2 → 3, 23 → 24, 24 → 25) were originally 2 bytes apart (=1 element away × 2 bytes) in the input. Shape of this dimension is 25.

Code

>>> x = np.asarray(range(1,26), np.int16).reshape(5,5)

>>> as_strided(x, shape=(25,), strides=(2,)) array([ 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], dtype=int16)

Similar to

>>> x.ravel() array([ 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], dtype=int16)

4) Skip every other element

Input itemsize: 1 byte | Input strides: (5, 1)

Answer

strides = (2,)

shape = (3,)

💡 Explanation

Adjacent elements in the output (i.e. 1 → 3, 3 → 5) were originally 2 bytes apart (=2 elements away × 1 byte) in the input. Shape of this dimension is 3.

Code

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)

>>> as_strided(x, shape=(3,), strides=(2,)) array([1, 3, 5], dtype=int8)

Similar to

>>> x[0,::2] array([1, 3, 5], dtype=int8)

5) Slice first column

Input itemsize: 8 bytes | Input strides: (40, 8)

Answer

strides = (40,)

shape = (4,)

💡 Explanation

Adjacent elements in the output (i.e. 1 → 6, 6 → 11, 11 → 16) were originally 40 bytes apart (=5 elements away × 8 bytes) in the input. Shape of this dimension is 4.

Code

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)

>>> as_strided(x, shape=(4,), strides=(40,)) array([ 1, 6, 11, 16])

Similar to

>>> x[:4,0] array([ 1, 6, 11, 16])

6) Slice a diagonal

Input itemsize: 8 bytes | Input strides: (40, 8)

Answer

strides = (48,)

shape = ( 5,)

💡 Explanation

Adjacent elements in the output (i.e. 1 → 7, 7 → 13, 13 → 19, 19 → 25) were originally 48 bytes apart (=6 elements away × 8 bytes) in the input. Shape of this dimension is 5.

Code

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)

>>> as_strided(x, shape=(5,), strides=(48,)) array([ 1, 7, 13, 19, 25])

Similar to

>>> x.diagonal() array([ 1, 7, 13, 19, 25])

7) Repeat the first element

Input itemsize: 8 bytes | Input strides: (40, 8)

Answer

strides = (0,)

shape = (5,)

💡 Explanation

Adjacent elements in the output (i.e. 1 → 1) were originally 0 bytes apart (=0 elements away × 8 bytes) in the input. Shape of this dimension is 5.

Code

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)

>>> as_strided(x, shape=(5,), strides=(0,)) array([ 1, 1, 1, 1, 1])

Similar to

>>> np.broadcast_to(x[0,0], (5,)) array([1, 1, 1, 1, 1])

2D exercises

8) Simple 2D slicing

Input itemsize: 8 bytes | Input strides: (40, 8)

Answer

strides = (40,8)

shape = ( 3,4)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 2, 2 → 3, 8 → 9) were originally 8 bytes apart (=1 element away × 8 bytes) in the input. Shape of this dimension is 4. Output’s left-to-right dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 6, 2 → 7, 9 → 14) were originally 40 bytes apart (=5 elements away × 8 bytes) in the input. Shape of this dimension is 3.

Code

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)

>>> as_strided(x, shape=(3,4), strides=(40,8)) array([[ 1, 2, 3, 4],

[ 6, 7, 8, 9],

[11, 12, 13, 14]])

Similar to

>>> x[:3,:4] array([[ 1, 2, 3, 4],

[ 6, 7, 8, 9],

[11, 12, 13, 14]])

9) Slice a zigzag

Input itemsize: 8 bytes | Input strides: (40, 8)

Answer

strides = (48,8)

shape = ( 4,2)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 2, 7 → 8, 13 → 14, 19 → 20) were originally 8 bytes apart (=1 element away × 8 bytes) in the input. Shape of this dimension is 2. Output’s left-to-right dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 7, 2 → 8, 13 → 19) were originally 48 bytes apart (=6 elements away × 8 bytes) in the input. Shape of this dimension is 4.

Code

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)

>>> as_strided(x, shape=(4,2), strides=(48,8)) array([[ 1, 2],

[ 7, 8],

[13, 14],

[19, 20]])

Similar to

>>> # this may not be achieved concisely

10) Sparse slicing

Input itemsize: 8 bytes | Input strides: (40, 8)

Answer

strides = (80,16)

shape = ( 3, 3)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 3, 21 → 23, 13 → 15) were originally 16 bytes apart (=2 elements away × 8 bytes) in the input. Shape of this dimension is 3. Output’s left-to-right dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 11, 13 → 23, 15 → 25) were originally 80 bytes apart (=10 elements away × 8 bytes) in the input. Shape of this dimension is 3.

Code

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)

>>> as_strided(x, shape=(3,3), strides=(80,16)) array([[ 1, 3, 5],

[11, 13, 15],

[21, 23, 25]])

Similar to

>>> x[::2,::2] array([[ 1, 3, 5],

[11, 13, 15],

[21, 23, 25]])

11) Transpose a 2D array

Input itemsize: 1 byte | Input strides: (5, 1)

Answer

strides = (1,5)

shape = (3,3)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 6, 7 → 12, 3 → 8) were originally 5 bytes apart (=5 elements away × 1 byte) in the input. Shape of this dimension is 3. Output’s top-to-right dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 2, 2 → 3, 11 → 12) were originally 1 byte apart (=1 element away × 1 byte) in the input. Shape of this dimension is 3.

Code

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)

>>> as_strided(x, shape=(3,3), strides=(1,5)) array([[ 1, 6, 11],

[ 2, 7, 12],

[ 3, 8, 13]], dtype=int8)

Similar to

>>> x[:3,:3].T array([[ 1, 6, 11],

[ 2, 7, 12],

[ 3, 8, 13]], dtype=int8)

12) Repeat the first column 4 times

Input itemsize: 4 bytes | Input strides: (20, 4)

Answer

strides = (20,0)

shape = ( 5,4)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 1, 6 → 6, 16 → 16) were originally 0 bytes apart (=0 elements away × 4 bytes) in the input. Shape of this dimension is 4. Output’s top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 6, 6 → 11, 11 → 16) were originally 20 bytes apart (=5 elements away × 4 bytes) in the input. Shape of this dimension is 5.

Code

>>> x = np.asarray(range(1,26), np.int32).reshape(5,5)

>>> as_strided(x, shape=(5,4), strides=(20,0)) array([[ 1, 1, 1, 1],

[ 6, 6, 6, 6],

[11, 11, 11, 11],

[16, 16, 16, 16],

[21, 21, 21, 21]], dtype=int32)

Similar to

>>> np.broadcast_to(x[:,0,None], (5,4)) array([[ 1, 1, 1, 1],

[ 6, 6, 6, 6],

[11, 11, 11, 11],

[16, 16, 16, 16],

[21, 21, 21, 21]], dtype=int32)

13) Reshape 1D array to 2D array

Input itemsize: 8 bytes | Input strides: (8,)

Answer

strides = (24,8)

shape = ( 4,3)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 2, 2 → 3, 7 → 8, 11 → 12) were originally 8 bytes apart (=1 element away × 8 bytes) in the input. Shape of this dimension is 3. Output’s top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 4, 4 → 7, 7 → 10) were originally 24 bytes apart (=3 elements away × 8 bytes) in the input. Shape of this dimension is 4.

Code

>>> x = np.asarray(range(1,13), np.int64)

>>> as_strided(x, shape=(4,3), strides=(24,8)) array([[ 1, 2, 3],

[ 4, 5, 6],

[ 7, 8, 9],

[10, 11, 12]])

Similar to

>>> x.reshape(4,3) array([[ 1, 2, 3],

[ 4, 5, 6],

[ 7, 8, 9],

[10, 11, 12]])

14) Slide a 1D window

Adapted from a StackOverflow post [1]. Similar to [2] and [3].

Input itemsize: 1 byte | Input strides: (1,)

Answer

strides = (1,1)

shape = (8,3)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 2, 3 → 4, 4 → 5) were originally 1 byte apart (=1 element away × 1 byte) in the input. Shape of this dimension is 3. Output’s top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (i.e. 1 → 2, 2 → 3, 4 → 5, …, 7 → 8) were originally 1 byte apart (=1 element away × 1 byte) in the input. Shape of this dimension is 8.

Code

>>> x = np.asarray(range(1,26), np.int8)

>>> as_strided(x, shape=(8,3), strides=(1,1)) array([[ 1, 2, 3],

[ 2, 3, 4],

[ 3, 4, 5],

[ 4, 5, 6],

[ 5, 6, 7],

[ 6, 7, 8],

[ 7, 8, 9],

[ 8, 9, 10]], dtype=int8)

Similar to

>>> # this may not be achieved concisely

15) Slide a 2D window then flatten

Question taken from a StackOverflow post [4].

Input itemsize: 1 byte | Input strides: (2, 1)

Answer

strides = (2,1)

shape = (4,6)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 0 → 1, 1 → 10, 31 → 40) were originally 1 byte apart (=1 element away × 1 byte) in the input. Shape of this dimension is 6. Output’s top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (eg. 0 → 10, 10 → 20, 41 → 51) were originally 2 bytes apart (=2 elements away × 1 byte) in the input. Shape of this dimension is 4.

Code

>>> x = np.asarray(

... [0,1,10,11,20,21,30,31,40,41,50,51],

... np.int8).reshape(6,2)

>>> as_strided(x, shape=(4,6), strides=(2,1)) array([[ 0, 1, 10, 11, 20, 21],

[10, 11, 20, 21, 30, 31],

[20, 21, 30, 31, 40, 41],

[30, 31, 40, 41, 50, 51]], dtype=int8)

Similar to

>>> # this may not be achieved concisely

16) Collapse an axis from a 3D array

Input itemsize: 1 byte | Input strides: (4, 2, 1)

Answer

strides = (4,1)

shape = (3,4)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 2, 3 → 4, 4 → 5) were originally 1 byte apart (=1 element away × 1 byte) in the input. Shape of this dimension is 4. Output’s top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (i.e. 1 → 5, 5 → 9, 2 → 6, 6 → 10) were originally 4 bytes apart (=4 elements away × 1 byte) in the input. Shape of this dimension is 3.

Code

>>> x = np.asarray(range(1,13), np.int8).reshape(3,2,2)

>>> as_strided(x, shape=(3,4), strides=(4,1)) array([[ 1, 2, 3, 4],

[ 5, 6, 7, 8],

[ 9, 10, 11, 12]], dtype=int8)

Similar to

>>> x.reshape(3,4) array([[ 1, 2, 3, 4],

[ 5, 6, 7, 8],

[ 9, 10, 11, 12]], dtype=int8)

3D exercises

17) 2 corners

Input itemsize: 2 bytes | Input strides: (10, 2)

Answer

strides = (30,10, 2)

shape = ( 2, 2, 2)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (i.e. 1 → 2, 6 → 7, 16 → 17, 21 → 22) were originally 2 bytes apart (=1 element away × 2 bytes) in the input. Shape of this dimension is 2. Output’s top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (i.e. 1 → 6, 2 → 7, 16 → 21, 17 → 22) were originally 10 bytes apart (=5 elements away × 2 bytes) in the input. Shape of this dimension is 2. Output’s box-to-box dimension (axis= -3 ):

Adjacent elements in the output (i.e. 1 → 16, 2 → 17, 6 → 21, 7 → 22) were originally 30 bytes apart (=15 elements away × 2 bytes) in the input. Shape of this dimension is 2.

Code

>>> x = np.asarray(range(1,26), np.int16).reshape(5,5)

>>> as_strided(x, shape=(2,2,2), strides=(30,10,2)) array([[[ 1, 2],

[ 6, 7]], [[16, 17],

[21, 22]]], dtype=int16)

Similar to

>>> # this may not be achieved concisely

18) Staggered slicing

Input itemsize: 1 bytes | Input strides: (5, 1)

Answer

strides = (10,6,1)

shape = ( 2,2,3)

💡 Explanation Output’s intrabox left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 2, 2 → 3, 17 → 18) were originally 1 byte apart (=1 element away × 1 byte) in the input. Shape of this dimension is 3. Output’s intrabox top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 7, 11 → 17, 12 → 18) were originally 6 bytes apart (=6 elements away × 1 byte) in the input. Shape of this dimension is 2. Output’s left box-to-right box dimension (axis= -3 ):

Adjacent elements in the output (eg. 1 → 11, 8 → 18, 9 → 19) were originally 10 bytes apart (=10 elements away × 1 byte) in the input. Shape of this dimension is 2.

Code

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)

>>> as_strided(x, shape=(2,2,3), strides=(10,6,1)) array([[[ 1, 2, 3],

[ 7, 8, 9]], [[11, 12, 13],

[17, 18, 19]]], dtype=int8)

Similar to

>>> # this may not be achieved concisely

19) Repeat a 2D array

Question taken from a StackOverflow post here [5].

Input itemsize: 2 bytes | Input strides: (10, 2)

Answer

strides = (0,10,2)

shape = (3, 2,4)

💡 Explanation Output’s intrabox left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 2, 2 → 3, 6 → 7) were originally 2 bytes apart (=1 element away × 2 bytes) in the input. Shape of this dimension is 4. Output’s intrabox top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 6, 2 → 7, 3 → 8) were originally 10 bytes apart (=5 elements away × 2 bytes) in the input. Shape of this dimension is 2. Output’s left box-to-right box dimension (axis= -3 ):

Adjacent elements in the output (eg. 1 → 1, 3 → 3, 7 → 7) were originally 0 bytes apart (=0 elements away × 2 bytes) in the input. Shape of this dimension is 3.

Code

>>> x = np.asarray(range(1,26), np.int16).reshape(5,5)

>>> as_strided(x, shape=(3,2,4), strides=(0,10,2)) array([[[1, 2, 3, 4],

[6, 7, 8, 9]], [[1, 2, 3, 4],

[6, 7, 8, 9]], [[1, 2, 3, 4],

[6, 7, 8, 9]]], dtype=int16)

Similar to

>>> # this may not be achieved concisely

20) 3D transpose

Input itemsize: 4 bytes | Input strides: (16, 8, 4)

Answer

strides = (16,4,8)

shape = ( 3,2,2)

💡 Explanation Output’s left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 3, 2 → 4, 10 → 12) were originally 8 bytes apart (=2 elements away × 4 bytes) in the input. Shape of this dimension is 2. Output’s top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 2, 3 → 4, 9 → 10, 11 → 12) were originally 4 bytes apart (=1 element away × 4 bytes) in the input. Shape of this dimension is 2. Output’s left box-to-right box dimension (axis= -3 ):

Adjacent elements in the output (eg. 1 → 5, 5 → 9) were originally 16 bytes apart (=4 elements away × 4 bytes) in the input. Shape of this dimension is 3.

Code

>>> x = np.asarray(range(1,13), np.int32).reshape(3,2,2)

>>> as_strided(x, shape=(3,2,2), strides=(16,4,8)) array([[[ 1, 3],

[ 2, 4]], [[ 5, 7],

[ 6, 8]], [[ 9, 11],

[10, 12]]], dtype=int32)

Similar to

>>> np.swapaxes(x,1,2) array([[[ 1, 3],

[ 2, 4]], [[ 5, 7],

[ 6, 8]], [[ 9, 11],

[10, 12]]], dtype=int32)

21) Slide a 2D window

Question adapted from SciPy 2008 conference [6].

Input itemsize: 8 bytes | Input strides: (40, 8)

Answer

strides = (40,40,8)

shape = ( 3, 2,5)

💡 Explanation Output’s intrabox left-to-right dimension (axis= -1 ):

Adjacent elements in the output (eg. 1 → 2, 12 → 13, 16 → 17) were originally 8 bytes apart (=1 element away × 8 bytes) in the input. Shape of this dimension is 5. Output’s intrabox top-to-bottom dimension (axis= -2 ):

Adjacent elements in the output (eg. 1 → 6, 8 → 13, 11 → 16) were originally 40 bytes apart (=5 elements away × 8 bytes) in the input. Shape of this dimension is 2. Output’s left box-to-right box dimension (axis= -3 ):

Adjacent elements in the output (eg. 9 → 14, 14 → 19) were originally 40 bytes apart (=5 elements away × 8 bytes) in the input. Shape of this dimension is 3.

Code

>>> x = np.asarray(range(1,21), np.int64).reshape(4,5)

>>> as_strided(x, shape=(3,2,5), strides=(40,40,8)) array([[[ 1, 2, 3, 4, 5],

[ 6, 7, 8, 9, 10]], [[ 6, 7, 8, 9, 10],

[11, 12, 13, 14, 15]], [[11, 12, 13, 14, 15],

[16, 17, 18, 19, 20]]])

Similar to

>>> # this may not be achieved concisely

22) Reshape 1D array to 3D array