NumPy Advanced Indexing — Core Concepts
Why this topic matters
Most NumPy tutorials cover basic slicing (a[2:5]), but real data work demands more surgical access. Advanced indexing lets you select arbitrary elements, filter by condition, and rearrange data — all without Python loops. Misunderstanding it leads to accidental copies, silent bugs from views-vs-copies confusion, and slow code that defeats the purpose of using NumPy.
Two kinds of advanced indexing
Fancy (integer array) indexing
Pass an array of indices instead of a slice:
a = np.array([10, 20, 30, 40, 50])
idx = np.array([0, 3, 4])
print(a[idx]) # [10, 40, 50]
This works in multiple dimensions too. You provide one index array per axis:
grid = np.arange(12).reshape(3, 4)
rows = np.array([0, 2])
cols = np.array([1, 3])
print(grid[rows, cols]) # [1, 11] — elements at (0,1) and (2,3)
Boolean (mask) indexing
Pass a boolean array the same shape as your data:
scores = np.array([55, 82, 91, 43, 78])
mask = scores > 75
print(scores[mask]) # [82, 91, 78]
The mask acts as a filter. Every True position is included, every False is skipped.
Views vs copies — the critical distinction
| Indexing type | Returns | Modifying result changes original? |
|---|---|---|
Basic slice (a[2:5]) | View | Yes |
Fancy indexing (a[[0, 3]]) | Copy | No |
Boolean indexing (a[mask]) | Copy | No |
This distinction catches many developers. If you assign to a fancy-indexed result, the original array does not change. But if you assign through fancy indexing, NumPy writes back to the original:
a = np.zeros(5)
a[[1, 3]] = 99 # writes to original — a is now [0, 99, 0, 99, 0]
b = a[[1, 3]] # b is a copy
b[0] = -1 # a is unchanged
Common misconception
Many people think a[[0, 2], [1, 3]] selects a sub-grid (rows 0 and 2, columns 1 and 3 — four elements). It does not. It selects pairs: elements (0,1) and (2,3) — just two elements. To select a sub-grid, use np.ix_:
grid = np.arange(20).reshape(4, 5)
sub = grid[np.ix_([0, 2], [1, 3])]
print(sub.shape) # (2, 2) — the actual sub-grid
Performance considerations
Fancy indexing is slower than slicing because it cannot use simple stride arithmetic. Each index must be looked up individually. For large arrays, prefer boolean masks when possible — NumPy optimizes contiguous true/false runs internally.
When you need to apply the same fancy index repeatedly, consider sorting your index array. Sorted access patterns are friendlier to CPU caches.
The one thing to remember: Advanced indexing always returns copies, not views — that one rule prevents the majority of shape and mutation bugs.
See Also
- Python Bokeh Get an intuitive feel for Bokeh so Python behavior stops feeling unpredictable.
- Python Numpy Broadcasting Rules How NumPy magically makes different-sized arrays work together without you writing any loops.
- Python Numpy Einsum One tiny function that replaces dozens of NumPy operations — once you learn its shorthand, array math becomes a breeze.
- Python Numpy Fft Spectral How NumPy breaks apart a signal into its hidden frequencies — like separating a chord into individual notes.
- Python Numpy Memory Views Why NumPy arrays can share the same data without copying it — and how that makes your code fast but occasionally surprising.