NumPy in Anger
- Explain what an ndarray is (buffer + dtype + shape) and why that makes it fast
- Replace Python loops with vectorized operations and measure the speedup
- Apply the broadcasting rules to predict result shapes before running the code
- Use boolean masks, fancy indexing, and axis-aware aggregations fluently
- State the view-vs-copy rules and avoid the mutation bugs they cause
| Spaced-rep warm-up: Week 9 due cards + Day 63 misses | 10 min |
| ELI5 + tech read: buffers, broadcasting, views | 20 min |
| Guided: timing lab + broadcasting/axis drills | 38 min |
| Practice: the image is just an array | 18 min |
| Project: numpy_drills.py | 24 min |
| Quiz + flashcards | 10 min |
Builds on: Day 50 β Vectors & NumPy basics Β· Day 51 β Matrices & matmul Β· Day 57 β Random generators & boolean masks
You have been cutting boards one at a time with a hand saw: a Python loop picks up each number, works on it, puts it down. NumPy is the table saw: you stack a thousand boards, push the whole stack through, done. The speed is real β often 100Γ β because the saw blade is compiled C running over a solid block of memory, instead of Python picking up and unwrapping each number individually.
But power tools have two safety rules. First, the guide fence β broadcasting: when two stacks of different sizes meet, NumPy lines them up from the right edge and stretches any side of size one to match. Learn the two-line rule and shapes stop being guesswork. Second, and this one bites everyone exactly once: when you slice an array, you usually get a window onto the SAME wood, not a fresh copy. Sand the piece in your hand and you discover you have sanded the original. Knowing when you hold a view and when you hold a copy is the difference between a power user and a person with mysterious data corruption. Today you use the machine in anger β timed, on real-sized data β because Week 10 is data craft and every tool this week stands on this one.
pandas columns ARE NumPy arrays; PyTorch tensors (Day 87) copy NumPy's API almost verbatim, broadcasting rules included. A data pipeline written with Python loops is the classic "worked in the demo, died on the customer's 10-million-row file" story β Day 22's complexity lesson, but the constant factor is 100Γ. And shape fluency is the daily language of ML debugging: half of all deep-learning bugs are shape bugs, and the engineers who read (1000, 3) β (3,) at a glance fix them in seconds.
Guided practice
Time the loop out of your code
18 min- Paste the starter. It computes the same two results β sum of squares, then standardization (subtract mean, divide by sd) β on a million floats, once with Python loops and once vectorized.
- Run it and record the two speedups. Expect roughly 50β200Γ depending on the machine. Say out loud WHY: the loop version unboxes a Python object per element; the vectorized version is one C loop over a packed buffer.
- Add a third comparison of your own: count how many values exceed 2.0 (loop with a counter vs
(x > 2).sum()). - Now the dtype experiment: rebuild the data as
float32and re-time. Then make anint8array of 200 and add 100 to it β observe the overflow wraparound. Write the rule: dtype is a contract, and arithmetic near the edges of the contract lies silently. - Check memory:
x.nbytesfor the float64 vs float32 versions. Halving memory matters when Day 66 loads real datasets.
Broadcasting and axes β predict, then run
20 min- The starter creates
datawith shape (1000, 3): a thousand rows of [duration_ms, tokens, cost]. For each expression below, WRITE the result shape on paper first, then run and check: a.data - data.mean(axis=0)(center each column) b.data / data.max(axis=0)(scale each column to β€ 1) c.data.mean(axis=1)(row means β shape (1000,)) d.col = data[:, 0]; col[:, None] * np.ones(3)(what shape? why?) - The trap, on purpose:
v = np.arange(4); w = v[:, None]; print((v + w).shape)β a (4,) plus a (4,1) makes (4,4). Write one sentence on when this trap fires in real code (adding a "column" that is actually 1-D to a row vector). - Center the columns using an explicit
[None, :]instead of relying on automatic alignment, and confirm it is identical. - The axis mantra: the axis you pass is the one that disappears. Verify on
data.sum(axis=0)β (3,) anddata.sum(axis=1)β (1000,). - Finish with the view/copy drill:
window = data[:10], setwindow[:] = -1, and checkdata[:3]. Then redo with.copy()and confirm the original survives.
On your own
The image is just an array
18 minBuild a synthetic 128Γ128 grayscale "image" without any files: img = np.clip(xx*0.5 + yy*0.5 + rng.normal(0, 0.05, (128,128)), 0, 1) where xx, yy = np.meshgrid(np.linspace(0,1,128), np.linspace(0,1,128)) β a noisy diagonal gradient, values in [0, 1].
Your goals, all loop-free: (1) brighten by 0.3 WITH clipping to [0, 1] and explain what unclipped uint8 arithmetic would have done instead; (2) increase contrast: (img β 0.5) Β· 1.8 + 0.5, clipped; (3) invert it; (4) extract the center 64Γ64 crop with slicing and state whether it is a view or a copy; (5) apply a checkerboard mask (built from index parity via meshgrid or np.indices) that zeroes alternating 8Γ8 blocks; (6) report mean brightness per row for the top 5 rows (one axis-aware call).
Hints: (row // 8 + col // 8) % 2 gives the checkerboard at block scale; a slice is always a view β mutate the crop and check the original. Print img.min(), img.max() after every step; leaving [0,1] silently is how image bugs are born.
numpy_drills.py β eight one-liners you will reuse all program
Create numpy_drills.py in your practice repo: eight functions, each essentially one vectorized expression, each with an assert proving it on a tiny hand-checked example: (1) standardize(X) per column; (2) minmax(X) per column; (3) pairwise_dist(A, B) via broadcasting (A[:, None, :] β B[None, :, :], then norm over the last axis); (4) one_hot(labels, k); (5) moving_avg(x, w) via cumsum; (6) clip_outliers(x, k) replacing values beyond k standard deviations with the boundary; (7) softmax(Z) row-wise, stable (subtract row max β Day 62's exp in disguise); (8) accuracy(pred, y). No Python loops anywhere. Commit it: Day 69's features, Day 85's forward pass, and Day 92's nearest-neighbor search all reuse these exact moves.
Common mistakes & misconceptions
- Mutating a slice and corrupting the original. Basic slices are views; call .copy() when you need independence, and know that boolean/fancy indexing already return copies.
- The (n,) vs (n,1) confusion. Adding them broadcasts to (n, n); a "vector" and a "column" are different shapes. Use [:, None] to be explicit.
- Reading axis=0 as "along the rows, per row". The passed axis is the one that COLLAPSES: axis=0 on (1000, 3) gives 3 column-aggregates.
- Writing Python loops over arrays out of habit. If you typed "for" over an ndarray, stop and look for the vectorized form β it exists ~95% of the time.
- Ignoring dtype until it bites: int8/uint8 overflow wraps silently, float32 loses precision in big sums, and integer division truncates.
- Assigning into the result of fancy indexing and expecting the original to change: x[idx][0] = 5 writes into a temporary copy and is silently lost.
Q1. a has shape (4,) and b has shape (4, 1). What is (a + b).shape?
Q2. For data of shape (1000, 3), what does data.mean(axis=0) return?
Q3. b = a[10:20]; b[:] = 0. What happened to a?
Go deeper β curated resources
- Strides β how NumPy walks memory β Shape Γ strides explains why transposes are free, why some views are possible and others are not, and what "contiguous" means in PyTorch error messages later. A 15-minute read that demystifies a dozen future errors.
- Timing lab run: both speedups recorded and the overflow demo observed
- All broadcasting shapes predicted correctly before running (or misses noted and re-derived)
- Image practice done loop-free with values verified in [0, 1] after each step
- numpy_drills.py committed with all asserts passing; quiz β₯ 2/3
β Back: Day 50 introduced these arrays gently; Day 57 used masks for probability; Day 22 explained why the loop dies at scale β today you measured the constant factor. The stable softmax is Day 62's cross-entropy machinery.
Forward β: Tomorrow pandas wraps these arrays in labels β every fast pandas operation is a NumPy operation underneath. Day 85's forward pass is matmul + broadcasting; Day 87's PyTorch tensors follow these exact rules on a GPU.
Unlocks: D65 pandas I β DataFrames Β· D69 Feature Engineering Β· D70 Week 10 Checkpoint: EDA Report Β· D79 PCA & Dimensionality