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

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.

TaskCode
Make a maskarr > 5
Filter by conditionarr[arr > 5]
Combine: and / or(a) & (b) / (a) | (b)
Assign by conditionarr[arr < 0] = 0
Fancy indexingarr[[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

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.