np.stack() joins a sequence of arrays along a new axis. That single word — new — is the whole difference between stack and its neighbours like concatenate, and it is where most of the confusion comes from. If you stack two arrays of shape (3,), you get one array of shape (2, 3). Concatenating them would have given you (6,).
The simplest possible example#
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.stack([a, b]))
print(np.stack([a, b]).shape)
[[1 2 3]
[4 5 6]]
(2, 3)
Both inputs were one-dimensional with shape (3,). The result is two-dimensional. NumPy created a new axis of length 2 — one slot per input array — and put it at the front.
Compare that with concatenate, which reuses the existing axis:
print(np.concatenate([a, b])) # [1 2 3 4 5 6]
print(np.concatenate([a, b]).shape) # (6,)
Rule of thumb: stack adds a dimension, concatenate extends one.
The axis argument decides where the new dimension goes#
axis=0 is the default and puts the new axis first. axis=1 puts it second, and so on.
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(np.stack([a, b], axis=0).shape) # (2, 3)
print(np.stack([a, b], axis=1).shape) # (3, 2)
print(np.stack([a, b], axis=-1).shape) # (3, 2)
print(np.stack([a, b], axis=1))
[[1 4]
[2 5]
[3 6]]
With axis=1, the arrays are interleaved into columns instead of being laid out as rows. This is the pattern you want when you have separate lists of x values and y values and need coordinate pairs:
xs = np.array([0.0, 1.0, 2.0])
ys = np.array([3.5, 4.5, 5.5])
points = np.stack([xs, ys], axis=-1)
print(points)
[[0. 3.5]
[1. 4.5]
[2. 5.5]]
Using axis=-1 rather than axis=1 means “the last axis”, which keeps working if the inputs later gain dimensions.
Stacking two-dimensional arrays#
The rule does not change when the inputs get bigger. Two arrays of shape (2, 3) stack into something with a 2 inserted somewhere:
m1 = np.array([[1, 2, 3], [4, 5, 6]])
m2 = np.array([[7, 8, 9], [10, 11, 12]])
print(np.stack([m1, m2], axis=0).shape) # (2, 2, 3)
print(np.stack([m1, m2], axis=1).shape) # (2, 2, 3)
print(np.stack([m1, m2], axis=2).shape) # (2, 3, 2)
Read it as: the new axis of length 2 is inserted at position axis, and the original shape (2, 3) fills in around it.
A practical use: three greyscale images of the same size stacked into one array you can index by image number.
frames = [np.zeros((64, 64)) for _ in range(3)]
video = np.stack(frames, axis=0)
print(video.shape) # (3, 64, 64)
print(video[1].shape) # (64, 64) - the second frame
The error you will hit#
a = np.array([1, 2, 3])
b = np.array([4, 5])
np.stack([a, b])
ValueError: all input arrays must have the same shape
This is stricter than concatenate. Concatenation only requires the arrays to match on every axis except the one being joined. Stacking creates a rectangular block, so every input must be identical in shape — no exceptions.
stack, vstack, hstack and dstack compared#
The named helpers are older and behave slightly differently. This table is the shortest useful summary:
| Function | Adds an axis? | Joins along | Two (3,) inputs give |
|---|---|---|---|
np.stack |
Yes | a new axis you choose | (2, 3) |
np.concatenate |
No | an existing axis | (6,) |
np.vstack |
Only for 1-D input | rows (axis 0) | (2, 3) |
np.hstack |
No | columns, or axis 0 for 1-D | (6,) |
np.dstack |
Yes, up to 3-D | depth (axis 2) | (1, 3, 2) |
The awkward part is that vstack and hstack change behaviour depending on how many dimensions the input has. vstack on 1-D arrays behaves like stack; on 2-D arrays it behaves like concatenate(axis=0). That inconsistency is exactly why np.stack was added — it always does the same thing.
Undoing a stack#
The portable way is simply to index or unpack:
stacked = np.stack([a, b]) # shape (2, 3)
first, second = stacked # unpacks along axis 0
print(first) # [1 2 3]
# or explicitly
parts = [stacked[i] for i in range(stacked.shape[0])]
For splitting along a different axis, np.split or np.moveaxis followed by unpacking will do it.
Questions people ask#
Is np.stack slow for large arrays?
It allocates a new array and copies the data, so it costs memory proportional to the total size. Stacking a long list of arrays one at a time in a loop is far slower than collecting them and calling np.stack once at the end.
Can I stack arrays of different dtypes?
Yes. NumPy promotes to a common type, so stacking an int array with a float array gives you a float result. If you need a specific type, pass dtype= or call .astype() afterwards.
What is the difference between np.stack and np.array on a list of arrays?
For equally shaped arrays they produce the same result. np.stack is clearer about intent, gives a better error message when shapes differ, and lets you choose the axis. np.array on ragged input either raises an error or builds an object array, which is rarely what you want.
How do I stack along a new axis in the middle of a big array?
Pass the position you want: np.stack(arrays, axis=2) inserts the new dimension as the third axis. Negative values count from the end.
Where to go next#
- Python lists explained — the built-in structure NumPy arrays are usually compared to.
- Why is my Python code not working? — a general approach for reading errors like the ValueError above.
- Python virtual environments — where to install NumPy so it does not clash with your other projects.