Module 3 · Lesson 6
Case Study: Vectorizing a Real Loop
Let’s take a realistic loop-based function and vectorize it end to end.
def score_loop(values):
"""Clip each value into [0, 100], then apply a bonus:
+10 if the clipped value is above 80, otherwise no bonus."""
out = []
for v in values:
v = max(0, min(100, v))
if v > 80:
v += 10
out.append(v)
return out
A tempting shortcut is np.vectorize(score_loop_single_element): it looks
vectorized because it accepts arrays, but internally it still calls your
Python function once per element in a Python loop — you get NumPy’s
syntax without NumPy’s speed. Prefer composing real ufuncs
(np.clip, np.where, comparisons) instead.
def score_loop(values):
out = []
for v in values:
v = max(0, min(100, v))
if v > 80:
v += 10
out.append(v)
return out
def score_vectorized(values):
clipped = np.clip(values, 0, 100)
return np.where(clipped > 80, clipped + 10, clipped)
sample = [-5, 50, 85, 120]
print(score_loop(sample))
print(score_vectorized(np.array(sample)))
Output:
[0, 50, 95, 110]
[ 0 50 95 110]
Vectorize the scoring function
Write score_vectorized_checked(values) that reproduces score_loop's
behavior exactly (clip to [0, 100], then +10 bonus if the clipped value
is > 80) using only np.clip and np.where — no loop, no
np.vectorize. Return a NumPy array.
💡 Hint
Clip first with np.clip(values, 0, 100), then use np.where on the clipped result to add the bonus conditionally.
✅ Solution
def score_vectorized_checked(values):
clipped = np.clip(values, 0, 100)
return np.where(clipped > 80, clipped + 10, clipped)
Spot the fake vectorization
Write is_real_ufunc_pipeline(func) that takes a function and returns
False if func is an instance of np.vectorize (i.e. built by wrapping a
scalar Python function), and True otherwise. This is a quick sanity check
you can literally run on your own code to catch accidental fake
vectorization.
💡 Hint
Use isinstance(func, np.vectorize).
✅ Solution
def is_real_ufunc_pipeline(func):
return not isinstance(func, np.vectorize)
🎯 Key Takeaways
- Operators (
+,*,>, …) are shorthand for ufuncs (np.add,np.multiply,np.greater, …) running in compiled code. - Broadcasting compares shapes right-to-left: equal dims match, size-1 dims
stretch, anything else raises
ValueError. - A vectorized rewrite of a loop isn’t just shorter — it’s dramatically faster, because per-element Python overhead disappears.
- Build boolean masks with
&/|/~andnp.where; never trust==on floats — usenp.isclose/np.allclose. np.outerand[:, np.newaxis]/[np.newaxis, :]broadcasting build full pairwise tables without nested loops.np.vectorizeis a convenience for mapping a scalar function over an array — it does not give you a real performance improvement; prefer composing real ufuncs.
Next up: Module 4 — Aggregations, Statistics & Sorting, where you’ll learn to summarize and reorder array data along any axis.