Adding, removing and moving axes
newaxis, squeeze, moveaxis — the tools for making shapes line up.
np.newaxisnp.expand_dims.squeeze()np.swapaxesnp.moveaxis.transpose(axes)Watch it happen
Play it through, or step back and forth yourself.
x (a stray length-1 axis)Operations leave length-1 axes lying around all the time — a[0:1], keepdims=True, loading a single image from a batch. Shape (1, 4) holds the same four numbers as (4,) but behaves differently.
The idea
Reshaping changes how many elements sit on each axis. This lesson is about changing how many axes there are and what order they're in — which sounds like bookkeeping until you meet the two situations where it's the whole job.
Removing length-1 axes
Singleton axes accumulate. a[0:1] leaves one, keepdims=True leaves one, loading a single item out of a batch leaves one.
x = np.zeros((1, 4, 1))
x.squeeze().shape # (4,) — drops every length-1 axis
x.squeeze(axis=0).shape # (4, 1) — drops only that onePrefer the explicit axis= form in real code. A bare squeeze() silently does nothing when there's nothing to drop, so a shape bug can slide straight past it; naming the axis raises instead.
Adding one
Three spellings, one meaning:
x[:, np.newaxis] # the readable one
x[:, None] # np.newaxis IS None — same thing
np.expand_dims(x, axis=1) # the function formThe position matters: x[:, None] turns (3,) into (3, 1), while x[None, :] gives (1, 3).

Why any of this matters
Here's the real reason you'll reach for it. Broadcasting lines shapes up from the right. So a (3,) array spreads across each row — and if you wanted it to spread down each column, you have to give it a second axis:
m = np.zeros((3, 3))
m + np.array([1, 2, 3]) # 1,2,3 across every row
m + np.array([1, 2, 3])[:, None] # 1,2,3 down every columnSame three numbers, perpendicular results. Whenever a broadcast comes out transposed from what you expected, this is why.
Reordering axes
.T reverses all axes. For 2-D that's the transpose you want; for 3-D it turns (2, 3, 4) into (4, 3, 2), which is almost never what anyone means.
np.swapaxes(x, 0, 1) # exchange two specific axes
np.moveaxis(x, 0, 2) # take axis 0, put it at position 2
x.transpose(1, 2, 0) # give the full new ordermoveaxis is the one worth remembering, because it reads as a sentence and because of this:
photo.shape # (120, 160, 3) — height, width, channels
np.moveaxis(photo, -1, 0).shape # (3, 120, 160) — channels firstImages arrive channels-last; PyTorch and most convolution code want channels-first. That one line is the conversion, and like every axis operation here it's a view — no data moves.

Practice
Write it yourself. The answer is there when you want it.
Putting the kettle on…
Starting up…
Write it yourself
not gradedMake a (1, 4, 1) of zeros and compare .squeeze() with .squeeze(axis=0). Then add [1, 2, 3] to a (3, 3) twice: once as it is, which lands across the rows, and once with [:, None], which lands down them. Finish by moving a (120, 160, 3) photo's channel axis to the front with np.moveaxis and printing the shape.
Your turn
4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Turn v from shape (4,) into a column of shape (4, 1).
np.zeros((1, 3, 1)) has two useless axes. Return its shape after removing all length-1 axes.
Add [10, 20, 30] down the rows of np.zeros((3, 4), dtype=np.int64) — so row 0 gains 10, row 1 gains 20, row 2 gains 30.
np.zeros((120, 160, 3)) is an image. Move the channel axis to the front and return the resulting shape.
