NumPy: Boolean and Fancy Indexing
Last updated: 2026-08-26
1. What You'll Learn
- ❶ Boolean masks — using conditional expressions to generate True/False arrays for filtering
- ❷
np.where— ternary conditional selection and element replacement - ❸ Fancy indexing — selecting elements in arbitrary order with integer arrays
- ❹ Combined indexing — mixing basic and advanced indexing
- ❺ Advanced indexing returns copies — the key difference from views
2. Story
Alice has 100,000 temperature readings and needs to find all readings above 40°C. She writes temps[temps > 40] and gets the result instantly. Bob is amazed: "A Python loop with if statements would take at least 5 lines — NumPy does it in one line, nearly 100x faster!" This is the power of NumPy advanced indexing — complex data filtering with concise expressions.
3. Boolean Masks
(1) What Is a Boolean Mask
A boolean mask is a True/False array with the same shape as the original. NumPy comparison operators work element-wise and return boolean arrays.
import numpy as np
arr = np.array([3, -1, 4, -2, 5])
mask = arr > 0
print(mask) # [ True False True False True]
print(arr[mask]) # [3 4 5]
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
(2) Comparison Operators
| Operator | Meaning | Equivalent Function |
|---|---|---|
> |
Greater than | np.greater |
>= |
Greater or equal | np.greater_equal |
< |
Less than | np.less |
<= |
Less or equal | np.less_equal |
== |
Equal | np.equal |
!= |
Not equal | np.not_equal |
(3) Logical Combinations
Combine multiple conditions with logical operators — use & (and), | (or), ~ (not), not Python's and/or/not. Each condition must be wrapped in parentheses.
arr = np.array([1, 5, 8, 3, 9, 2])
mask = (arr > 3) & (arr < 8) # 3 < arr < 8
print(arr[mask]) # [5]
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
| Logical Op | Meaning | Equivalent Function |
|---|---|---|
& |
Logical AND | np.logical_and |
| |
Logical OR | np.logical_or |
~ |
Logical NOT | np.logical_not |
4. np.where and Conditional Selection
(1) Three-argument form: np.where(condition, x, y)
Returns elements from x where condition is True, from y where False.
import numpy as np
arr = np.array([-3, 5, -1, 8, -2, 7])
result = np.where(arr > 0, arr, 0)
print(result) # [0 5 0 8 0 7]
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
(2) One-argument form: np.where(condition)
Returns the indices where the condition is True.
indices = np.where(arr > 0)
print(indices) # (array([1, 3, 5]),)
print(arr[indices]) # [5 8 7]
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
5. Fancy Indexing
Fancy indexing uses integer arrays to select elements in any order.
import numpy as np
a = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
# Select specific indices
print(a[[0, 3, 7]]) # [0 3 7]
# Select in any order, with repeats
print(a[[5, 5, 1, 9]]) # [5 5 1 9]
# 2D fancy indexing
b = np.arange(12).reshape(3, 4)
print(b[[0, 2]]) # rows 0 and 2
print(b[:, [0, 2, 3]]) # columns 0, 2, 3 for all rows
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
▶ Example
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: Boolean mask filtering (Difficulty ⭐)
import numpy as np
scores = np.array([55, 82, 91, 47, 73, 100, 68])
passing = scores[scores >= 60]
print("Passing:", passing) # [82 91 73 100 68]
excellent = scores[scores >= 90]
print("Excellent:", excellent) # [91 100]
# Multiple conditions
mid = scores[(scores >= 60) & (scores < 90)]
print("Mid-range:", mid) # [82 73 68]
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
▶ Example
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
: np.where for conditional replacement (Difficulty ⭐⭐)
import numpy as np
# Clip to range: replace values outside [0, 100]
data = np.array([-5, 120, 30, -20, 80, 200])
clipped = np.where(data < 0, 0, np.where(data > 100, 100, data))
print("Clipped:", clipped) # [ 0 100 30 0 80 100]
# Categorize
grades = np.array([45, 72, 88, 55, 91])
letter = np.where(grades >= 85, 'A',
np.where(grades >= 70, 'B',
np.where(grades >= 60, 'C', 'D')))
print("Grades:", letter) # ['D' 'B' 'A' 'D' 'A']
> **Output:** Run NumPy 2.x in your local Python environment to see the ndarray output. The Piston server does not have NumPy pre-installed — install it locally (`pip install numpy`) and compare. Actual values may vary by NumPy version and random seed.
6. Comparison Tables
(1) Indexing Methods
| Method | Syntax | Returns | Copies? | Use Case |
|---|---|---|---|---|
| Basic slice | a[1:3] |
View | No | Sub-array, contiguous range |
| Integer index | a[2] |
Scalar | N/A | Single element |
| Boolean mask | a[mask] |
1D array | Yes | Conditional filtering |
| Fancy indexing | a[[1,3,5]] |
Array | Yes | Arbitrary order selection |
| np.where | np.where(cond, x, y) |
Array | Yes | Ternary selection |
(2) Boolean vs Fancy
| Feature | Boolean Mask | Fancy Indexing |
|---|---|---|
| Syntax | arr[arr > 0] |
arr[[1, 3, 5]] |
| Selection logic | Condition-based | Position-based |
| Result shape | 1D (flattened) | Same shape as index array |
| Speed | Fast for large masks | Fast for sparse selection |
▶ Example: 2D fancy indexing with np.ix_ (Difficulty ⭐⭐)
import numpy as np
arr = np.arange(20).reshape(4, 5)
print("Original:\n", arr)
# Select specific rows and columns
rows = [0, 2, 3]
cols = [1, 3]
result = arr[np.ix_(rows, cols)]
print("Rows [0,2,3], Cols [1,3]:\n", result)
# Modify selected elements
arr[np.ix_([0, 1], [0, 4])] = -1
print("After modification:\n", arr)
Output:
TEXT 📖 Display onlyOriginal: [[ 0 1 2 3 4] [ 5 6 7 8 9] [10 11 12 13 14] [15 16 17 18 19]] Rows [0,2,3], Cols [1,3]: [[ 1 3] [11 13] [16 18]] After modification: [[-1 1 2 3 -1] [-1 6 7 8 -1] [10 11 12 13 14] [15 16 17 18 19]]
❓ FAQ
arr[arr > 0] = 0 sets all positive values to 0. This is an in-place modification. np.where(arr > 0, 0, arr) returns a new array instead.arr[mask] returns the values where mask is True. np.where(mask) returns the indices where mask is True. Use arr[mask] for the values, np.where(mask) when you need positions.arr[[0,2], [1,3]] selects elements at (0,1) and (2,3). For independent row/column selection, use arr[np.ix_([0,2], [1,3])].np.where(mask) to get the original shape indices.📖 Summary
- Boolean masks:
arr[arr > 0]— filter by condition, returns a copy - Logical combinations: use
&,|,~with parentheses around each condition np.where(cond, x, y): ternary selection — returns x where True, y where Falsenp.where(cond): returns indices where condition is True- Fancy indexing:
arr[[i1, i2, ...]]— select by arbitrary integer indices - Advanced indexing (boolean + fancy) always returns a copy, not a view
📝 Exercises
-
Beginner (Difficulty ⭐): Create an array of 20 random integers between -10 and 10. Use a boolean mask to extract all positive values. Use
np.whereto replace all negative values with 0. -
Intermediate (Difficulty ⭐⭐): Create a 5x5 array of random integers 0-100. Use boolean masks to: (a) count values above 50, (b) replace values below 20 with 20, (c) extract the row and column indices of all values > 80 using
np.where. -
Advanced (Difficulty ⭐⭐⭐): Create a 10x4 array of student scores (10 students, 4 subjects). Use fancy indexing to: (a) select students at positions [0,3,7] for all subjects, (b) select subjects at positions [1,3] for all students, (c) compute the average of the top 3 students by total score.