NumPy · Module 7 of 13 · 4 concepts · about 5 min

Reshaping & Combining Arrays

Changing an array's shape without touching its data, and joining or dividing several arrays along an axis.

At work: Getting data into the shape a model or report expects: batches of images, stacked time series, feature matrices.

reshape, and -1

What it is

reshape(shape) reinterprets the same buffer with a new shape, in row-major order by default. One axis may be -1, meaning 'infer this from the total size'.

Why it matters

It is the shape-plumbing between a model and its data — flattening a batch of images, adding the feature axis a estimator expects — and it is free whenever the array is contiguous.

In the real world: Turning 8,760 hourly readings into a 365-days-by-24-hours grid to see the daily pattern of electricity use.

Visual example

reshape

reshape re-reads the same flat buffer in row-major order — the values keep their order, only the folding changes.

x — shape (2, 3)
012
0012
1345
reshape(3, 2)
x.reshape(3, 2)
01
001
123
245

The buffer never changes

flat: 0 1 2 3 4 5

(2, 3): rows of 3

(3, 2): rows of 2

Why this matters

Shape errors between data loaders, feature matrices and models are almost always solved by one reshape — and knowing it is free means you can use it freely.

Common misconception

Wrong idea: reshape rearranges the values to fit the new shape.

Correct idea: it changes nothing but the shape. The flat order 0 1 2 3 4 5 is read in rows of the new width. To reorder axes you need transpose.

x = np.arange(6)
x.reshape(2, 3)      # [[0, 1, 2], [3, 4, 5]] — row-major fill
x.reshape(-1, 2)     # (3, 2): the -1 is inferred
x.reshape(3, 2).shape
imgs.reshape(len(imgs), -1)   # flatten each image, keep the batch axis

Reshaping to reorder axes. reshape never moves data, so turning (batch, h, w) into (h, w, batch) with reshape interleaves the images into nonsense. Axis reordering is transpose, which does move data when you later ask for it contiguous.

Practise it: Flatten a Batch of Images

transpose, ravel and flatten

What it is

x.T or x.transpose(axes) permutes axes and returns a view. ravel() flattens, returning a view when it can; flatten() always copies.

Why it matters

It is the difference between a free axis swap and a real data movement, and it is what makes A @ B.T cheap.

In the real world: Flipping a stores-by-days table to days-by-stores so it matches what a forecasting model expects.

x = np.arange(24).reshape(2, 3, 4)
x.transpose(2, 0, 1).shape     # (4, 2, 3) — a view, strides reordered
x.T.shape                      # (4, 3, 2) — reverses every axis
x.ravel().base is not None     # True when a view was possible
x.T.ravel()                    # has to copy: the strides are not contiguous

Assuming .T on a 1-D array does something. It returns the same (n,) array; people then debug a matrix multiplication for a while.

Practise it: Flatten a Batch of Images

concatenate, stack and their axis argument

What it is

np.concatenate(arrays, axis=) joins along an existing axis; np.stack(arrays, axis=) creates a new axis. vstack, hstack and column_stack are named shortcuts.

Why it matters

It is how features get assembled, and the two functions differ in the result's ndim — which is exactly the mistake that shows up as a shape error one call later.

In the real world: Joining this month's feature rows onto last month's to retrain a model, or stacking 32 photos into one batch.

a, b = np.zeros((2, 3)), np.ones((2, 3))
np.concatenate([a, b], axis=0).shape   # (4, 3) — more rows
np.concatenate([a, b], axis=1).shape   # (2, 6) — more columns
np.stack([a, b]).shape                 # (2, 2, 3) — a new leading axis
np.column_stack([np.arange(3), np.arange(3)]).shape   # (3, 2)

Concatenating in a loop to build up a result. Every call allocates the whole output again, so it is quadratic — collect the pieces in a list and concatenate once.

split, and the inverse of concatenate

What it is

np.split(arr, n, axis=) divides an array into n equal pieces along an axis; array_split allows uneven pieces when the length does not divide evenly.

Why it matters

Cross-validation folds, batching and train/val/test cuts are all this: one array divided into several views along an axis, with no copy until something writes to a piece.

In the real world: Cutting a long list of transactions into equal batches to score them in parallel.

x = np.arange(12).reshape(4, 3)
folds = np.split(x, 4, axis=0)      # 4 arrays, each shape (1, 3)
first, rest = np.split(x, [1], axis=0)   # sizes, not counts: shapes (1, 3) and (3, 3)

Passing a plain integer to np.split when the axis length is not divisible by it. It raises rather than giving you a ragged last piece — use array_split when the sizes are not guaranteed to divide evenly.

Practise it: Reproducible Train and Test Indices