NumPy·Lesson 9·11 min·0/4 exercises

where, select and clip

An if-statement over a whole array, without losing its shape.

np.wherenp.selectnp.clipnp.nonzeroa[mask] = xnp.putmask

Watch it happen

Play it through, or step back and forth yourself.

scores
72
45
90
58
33
81
shape (2, 3)
a mask selectsscores[scores >= 60]shape (3,) — flattened
where replacesnp.where(scores >= 60, 1, 0)shape (2, 3) — kept

A 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 failures

All 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
Hand-drawn notes contrasting a mask, which selects and flattens, with np.where, which replaces in place and keeps the original 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 named

Those 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
Hand-drawn notes showing clip pushing out-of-range values onto the nearest boundary of a number line while values inside are left alone, dropping nothing.

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 out

The 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 graded

Print 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.

Write something and press Run — the output appears here.

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.

your answer

Squeeze scores into the range 50 to 85.

your answer

Return the coordinates of every mark of 80 or more, using np.nonzero.

your answer

Turn scores into bands with np.select: 3 for 80 and above, 2 for 60–79, 1 for 40–59, and 0 below that.

your answer