LESSON 176 · PYTHON TOOLS28 min

NumPy boolean indexing: filter arrays with masks and avoid copy surprises

Build a Boolean mask, select matching NumPy values or rows, and distinguish a selected copy from assignment into the source.

Before you start: Lists · List Comprehensions · Basic Operators

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: NumPy boolean indexing: filter arrays with masks and avoid copy surprises

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    We have a NumPy array of sensor readings and want values above ten. The direct expression values > 10 gives a Boolean mask, with one flag per element. Indexing the array with that mask gives us twelve and nineteen. This is NumPy boolean indexing, and it becomes useful when filtering numerical arrays or selecting rows from a table. There are two easy traps. Python's and operator cannot combine multi-element array conditions, and changing a selected result does not change the source array because Boolean indexing returns a copy. We will run the first mistake, inspect the mask and its shape, then select values and

  2. 00:40

    rows correctly. Finally, we will compare editing the selected copy with assigning through the mask. The linked browser lesson practices the selection predicate with a plain Python list, because the local NumPy demonstration is the authoritative example of array behavior. Before looking at code, predict the mask for four, twelve, nineteen, and seven. The positions above ten should be False, True, True, False. The first comparison produces several booleans, not one Python bool. The and operator tries to decide whether the whole left-hand array is true before it even considers the right-hand comparison.

  3. 01:21

    NumPy cannot infer whether we meant all values, any value, or an elementwise combination, so it raises. The correct elementwise operator is ampersand, with each comparison in parentheses: (values > 5) & (values < 20). Parentheses matter because of Python operator precedence. A second mistake would be using a mask with the wrong number of entries. For a one-dimensional four-value array, each position needs a corresponding Boolean. Shape checks are especially important when data is reshaped or filtered in stages. An error here is more useful than silently selecting the wrong positions.

  4. 02:02

    In the next run, we will print a simple mask and a combined mask so the meaning of each flag is clear before indexing the data. We can now see the mask as data. It has four Boolean entries and a shape matching the four-element source. above_ten marks exactly twelve and nineteen. between also marks seven because seven is greater than five and less than twenty. The ampersand applies the two comparisons element by element, unlike and. In a larger expression, keep each comparison parenthesized so the intended grouping is visible. A mask does not sort anything; it preserves the positions where the condition is true.

  5. 02:43

    For a two-dimensional array, we need to decide whether we want individual cells or entire rows. A full-shape mask selects matching cells and usually produces a flat one- dimensional result. A one-dimensional row mask with one flag per row selects whole rows. We will demonstrate both the simple value filter and a row filter in the fix. Check the output shape when passing the selected result onward to a function that expects a matrix. The first selection answers our original question: values greater than ten are twelve and nineteen. The second selection uses the reading in column one to build a mask with one Boolean per row.

  6. 03:25

    There are three rows, so the mask has three entries. Indexing rows with that mask retains the complete first and third rows, producing a two-by-two array. If we instead used rows > 10 as the mask, it would have the full two-dimensional shape and select individual values, returning a flat collection. Neither form is universally better; they answer different questions. The page's browser task asks for a plain- list version of the first predicate, preserving order and leaving the input untouched. Run the local NumPy snippet for the array form. At this point, a subtle assumption remains: does selected_rows share memory with rows?

  7. 04:06

    For Boolean indexing, it is a copy. We will verify that before deciding how to update source values. The first two prints prove that changing selected did not change values. Boolean selection created a copy. That is useful when you want to analyze matching readings without altering the original data. The last line uses the mask on the left side of assignment, which updates the matching positions in the source array. This is the form to use when replacing or clipping values in place. Keep the mask's meaning in mind after a mutation; if you recompute values > 10 after replacing those positions with zero, the new mask will be different.

  8. 04:48

    Also remember that a NumPy array condition has array truth values, so combine conditions with parenthesized ampersands or vertical bars, not Python's scalar and or or. In the lesson exercise, first solve the threshold predicate on a regular list, including a no-match case. Then run this local code to see the NumPy- specific shape and copy rules. A small four-element example is enough to expose both mistakes before they enter a larger data pipeline.

Understand the concept

NumPy boolean indexing lets you filter an array with a Boolean condition such as values > 10. The comparison produces a mask with True where the condition holds. values[mask] selects those positions. Install NumPy in your local Python environment with python -m pip install numpy and run the complete local example below. The browser practice panel does not guarantee NumPy, so its exercise uses a list comprehension to rehearse the same selection logic without claiming to be a NumPy array.

A one-dimensional mask must align with the axis it indexes. A mask built from a four-element array has four Boolean entries; trying to index it with three entries raises an IndexError. For a two-dimensional array, rows[row_mask] selects whole rows when row_mask has one entry per row. If you use a full-shape Boolean mask on a two-dimensional array, the selected values come back as a flat one-dimensional result. These are different operations, so check shapes before assuming the output layout.

Boolean indexing is advanced indexing and the selected result is a copy. Changing selected[0] does not change values. To modify the source array, assign through the indexed expression: values[mask] = 0. This distinction prevents a subtle bug where code appears to update the filtered values but leaves the original untouched. If you need the selected values for downstream analysis, a copy is often exactly what you want.

For multiple conditions, combine elementwise masks with & and parenthesize each comparison, for example (values >= 5) & (values < 20). Python's and is for one truth value, so it cannot combine multi-element NumPy arrays. The local example shows both a one-dimensional filter and a row mask, then verifies copy-versus-assignment behavior. The browser task selects values above a threshold from a plain list; solve it before translating the predicate to NumPy syntax.

  • Comparisons create Boolean masks
  • A mask must align with the indexed axis
  • Boolean selection returns a copy
  • Assign through arr[mask] to update the source

