NumPy advanced indexing is the family of subscript operations that select array elements using a boolean mask or an integer array of indices, often built with helpers like np.where and np.logical_and. It is distinct from basic indexing (slicing, integer indexing, negative indexing) in one consequential way: advanced indexing returns a copy of the data; basic slicing returns a view of the original array. That copy-vs-view distinction is the entire reason NumPy draws the basic/advanced line — get it wrong and assignments either silently fail to propagate or silently mutate something you didn’t mean to.
For the basic array structure these techniques operate on, see NumPy arrays. For the slicing/integer/negative-index forms that are basic (and return views), see NumPy array slicing.
Basic vs advanced indexing (the contract)
| Indexer | Category | Returns |
|---|---|---|
arr[2], arr[:, 1:3], arr[::-1], arr[-2:] | Basic (slicing, integer, negative) | View — shares memory with arr |
arr[mask] where mask.dtype == bool | Advanced (boolean) | Copy — independent array |
arr[[0, 2, 5]], arr[idx] where idx is an integer ndarray | Advanced (integer-array, “fancy”) | Copy — independent array |
Mix: arr[1:3, [0, 2]] | Advanced (any advanced part promotes the whole expression) | Copy |
Practical consequence:
import numpy as np
arr = np.arange(10)
# Basic slicing — view
b = arr[2:5]
b[0] = 99
print(arr) # [ 0 1 99 3 4 5 6 7 8 9] — original modified
# Advanced (boolean) indexing — copy
c = arr[arr > 4]
c[0] = -1
print(arr) # [ 0 1 99 3 4 5 6 7 8 9] — original untouched
# In-place write through a boolean mask still works
arr[arr > 4] = 0
print(arr) # [0 1 99 3 4 0 0 0 0 0] — direct subscript-assign goes backThe last case is the crucial subtlety: arr[mask] = … (assignment) writes through to the original, but c = arr[mask]; c[...] = … (binding then mutating) does not, because c is a fresh copy. Subscript-assignment is special syntax that NumPy intercepts.
Boolean indexing
Apply a comparison to an array to get a boolean mask of the same shape:
greater_than_five = test_data > 5greater_than_five[i, j] is True if test_data[i, j] > 5, else False.
Use the mask to select elements:
test_data[greater_than_five]The result is a 1D array containing only the elements where the mask is True. The original 2D structure is lost — boolean indexing always flattens to 1D.
Shape-preserving selection: np.where
To preserve the original array shape, use np.where(condition, value_if_true, value_if_false):
drop_under_five = np.where(test_data > 5, test_data, 0)- Where the condition is true, take the value from
test_data. - Where false, replace with
0.
The result has the same shape as the input. This is useful for “keep some, replace others” operations.
Multiple conditions
Combine boolean masks with np.logical_and (or & for boolean arrays):
mask = np.logical_and(test_data > 5, test_data < 20)
test_data[mask]The result is a 1D array of elements satisfying both conditions: .
Equivalent operators: np.logical_or, np.logical_not, np.logical_xor. Or use bitwise operators on boolean arrays: &, |, ~. Don’t use Python’s and/or/not — they don’t broadcast over arrays.
When to use each
- Boolean masking (
arr[arr > 5]): filter by an arbitrary condition; flattens to 1D; returns a copy. - Integer-array (fancy) indexing (
arr[[0, 3, 7]],arr[idx]): pick out specified positions in a chosen order; returns a copy. np.where(cond, a, b): shape-preserving “replace where condition” — useful for “keep some, replace others” without flattening.np.logical_and/np.logical_or/np.logical_not(or&/|/~on boolean arrays): combine masks before indexing. Don’t use Python’sand/or/not— they don’t broadcast.- For consecutive subsets, reversal, and column/row picks (the basic-indexing forms), use slicing — see NumPy array slicing for that family. Slicing returns a view, which is the right tool when you want changes to propagate back to the original.
For basic array creation and operations, see NumPy arrays. For arithmetic and elementwise operations, see NumPy arithmetic and comparison operations.