where, select and clip
An if-statement over a whole array, without losing its shape.
np.wherenp.selectnp.clipnp.nonzeroa[mask] = xnp.putmaskWatch it happen
Play it through, or step back and forth yourself.
scoresscores[scores >= 60]shape (3,) — flattenednp.where(scores >= 60, 1, 0)shape (2, 3) — keptA mask selects, and loses the shape. Often you want the opposite: keep the shape and replace the values that don't qualify. That's an if-statement over a whole array, and np.where is how you write it.
The idea
A mask selects and loses the shape. Very often you want the opposite: keep every position and replace the values that don't qualify. That's an if-statement applied to a whole array, and np.where is how you write one.
where
np.where(condition, value_if_true, value_if_false)It walks every position and picks from the second or third argument. The result has the same shape as the condition:
np.where(scores >= 60, 1, 0) # a pass/fail grid
np.where(scores >= 60, scores, 60) # lift failures up to 60
np.where(scores >= 60, scores, 0) # zero out failuresAll three arguments broadcast, so you can mix scalars and arrays freely — including two different arrays, which is how you'd merge two datasets by a condition.
Compare the two tools directly:
scores[scores >= 60] # shape (3,) — selects, flattens
np.where(scores >= 60, 1, 0) # shape (2, 3) — replaces, keeps shape
where with one argument
Called with only a condition, np.where does something different: it returns the coordinates of the True positions, as one array per axis.
np.where(scores >= 80) # (array([0, 1]), array([2, 2]))
np.nonzero(scores >= 80) # identical, and better namedThose tuples are ready to use as fancy indices, which ties lesson 8 to this one. Prefer np.nonzero when that's what you mean — the one-argument where is a historical accident and reads confusingly next to the three-argument form.
clip
Squeezing values into a range is common enough to have its own function, and it says what it means:
scores.clip(40, 80) # both ends
scores.clip(min=40) # floor only
np.clip(photo.astype(int) + 60, 0, 255) # the image-brightening pattern
More than two outcomes
Nesting np.where inside itself gets unreadable after the second level. For several bands, use np.select — a list of conditions and a matching list of results, evaluated in order, first match wins:
np.select(
[scores >= 80, scores >= 60, scores >= 40],
[3, 2, 1 ],
default=0,
)Order matters. Because the first match wins, put the most specific condition first — with these bands reversed, everything above 40 would score 1.
Writing through a mask
You can also assign directly into the matching positions:
scores[scores < 40] = 40 # in place
np.putmask(scores, scores < 40, 40) # the same thing, spelled outThe difference from np.where matters: np.where returns a new array and leaves the original alone, while assignment edits in place — and therefore edits every view onto that memory, exactly as in lesson 6. Reach for np.where by default, and assign in place only when you mean to.
Practice
Write it yourself. The answer is there when you want it.
Putting the kettle on…
Starting up…
Write it yourself
not gradedPrint scores, then np.where three ways: 1 and 0 for pass and fail at 60, the scores with everything under 60 raised to 60, and .clip(40, 80). Print the coordinates of every score of 80 or more with np.nonzero. Finish with np.select grading into 3, 2, 1 and 0.
Your turn
4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Return scores with every mark below 60 replaced by 0, keeping the (2, 3) shape.
Squeeze scores into the range 50 to 85.
Return the coordinates of every mark of 80 or more, using np.nonzero.
Turn scores into bands with np.select: 3 for 80 and above, 2 for 60–79, 1 for 40–59, and 0 below that.
