NumPy Indexing and Slicing — Beyond the Basics
- Basic slicing returns a view — mutating it mutates the original. Call .copy() when you need independence.
- Fancy indexing (integer arrays) always returns a copy, even when the indices are in order.
- Boolean indexing filters by condition and returns a copy — the original is unchanged.
- NumPy offers four indexing methods: basic slicing (view), integer fancy indexing (copy), boolean indexing (copy), and field access for structured arrays.
- Basic slices always return a view — they share memory with the original. Modifying the view changes the original.
- Fancy indexing (integer arrays) always returns a copy, even if indices are in sequence.
- Boolean indexing also returns a copy — the original array stays untouched.
- Use np.shares_memory() or the .base attribute to check if an array is a view.
- Biggest mistake: assuming a slice is independent; call .copy() explicitly when needed.
Need to know if arr is a view or copy
print(arr.base) # None = own data, otherwise = parent arrayprint(np.shares_memory(arr, original)) # True = viewWant to ensure an operation returns a view
view = original[1:5, 2:7] # always viewprint(np.shares_memory(view, original)) # TrueMemory doubled after indexing a large array
result = arr[[0, 2, 5]] # copy → memory spikeresult = arr[0:6:2] # view → no extra memoryProduction Incident
np.shares_memory().When dealing with shared memory arrays, copy before mutation.If a pipeline includes multiple transformation steps, make explicit copies at the boundaries.Production Debug GuideSymptom → Action guide for production incidents involving unexpected array mutations.
Basic Slicing — Views, Not Copies
Basic slicing uses integers, slices (start:stop:step), and np.newaxis. It always returns a view — a window into the same memory. This is the most common pattern and the source of most confusion.
import numpy as np a = np.arange(12).reshape(3, 4) print(a) # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] # Slicing returns a view view = a[1:, 2:] print(view) # [[ 6 7] # [10 11]] # Modifying the view modifies the original view[0, 0] = 99 print(a[1, 2]) # 99 — the original changed # Use .copy() when you want independence safe = a[1:, 2:].copy() safe[0, 0] = 0 print(a[1, 2]) # still 99
[ 4 5 6 7]
[ 8 9 10 11]]
[[ 6 7]
[10 11]]
99
99
- The window doesn't own the items; it just points to them.
- Any change you make through the window changes the shelf.
- Calling .copy() builds a new shelf with identical items.
- Always ask: "Do I need to modify the original? If not, .copy()."
Integer Array Indexing — Fancy Indexing
Pass an array of indices to select specific elements. This always returns a copy, and the output shape matches the index array shape. It's called "fancy indexing" and gives you powerful reordering and selection.
import numpy as np a = np.array([10, 20, 30, 40, 50]) # Select elements at positions 0, 2, 4 print(a[[0, 2, 4]]) # [10 30 50] # Reorder and repeat elements print(a[[4, 4, 2, 0]]) # [50 50 30 10] # 2D fancy indexing m = np.arange(12).reshape(3, 4) rows = np.array([0, 1, 2]) cols = np.array([0, 2, 3]) print(m[rows, cols]) # [0, 6, 11] — picks m[0,0], m[1,2], m[2,3] # Use np.ix_ to select a submatrix (outer indexing) print(m[np.ix_([0, 2], [1, 3])]) # [[ 1 3] # [ 9 11]]
[50 50 30 10]
[ 0 6 11]
[[ 1 3]
[ 9 11]]
Boolean Indexing — Filtering Arrays
Pass a boolean array of the same shape to select elements where the condition is True. This is how you filter arrays without writing a loop. Always returns a copy.
import numpy as np temps = np.array([22.1, 18.4, 35.7, 29.3, 15.0, 38.2]) # All temperatures above 30 degrees hot = temps[temps > 30] print(hot) # [35.7 29.3 ... wait, 29.3 < 30] print(temps[temps > 30]) # [35.7 38.2] # Compound conditions — must use & and |, not and/or extreme = temps[(temps < 18) | (temps > 35)] print(extreme) # [18.4 ... ] print(temps[(temps < 18) | (temps > 35)]) # [15. 38.2] # np.where: replace values that fail the condition clipped = np.where(temps > 35, 35.0, temps) print(clipped) # caps at 35.0
[15. 38.2]
[22.1 18.4 35. 29.3 15. 35. ]
np.newaxis and Ellipsis
np.newaxis inserts a new dimension — it is just an alias for None. The ellipsis ... means 'all the dimensions in between'. These are crucial for broadcasting and nD array manipulation.
import numpy as np v = np.array([1, 2, 3]) # shape (3,) # Turn a row vector into a column vector col = v[:, np.newaxis] # shape (3, 1) print(col.shape) # (3, 1) # Ellipsis: useful for nD arrays data = np.zeros((2, 3, 4, 5)) print(data[0, ..., 2].shape) # (3, 4) — first slice, last slice, middle left alone print(data[..., -1].shape) # (2, 3, 4) — all dims except the last
(3, 4)
(2, 3, 4)
Combining Indexing Techniques — Power and Pitfalls
You can mix basic slicing with fancy indexing or boolean indexing in the same expression. The result follows the copy/view rules per axis: any axis using a slice stays a view, any axis using fancy/boolean becomes a copy. The combined result is always a copy if any axis uses fancy indexing.
import numpy as np arr = np.arange(24).reshape(2, 3, 4) # Basic slice on first axis, fancy on second: result is a copy result = arr[0, [0, 2], :] # shape (2, 4) print(result.base is arr) # False # Basic slice on first, boolean on second: also copy mask = np.array([True, False, True]) result2 = arr[0, mask, :] print(result2.base is arr) # False # All axes basic: view view = arr[0, :, 1:3] print(view.base is arr) # True
False
True
🎯 Key Takeaways
- Basic slicing returns a view — mutating it mutates the original. Call .copy() when you need independence.
- Fancy indexing (integer arrays) always returns a copy, even when the indices are in order.
- Boolean indexing filters by condition and returns a copy — the original is unchanged.
- np.ix_ constructs an open mesh for selecting submatrices with fancy indexing.
- np.newaxis is just None — it inserts a length-1 dimension without copying data.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat is the difference between a view and a copy in NumPy? How do you check which you have?Mid-levelReveal
- QWhy does modifying a NumPy slice affect the original array?JuniorReveal
- QHow does boolean indexing differ from fancy indexing in terms of memory?SeniorReveal
- QWhat does np.ix_ do and when would you use it?Mid-levelReveal
- QExplain what happens when you mix basic slicing with fancy indexing in the same expression.SeniorReveal
Frequently Asked Questions
How do I know if an operation returns a view or a copy?
Check the base attribute. If arr.base is original returns True, arr is a view. You can also check np.shares_memory(arr, original). Basic slices are always views; fancy and boolean indexing are always copies.
Why can't I use 'and' and 'or' with NumPy boolean arrays?
Python's 'and'/'or' operate on the truthiness of objects, which for arrays raises an ambiguity error. NumPy uses & (bitwise AND) and | (bitwise OR) for element-wise logical operations. Always wrap each condition in parentheses: (a > 0) & (a < 10).
What is the difference between a[0] and a[[0]]?
a[0] is basic indexing and reduces the dimension by 1 — a[0] on a (3,4) array gives shape (4,). a[[0]] is fancy indexing and preserves dimensions — it gives shape (1,4). The difference matters when you need to keep the array 2D.
Can I get a view from fancy indexing?
No. Fancy indexing always returns a copy. If you need a view and have to use an array of indices, restructure your logic to use basic slicing (e.g., using [start:stop:step]) whenever possible.
What is the ellipsis (...) in NumPy indexing?
The ellipsis represents 'all the dimensions not explicitly listed'. For a 4D array, a[0, ..., 2] means a[0, :, :, 2]. It makes code cleaner for high-dimensional arrays.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.