Chapter 3 · Part 2

Vectorized math & broadcasting

Here's why NumPy exists. In plain Python, doing math on a list means writing a loop. In NumPy you operate on the whole array at once — it's shorter, clearer, and runs far faster because the looping happens in optimized C under the hood. This is called vectorization.

Math on whole arrays

Arithmetic and math functions apply element by element, automatically:

vectorized.py
v = np.array([10, 20, 30, 40, 50])

v * 2 + 1              # [ 21  41  61  81 101]   — no loop needed
np.sqrt([1, 4, 9, 16]) # [1. 2. 3. 4.]
np.exp([0, 1])         # [1.        2.7182818]

Compare that to the Python you didn't have to write — [x * 2 + 1 for x in v]. One array expression replaces the loop, and it stays fast even on a million elements.

Broadcasting: stretching shapes to fit

What happens when shapes don't match? NumPy broadcasts — it stretches the smaller array across the larger one so they line up. The simplest case you've already seen: v * 2 stretches the scalar 2 across every element. It also works between arrays:

broadcasting.py
col = np.array([[1],
              [2],
              [3]])          # shape (3, 1) — a column
row = np.array([10, 20, 30])   # shape (3,)   — a row

col + row
# [[11 21 31]
#  [12 22 32]
#  [13 23 33]]

The (3, 1) column and the (3,) row stretch against each other to fill a full (3, 3) grid — every combination, computed at once. That's the whole idea: line up shapes, stretch the size-1 dimensions.

🎯 Your turn
Build a 5×5 multiplication table (row i, column j holds i×j for 1..5) using broadcasting — no loops. Hint: a column 1..5 times a row 1..5. The [:, None] turns a row into a column.

Vectorization and broadcasting are the heart of NumPy. Next: boiling arrays down to summaries.