See it step by step

Read the code, predict the output, then compare it with the result.

01. Rehearse the mask predicate

PYTHON
values = [4, 12, 19, 7]
mask = [value > 10 for value in values]
print(mask)
print([value for value, keep in zip(values, mask) if keep])
EXPECTED OUTPUT
[False, True, True, False]
[12, 19]

The browser example mirrors the selection idea; NumPy constructs the mask with values > 10 directly.

02. Use strict boundaries

PYTHON
values = [10, 11, 19, 20]
print([value for value in values if 10 < value < 20])
EXPECTED OUTPUT
[11, 19]

NumPy expresses the same range with (values > 10) & (values < 20).

03. Model row selection

PYTHON
rows = [[1, 12], [2, 7], [3, 19]]
print([row for row in rows if row[1] > 10])
EXPECTED OUTPUT
[[1, 12], [3, 19]]

A row mask selects whole rows when it has one Boolean per row.

Run the NumPy mask example locally

Install NumPy with python -m pip install numpy in your local environment. The browser practice task models the predicate with standard Python.

PYTHON
import numpy as np

values = np.array([4, 12, 19, 7])
mask = values > 10
selected = values[mask]
print(mask.tolist(), selected.tolist())
selected[0] = 99
print(values.tolist(), selected.tolist())
values[mask] = 0
print(values.tolist())

rows = np.array([[1, 12], [2, 7], [3, 19]])
row_mask = rows[:, 1] > 10
print(rows[row_mask].tolist())

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Read a mask before selecting values

A NumPy comparison such as values > 10 yields an array of booleans, one per input value. Indexing with that mask keeps positions marked True. The browser example creates the same flags with a list comprehension because it does not require NumPy installation. In local NumPy code, values[mask] is the compact selection expression. The result preserves the order of matching elements; it does not sort them.

A mask is meaningful only relative to the axis it indexes. For a one-dimensional array, it must have a compatible length. A mask of three entries cannot index four values. Inspect mask.shape and values.shape during debugging. The tutorial's concrete four-value example is small enough to predict before you run it, which makes a shape or boundary mistake obvious.

PYTHON
values = [4, 12, 19, 7]
mask = [value > 10 for value in values]
print(mask)
print([value for value, keep in zip(values, mask) if keep])
EXPECTED OUTPUT
[False, True, True, False]
[12, 19]
Follow the reasoning
  1. Predict which entries exceed the threshold before running the code.
  2. Compare the Boolean flags and selected values.
02 / 03

Handle rows and combined conditions

For a two-dimensional NumPy array, a one-dimensional mask with one boolean per row selects whole rows. Build it from one column, for example rows[:, 1] > 10, and then index rows[row_mask]. A full-shape mask instead selects individual cells and yields a one-dimensional collection of matches. These two forms answer different questions, so check the intended output shape before writing downstream code.

Two conditions must be combined elementwise. In NumPy, write (values >= 5) & (values < 20), with parentheses around each comparison. Python's and asks whether each whole array has one truth value and normally raises an ambiguity error for multi-element arrays. The browser example uses Python's chained scalar comparison for each item; translating to NumPy requires the parenthesized & expression.

PYTHON
rows = [[1, 12], [2, 7], [3, 19]]
print([row for row in rows if row[1] > 10])
values = [4, 12, 19, 7]
print([value for value in values if 5 <= value < 20])
EXPECTED OUTPUT
[[1, 12], [3, 19]]
[12, 19, 7]
Follow the reasoning
  1. Build one predicate per row or element according to the desired output.
  2. Combine NumPy masks with &, never scalar and.
03 / 03

Know whether you selected or assigned

NumPy Boolean indexing creates a copy of selected data. If selected = values[mask], mutating selected does not change values. This can surprise code that treats selected as a view into the original array. The local example prints both arrays after changing selected to demonstrate the distinction. When the goal is to update the source, assign through the index expression itself: values[mask] = 0.

A filtered copy is useful for calculating statistics without touching the source. An indexed assignment is useful for clipping or replacing source values. Decide which operation the task needs and verify the original afterward. The browser task mirrors only the selection predicate with a list comprehension; it does not promise NumPy memory semantics. Run the local NumPy snippet for the copy and assignment demonstration.

PYTHON
values = [4, 12, 19, 7]
selected = [value for value in values if value > 10]
selected[0] = 99
print(values)
print(selected)
EXPECTED OUTPUT
[4, 12, 19, 7]
[99, 19]
Follow the reasoning
  1. Inspect the original after changing a selection.
  2. Use indexed assignment when the source should change.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnComplete select_readings(readings, threshold) using a list comprehension to return only values strictly greater than threshold, preserving order. The prepared call should print [12, 19]. The local NumPy example uses the same predicate as a Boolean mask.
Need a hint?

Filter the comprehension with if value > threshold. Do not mutate the input list.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.What does values > 10 produce for a NumPy array?

    02.Does changing selected = values[mask] change values?

    03.How do you combine two array conditions elementwise?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Trying to mutate a selected copy

    COMMON MISTAKE
    selected = values[mask]; selected[0] = 0
    FIX
    Assign through values[mask] = 0 when the source should change.

    What happens: The original values array remains unchanged.
    Boolean selection returns a separate array.

    Combining masks with and

    COMMON MISTAKE
    values > 5 and values < 20
    FIX
    Use (values > 5) & (values < 20).

    What happens: NumPy cannot interpret the truth value of a multi-element array.
    The ampersand combines the Boolean arrays element by element.

    Browse this lesson’s problem fixes

    Sources and further reading