Day 4: NumPy vectorization: broadcasting, indexing, why loops are slow
The single habit that matters most this week
A Python for loop over an array's elements pays Python's interpreter overhead — type checks, function call overhead, object allocation — on every single element. A vectorized operation hands the whole array to a compiled C loop once. On a million-element array, the difference isn't a rounding error; it's routinely 50-100x.
import numpy as np, time
a = np.random.rand(1_000_000)
# loop
start = time.perf_counter()
result = np.empty_like(a)
for i in range(len(a)):
result[i] = a[i] * 2 + 1
print("loop:", time.perf_counter() - start) # ~0.15-0.3s
# vectorized
start = time.perf_counter()
result = a * 2 + 1
print("vectorized:", time.perf_counter() - start) # ~0.001-0.003sSame computation, 1M elements — vectorized NumPy vs a plain Python loop (log scale)
The rule to internalize
If you catch yourself writing for i in range(len(array)), stop — there is almost always a vectorized equivalent. This one habit is the difference between code that processes a catalog in seconds and code that processes it in minutes.
Broadcasting
Broadcasting is the rule set that lets NumPy apply an operation between arrays of *different* shapes without you writing an explicit loop — e.g. adding a single RGB offset (3,) to every pixel of an entire image (H, W, 3). NumPy compares shapes from the right; dimensions match if they're equal or one of them is 1.
image = np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)
brightness_offset = np.array([10, 10, 10]) # shape (3,)
# broadcasting stretches (3,) across every (H, W) pixel — no loop needed
brightened = np.clip(image.astype(int) + brightness_offset, 0, 255).astype(np.uint8)Indexing: slicing, fancy indexing, boolean masks
# slicing: every row, first 100 columns
cropped = image[:, :100]
# boolean mask: every pixel darker than 50 across all channels
dark_mask = image.mean(axis=2) < 50
# fancy indexing: select specific rows by an array of indices
selected_rows = image[[0, 5, 10]]Key terms
- Vectorization
- Expressing a computation as whole-array operations so it runs inside compiled loops instead of the Python interpreter.
- Broadcasting
- NumPy's rule set for applying operations between differently-shaped arrays by virtually stretching smaller dimensions.
- Boolean mask
- An array of True/False values, the same shape as the data, used to select elements matching a condition.
Why is `image * 2` dramatically faster than looping over every pixel and multiplying by 2 in pure Python?