5.4.4 · D3Scientific Computing (Python)

Worked examples — Broadcasting — rules, why it works, gotchas

3,025 words14 min readBack to topic

Before anything: a shape is just the tuple of sizes NumPy prints, one number per axis. (3, 4) means "3 rows, 4 columns." An axis is one direction you can travel in. If you have not met these, read NumPy Arrays — shape, dtype, strides first — we assume only that (3, 4) = 3 rows × 4 columns and nothing more.


The scenario matrix

Every broadcasting situation is one of the cells below. We label each worked example with its cell so you can see the whole space gets covered.

Cell Situation What happens Example
A Both axes equal element-by-element, no stretch Ex 1
B One axis is 1 → stretch size-1 side reused Ex 2
C Missing leading axes padded with 1s on the left, then stretch Ex 3
D Both size-1 axes stretch outer-grid Ex 4
E Scalar (shape ()) all-1 dims, stretches to anything Ex 5
F Orientation trap — right shapes, wrong axis silent wrong answer / error Ex 6
G Hard error — 3 vs 4, neither is 1 raises ValueError Ex 7
H Degenerate: zero-length axis (size 0) legal, result has a 0-axis Ex 8
I 3-D real-world word problem stack all rules at once Ex 9
J Exam twist(3,) + (3,1) accidental matrix result bigger than both inputs Ex 10

The engine of all of them is the same three-step algorithm:

Read the figure below before continuing — it is the algorithm. On the left, shape A = (4, 3) and shape B = (3,) are drawn as boxes. Follow the coral dashed line: that is the right edge, and both shapes are pushed against it — this is "right-align." Watch the butter-yellow (1) box appear under the front of B: that is the pad-left step filling the missing leading axis with a 1. Then each vertical column of boxes is compared: (4 vs 1) stretches to 4, (3 vs 3) stays 3, so the result reads off as (4, 3) in coral on the right. Every later example is just this picture with different numbers.

Figure — Broadcasting — rules, why it works, gotchas

Cell A — both axes equal (no stretch)

This is the boring baseline. Everything else differs from it by stretching.


Cell B — one axis is 1 (the workhorse)

The figure below shows why this is memory-free. The lavender (4,3) grid on the left is X; the mint (3,) row on top-right is mu. Follow the coral arrows: each of the 4 rows of X points back at the same single mint row — that is the size-1 axis stretching by re-reading one buffer (stride-0), never copying. Look at the caption: 3 numbers serve all 4 rows.

Figure — Broadcasting — rules, why it works, gotchas

Cell C — missing leading axes


Cell D — both size-1 axes stretch (outer grid)

In the figure, the lavender column col runs down the left edge and the mint row row runs across the top; each butter-yellow inner cell is their sum. Trace cell [2,3]: line down from col=2 and across from row=3, and the box reads 5 — literally i + j. Both a column and a row got stretched into a full grid.

Figure — Broadcasting — rules, why it works, gotchas

Cell E — the scalar (simplest case)


Cell F — the orientation trap (silent wrong / error)

The figure contrasts the two attempts. Top row: M * w lines the mint 3 under the lavender 4 — the coral text spells out "4 ≠ 3, neither is 1 → ERROR." Bottom row: after w[:,None], the mint 3 now sits under M's 3 and a butter 1 sits under the 4, so it stretches cleanly to (3,4). The side box shows the payoff: row 0 × 1, row 1 × 2, row 2 × 3.

Figure — Broadcasting — rules, why it works, gotchas

Cell G — the hard error (know why it's fatal)


Cell H — degenerate zero-length axis


Cell I — full 3-D real-world problem


Cell J — the exam twist (result bigger than both inputs)

The figure lays it out: a padded into a mint row across the top, b as a lavender column down the left, and the butter 3×3 grid of sums between them. Read res[2,1]: from b=3 (row) and a=20 (col) the box shows 23. A (3,) and a (3,1) silently produced a matrix — the classic exam gotcha.

Figure — Broadcasting — rules, why it works, gotchas

Flashcards

Which single size acts as the broadcast wildcard?
1 (and only 1 — 0 is not a wildcard).
(3,4) + (2,4) → ?
ERROR — axis 0 is 3 vs 2, neither is 1.
(3,) + (3,1) → ?
(3,3) — a matrix, because the (3,) pads to (1,3) and both axes stretch.
(0,4) - (4,) → ?
(0,4), a valid empty array (size-1 stretches to 0).
Scale each ROW of (3,4) by w shape (3,) — the fix?
w[:, None](3,1) so it aligns with axis 0.
(10,32,32,3) - (3,) lands the 3 on which axis?
The last (channel) axis, by right-alignment.

Recall Mnemonic for every cell

"Right-align, pad-left with 1s, then stretch a 1 or crash on a mismatch." Zero is data, not a wildcard. When in doubt, print .shape.


Connections

  • Parent: Broadcasting rules (Hinglish)
  • NumPy Arrays — shape, dtype, strides
  • Vectorization vs Python loops
  • Reshaping and newaxis (None) indexing
  • Array Memory Layout — C vs Fortran order
  • Reductions and the axis argument
  • np.tile and np.repeat (explicit replication)