Broadcasting is how you center columns, build grids and compare all pairs, all without loops or tiling.
Broadcasting describes how NumPy combines arrays whose shapes differ. Conceptually the smaller array is stretched to match the larger one; in reality nothing is copied, NumPy just reuses the same values with a stride of zero.
The rule, applied to the shapes aligned from the right:
- Two dimensions are compatible if they are equal or one of them is 1.
- If one array has fewer dimensions, it is treated as having extra length-1 dimensions on the left.
- The result takes the larger size in each dimension. Any other mismatch raises
ValueError.
Worked examples:
(3, 4)with(4,): the vector becomes(1, 4)and is applied to every row.(3, 4)with(3,): aligned from the right that is 4 vs 3, an error. To apply one value per row, reshape to(3, 1).(5, 1)with(1, 6): gives(5, 6), every combination, an "outer" operation.
That last pattern is very powerful: x[:, None] - y[None, :] gives all pairwise differences, and with **2 and a sum over a third axis it gives a distance matrix.
Use keepdims=True in reductions to get shapes that broadcast back cleanly, e.g. (a - a.mean(axis=1, keepdims=True)).
The trap is memory. Broadcasting avoids copying inputs, but the result has the full broadcast shape. Pairwise differences of two 100,000-element vectors produce 10 billion elements, 80 GB of float64. When the full result is too big, process in chunks or use a specialised routine.
np.broadcast_shapes(s1, s2) tells you the result shape, and np.broadcast_to(a, shape) makes a read-only broadcast view.
Example
import numpy as np
data = np.array([[1.0, 2.0, 3.0],
[4.0, 6.0, 8.0]]) # shape (2, 3)
col_means = data.mean(axis=0) # shape (3,)
print(data - col_means) # centre each column
row_means = data.mean(axis=1, keepdims=True) # shape (2, 1)
print(data - row_means) # centre each row
x = np.array([0, 1, 2])
y = np.array([10, 20])
print(x[:, None] + y[None, :]) # shape (3, 2) table
# [[10 20]
# [11 21]
# [12 22]]
print(np.broadcast_shapes((8, 1, 6), (7, 1))) # (8, 7, 6)
try:
data - np.array([1, 2]) # (2, 3) vs (2,)
except ValueError as e:
print("error:", e)Remember
- Shapes are compared from the right; sizes must match or be 1
- Missing leading dimensions count as 1
a[:, None] op b[None, :]computes all pairs- Inputs aren't copied, but the result has the full broadcast size
Watch out for
- Expecting a length-n vector to apply per row of an (n, m) array; it aligns with the last axis, so reshape it to (n, 1).