Boolean & Fancy Indexing
Reviewed & published by Brayan K
Boolean indexing selects array elements by a True/False condition, while fancy indexing selects them by an array of integer positions — together they let you filter and gather data without writing a single loop.
Part of the free NumPy course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll build boolean masks, filter with conditions, combine tests with & and |, overwrite values that match a rule, and grab arbitrary elements with integer index arrays.
What You'll Learn in This Lesson
- Building boolean masks like arr > 5
- Filtering with arr[arr > 5]
- Combining conditions with & and |
- Why parentheses around each test matter
- Assigning through a mask like arr[arr < 0] = 0
- Fancy indexing with integer arrays
1 Boolean Masks
A comparison like arr > 5 doesn't return a single answer — it compares every element and returns a boolean mask: an array of True/False the same shape as the original. That mask is the foundation of filtering, where each True marks a position you want to keep.
import numpy as np
arr = np.array([3, 8, 1, 10, 5, 7])
mask = arr > 5
print(mask)
# [False True False True False True]
# The mask is the same length as the array
print(mask.shape) # (6,)
# ✅ Expected output:
# [False True False True False True]
# (6,)Expected output: [False True False True False True] and (6,).
2 Filtering with a Mask
Put the mask inside the brackets — arr[arr > 5] — and NumPy returns only the elements where the mask is True. This is the single most common way to filter data in NumPy, and the result is always a 1D copy of the matching values.
import numpy as np
arr = np.array([3, 8, 1, 10, 5, 7])
# Keep only the elements greater than 5
print(arr[arr > 5]) # [ 8 10 7]
# Keep the even numbers (remainder 0 when divided by 2)
print(arr[arr % 2 == 0]) # [ 8 10]
# ✅ Expected output:
# [ 8 10 7]
# [ 8 10]Expected output: [ 8 10 7] and [ 8 10].
3 Combining Conditions with & and |
To combine two tests, use & for "and" and | for "or". Wrap each comparison in parentheses — these operators bind tighter than > or <, so without parentheses NumPy reads the expression wrong and errors out.
import numpy as np
arr = np.array([3, 8, 1, 10, 5, 7])
# Elements that are > 2 AND < 9 (note the parentheses!)
print(arr[(arr > 2) & (arr < 9)]) # [3 8 5 7]
# Elements that are < 3 OR > 9
print(arr[(arr < 3) | (arr > 9)]) # [ 1 10]
# ✅ Expected output:
# [3 8 5 7]
# [ 1 10]Expected output: [3 8 5 7] and [ 1 10].
4 Assigning Through a Mask
A mask can sit on the left of an assignment too. arr[arr < 0] = 0 finds every negative value and replaces it with zero in place — a clean way to clip or clean data.
import numpy as np
arr = np.array([-3, 5, -1, 8, -7, 2])
# Replace every negative value with 0
arr[arr < 0] = 0
print(arr) # [0 5 0 8 0 2]
# Cap everything above 6 down to 6
arr[arr > 6] = 6
print(arr) # [0 5 0 6 0 2]
# ✅ Expected output:
# [0 5 0 8 0 2]
# [0 5 0 6 0 2]Expected output: [0 5 0 8 0 2] then [0 5 0 6 0 2].
5 Fancy Indexing with Integer Arrays
Fancy indexing uses a list or array of positions to pick elements in any order: arr[[0, 2, 4]]. It also works on 2D arrays — pass an array of row indices and an array of column indices to gather specific cells.
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
# Pick positions 0, 2 and 4 in that order
print(arr[[0, 2, 4]]) # [10 30 50]
# Order is whatever you ask for
print(arr[[4, 0, 1]]) # [50 10 20]
# 2D fancy indexing: rows [0,2] paired with cols [1,0]
grid = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(grid[[0, 2], [1, 0]]) # [2 7] -> grid[0,1] and grid[2,0]
# ✅ Expected output:
# [10 30 50]
# [50 10 20]
# [2 7]Expected output: [10 30 50], [50 10 20], and [2 7].
🎯 YOUR TURN: Fill in the Blanks
Replace each ___ so the program filters, combines conditions, and uses fancy indexing.
import numpy as np
arr = np.array([2, 9, 4, 11, 6, 1])
print(arr[arr ___ 8]) # keep elements greater than 8 -> [ 9 11]
print(arr[(arr > 3) ___ (arr < 8)]) # > 3 AND < 8 -> [4 6]
print(arr[[___, 3]]) # fancy: positions 0 and 3 -> [ 2 11]Expected output: [ 9 11], [4 6], [ 2 11]. (Answers: >, &, 0.)
⚠️ Common Errors & Quick Reference
❌ ValueError: The truth value of an array with more than one element is ambiguous
You combined conditions with and/or instead of the bitwise operators.
✅ Fix: use & and | — for example (arr > 2) & (arr < 9).
❌ Wrong results when combining two conditions
You left out the parentheses, so & ran before the comparisons.
✅ Fix: wrap each test: (arr > 2) & (arr < 9), never arr > 2 & arr < 9.
| Task | Code |
|---|---|
| Make a mask | arr > 5 |
| Filter by condition | arr[arr > 5] |
| Combine: and / or | (a) & (b) / (a) | (b) |
| Assign by condition | arr[arr < 0] = 0 |
| Fancy indexing | arr[[0, 2, 4]] |
🏆 Mini Challenge: Clean the Sensor Data
A sensor reports some impossible negative readings. Clip them to zero, then count how many readings are above 50.
import numpy as np
readings = np.array([45, -2, 80, 12, -10, 99, 50])
# 1. Replace impossible negatives with 0
readings[readings < 0] = 0
print(readings) # [45 0 80 12 0 99 50]
# 2. How many readings are strictly above 50?
high = readings[readings > 50]
print(high) # [80 99]
print(high.size) # 2
# ✅ Expected output:
# [45 0 80 12 0 99 50]
# [80 99]
# 2❓ Frequently Asked Questions
Lesson complete — you can filter like a pro!
You now build boolean masks, filter with conditions, combine tests with & and | (always in parentheses), overwrite matching values, and gather arbitrary elements with fancy integer indexing.
Practice quiz
What does arr > 5 return for a NumPy array?
- A single True or False
- A boolean mask of True/False values
- The elements greater than 5
- The count of large elements
Answer: A boolean mask of True/False values. A comparison returns a boolean mask the same shape as the array.
For arr = [3, 8, 1, 10, 5, 7], what is arr[arr > 5]?
- [3 1 5]
- [False True False True False True]
- [8 10 7]
- [8 10 7 5]
Answer: [8 10 7]. Boolean indexing keeps only the elements where the mask is True.
For arr = [3, 8, 1, 10, 5, 7], what is arr[arr % 2 == 0]?
- [3 1 5 7]
- [8 10 7]
- [1 3 5]
- [8 10]
Answer: [8 10]. The mask keeps the even numbers: 8 and 10.
How do you combine two array conditions for AND?
- (a) & (b)
- a and b
- a && b
- and(a, b)
Answer: (a) & (b). Use the bitwise & with each comparison in parentheses.
Why must each comparison be wrapped in parentheses with & and |?
- To make it run faster
- Because & binds tighter than the comparison operators
- Because NumPy requires tuples
- Parentheses are optional
Answer: Because & binds tighter than the comparison operators. & and | bind tighter than > or <, so parentheses force the comparisons first.
For arr = [3, 8, 1, 10, 5, 7], what is arr[(arr > 2) & (arr < 9)]?
- [1 10]
- [8 10 7]
- [3 8 5 7]
- [3 5 7]
Answer: [3 8 5 7]. Elements that are both > 2 and < 9 are 3, 8, 5, and 7.
What does arr[arr < 0] = 0 do?
- Returns a new copy of arr
- Raises an error
- Deletes negative values
- Replaces every negative value with 0 in place
Answer: Replaces every negative value with 0 in place. Assigning through a mask overwrites the matching positions in place.
For arr = [10, 20, 30, 40, 50], what is arr[[4, 0, 1]]?
- [50 10 20]
- [10 20 50]
- [40 0 1]
- [10 30 50]
Answer: [50 10 20]. Fancy indexing returns elements in the exact order of the index list.
For grid = [[1,2,3],[4,5,6],[7,8,9]], what is grid[[0, 2], [1, 0]]?
- [1 7]
- [2 7]
- [2 9]
- [1 9]
Answer: [2 7]. It pairs (0,1) and (2,0), giving grid[0,1]=2 and grid[2,0]=7.
Do boolean indexing and fancy indexing return a copy or a view?
- A view that shares memory
- It depends on dtype
- A new copy of the data
- Neither; they error
Answer: A new copy of the data. Both boolean and fancy indexing always return a new copy.
Continue this course
- Previous: Indexing and Slicing Arrays
- Next: Reshaping and Flattening — Change array shape with reshape, ravel, flatten, and transpose
- Quick reference: NumPy cheat sheet › Indexing & Slicing
Frequently asked questions
What is a boolean mask in NumPy?
A boolean mask is an array of True/False values, usually produced by a comparison like arr > 5. When you index an array with that mask, NumPy keeps only the positions where the mask is True.
Why must I use & and | instead of 'and' and 'or'?
Python's 'and' and 'or' work on single True/False values and cannot evaluate a whole array, so they raise a ValueError. NumPy uses the bitwise operators & and | to combine element-by-element. Always wrap each comparison in parentheses because & binds tighter than the comparison operators.
What is fancy indexing?
Fancy indexing means using an array (or list) of integer positions to select elements, like arr[[0, 2, 4]]. Unlike a slice, it can pick any positions in any order and always returns a new copy, not a view.
Does boolean indexing return a copy or a view?
Boolean indexing and fancy indexing both return a new copy of the selected data. Plain slicing is what returns a view that shares memory with the original.