Boolean Indexing & Masks
Filter arrays by a condition — build True/False masks, combine them with & | ~ and use them to select or count values.
"Show all of Virat's fifties", "how many ducks did he make?", "scores between 30 and 60" — these questions are about conditions, not positions. NumPy answers them with boolean indexing, one of its most powerful features: no loop, no if, just one expression.
Definition
A boolean mask is an array of
True/Falsevalues with the same shape as the data, usually created by a comparison such asarr > 50. Boolean indexing means putting that mask inside the brackets —arr[mask]— to select only the elements where the mask is True. The result is always a new 1D array (a copy).
- Mask — like a stencil laid over the array:
True= "keep this position",False= "skip it". - Same shape — the mask is matched position by position, so its shape must match the data (or the axis being filtered).
- 1D result — whatever the input's dimensions,
arr[mask]returns the selected values in a flat 1D array, in row-major order. - Copy — unlike slicing, boolean indexing copies the data; changing the result does not change
arr. (Assigning through the mask,arr[mask] = 0, does changearr.)
Syntax
python
mask = arr > 50 # 1) build a mask with a comparison
arr[mask] # 2) use it to filter
arr[arr > 50] # both steps in one line
arr[(arr >= 30) & (arr <= 60)] # AND — both conditions
arr[(arr < 20) | (arr > 90)] # OR — at least one condition
arr[~(arr > 50)] # NOT — invert the mask
mask.sum() # count of True values| Operator | Meaning | Python keyword it replaces | Example |
|---|---|---|---|
& | element-wise AND | and | (a > 30) & (a < 60) |
| | element-wise OR | or | (a == 0) | (a >= 100) |
~ | element-wise NOT | not | ~(a > 50) |
Note: You must wrap each condition in parentheses. In Python,
&and|bind more tightly than>,<,==, soa > 30 & a < 60is read asa > (30 & a) < 60— which either gives wrong results or raises an error.
Example — step by step: mask, then filter
python
import numpy as np
virat_runs = np.array([72, 45, 88, 31, 64, 0, 103, 52, 19, 77, 41, 93, 28, 66])
mask = virat_runs > 50
print("mask :", mask)
print("dtype:", mask.dtype, " shape:", mask.shape)
print("Scores above 50:", virat_runs[mask])
print("How many 50+ :", mask.sum())
print("Ducks :", virat_runs[virat_runs == 0])text
mask : [ True False True False True False True True False True False True
False True]
dtype: bool shape: (14,)
Scores above 50: [ 72 88 64 103 52 77 93 66]
How many 50+ : 8
Ducks : [0]How it works: the comparison produces a boolean array of the same shape. Inside the brackets, NumPy keeps only positions holding True. Because True counts as 1 and False as 0, mask.sum() is the number of matching elements.
Example — combining conditions with & | ~
python
virat_runs = np.array([72, 45, 88, 31, 64, 0, 103, 52, 19, 77, 41, 93, 28, 66])
print("30 to 60 :", virat_runs[(virat_runs >= 30) & (virat_runs <= 60)])
print("<20 or 100+ :", virat_runs[(virat_runs < 20) | (virat_runs >= 100)])
print("NOT above 50 :", virat_runs[~(virat_runs > 50)])
print("Match numbers of 50+ scores:", np.arange(1, 15)[virat_runs >= 50])text
30 to 60 : [45 31 52 41]
<20 or 100+ : [ 0 103 19]
NOT above 50 : [45 31 0 19 41 28]
Match numbers of 50+ scores: [ 1 3 5 7 8 10 12 14]How it works: &, | and ~ combine masks element by element. The last line uses a mask built from virat_runs to filter a different array of the same length — the match numbers 1–14 — which is how you find "in which matches" something happened.
Example — why and and missing parentheses fail
python
virat_runs = np.array([72, 45, 88, 31, 64, 0, 103, 52, 19, 77, 41, 93, 28, 66])
try:
virat_runs[virat_runs > 30 and virat_runs < 60]
except ValueError as e:
print("Using 'and':", e)
try:
virat_runs[virat_runs > 30 & virat_runs < 60]
except ValueError as e:
print("No brackets:", e)text
Using 'and': The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
No brackets: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()How it works: Python's and asks "is this whole array True?" — for 14 values that question is ambiguous, so NumPy refuses. Without parentheses, 30 & virat_runs is computed first and the chained comparison again needs the truth of a whole array. Fix: (virat_runs > 30) & (virat_runs < 60).
Example — masks on a 2D array
python
scores = np.array([
[72, 45, 88, 31, 64, 0, 103, 52], # row 0: Virat
[56, 0, 74, 88, 32, 45, 61, 17], # row 1: Rohit
[45, 32, 0, 67, 18, 54, 78, 22], # row 2: Hardik
])
print("All 50+ scores (flattened):", scores[scores >= 50])
print("50+ count per player :", (scores >= 50).sum(axis=1))
# Filter ROWS with a 1D mask: players whose total is above 350
totals = scores.sum(axis=1)
print("Totals:", totals)
print("Players with 350+ runs:")
print(scores[totals > 350])text
All 50+ scores (flattened): [ 72 88 64 103 52 56 74 88 61 67 54 78]
50+ count per player : [5 4 3]
Totals: [455 373 316]
Players with 350+ runs:
[[ 72 45 88 31 64 0 103 52]
[ 56 0 74 88 32 45 61 17]]How it works: a 2D mask on a 2D array returns a flat 1D list of matching values (the table structure is lost). A 1D mask on axis 0 (one True/False per row) keeps whole rows, so the result stays 2D.
Comparison — Python loop vs boolean indexing
| Python list + loop | NumPy boolean indexing | |
|---|---|---|
| Code | [x for x in runs if x > 50] | runs[runs > 50] |
| Speed | interpreted loop, slow on big data | compiled C loop, very fast |
| Combine conditions | and, or, not | &, |, ~ with parentheses |
| Count matches | sum(1 for x in runs if x > 50) | (runs > 50).sum() |
Common mistakes
- ❌
arr[arr > 30 and arr < 60]→ValueError: The truth value of an array ... is ambiguous. ✅arr[(arr > 30) & (arr < 60)]. - ❌ Forgetting parentheses:
arr[arr > 30 & arr < 60]. ✅ Wrap every condition. - ❌ Using
!ornotto invert. ✅ Use~mask(ornp.logical_not(mask)). - ❌ Expecting
scores[scores > 50]to keep the 2D table. ✅ It returns 1D. To keep the shape, usenp.where(scores > 50, scores, 0)(next topics). - ❌ A mask of the wrong length →
IndexError: boolean index did not match indexed array.
Interview tip
Q: How do you count how many values in an array are greater than 50?
A: (arr > 50).sum() or np.count_nonzero(arr > 50). The comparison creates a boolean mask; summing it counts the True values because True = 1 and False = 0.
Key points to remember
- A comparison like arr > 50 returns a boolean mask of the same shape as arr.
- arr[mask] keeps only the elements where the mask is True; the result is a new 1D array (a copy).
- Combine masks with & (and), | (or), ~ (not) — never with the Python keywords and/or/not.
- Always put each condition in parentheses: (a > 30) & (a < 60), because & and | bind tighter than comparisons.
- mask.sum() counts matches because True = 1 and False = 0.
- A mask built from one array can filter another array of the same length (e.g. match numbers).
- A 1D mask on the rows of a 2D array keeps whole rows; a 2D mask flattens the result to 1D.
Try it yourself
Fifties, ducks and counting
Combining conditions with & | ~
Filtering rows of a 2D table
Practice: find the 'big match' scores
Revision Flashcards
Tap a card to flip it and see the answer.
