Array Indexing and Slicing
NumPy extends Python's slice syntax to multiple dimensions and adds two powerful mechanisms unavailable on plain lists: fancy indexing (indexing with an array or list of positions) and boolean masking (indexing with a boolean array). Each mechanism has different semantics for whether the returned array shares memory with the original — a distinction that matters enormously for both correctness and performance.
Cricket analogy: Selecting specific overs like [2, 5, 9] from a season's ball-by-ball array (fancy indexing) or filtering deliveries where runs > 4 (boolean masking) go beyond what a plain Python list of scores could do, and each has different memory-sharing rules that matter for correctness.
Basic Slicing Returns a View
Slicing with start:stop:step syntax, e.g. arr[1:4] or arr[:, 2], returns a view: a new ndarray object pointing at the same underlying memory buffer as the original, just with different shape/stride metadata. No data is copied, which makes slicing extremely cheap, but it also means that modifying the sliced view mutates the original array. If you need an independent copy, call .copy() explicitly.
Cricket analogy: Slicing overs[1:4] from a full innings array returns a view sharing the same memory as the original scorecard, so editing a value in the sliced overs also silently changes the original innings unless you call .copy() first.
Fancy Indexing and Boolean Masking Return Copies
In contrast, fancy indexing — arr[[0, 2, 4]] or arr[rows, cols] with array/list indices — and boolean masking — arr[arr > 5] — always return a new array with copied data, because the selected elements are not contiguous in general and cannot be represented as a simple view (a stride pattern). This is why chained assignment like arr[arr > 5][0] = 100 silently fails to modify arr: the boolean mask creates a temporary copy, and the assignment happens on that copy.
Cricket analogy: Selecting scores[[0, 2, 4]] (fancy indexing) or scores[scores > 50] (boolean masking) for a batter's half-centuries always returns a new copied array, which is why chained assignment like scores[scores > 50][0] = 100 silently fails to update the original.
import numpy as np
matrix = np.arange(12).reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# Basic slicing -- returns a VIEW
row_view = matrix[1, :] # [4 5 6 7], shares memory with matrix
row_view[0] = 99
print(matrix[1, 0]) # 99 -- original was mutated!
# Fancy indexing -- returns a COPY
selected_rows = matrix[[0, 2]] # rows 0 and 2, independent copy
selected_rows[0, 0] = -1
print(matrix[0, 0]) # 0 -- original untouched
# Boolean masking -- returns a COPY
mask = matrix > 6
filtered = matrix[mask] # 1-D array of elements > 6
print(filtered) # [ 7 8 9 10 11]A useful mnemonic: slices with colons (:) are views (cheap, memory-sharing); indexing with lists, arrays, or booleans is fancy indexing (expensive-ish, always a fresh copy).
arr[mask][0] = value does NOT modify arr, because arr[mask] already produced a copy before the assignment ran. Use arr[mask, 0] = value or arr[np.where(mask)] patterns, or restructure with arr[idx] = value on a single indexing step, to actually mutate in place.
- Basic slicing (start:stop:step) returns a view sharing memory with the original array.
- Fancy indexing (list/array of indices) always returns a new, independent copy.
- Boolean masking (arr[condition]) also always returns a copy, never a view.
- Mutating a view mutates the original array; mutating a copy does not.
- Chained indexing like arr[mask][0] = x fails silently because the first step already copied.
- Use .copy() explicitly whenever you need an independent array from a slice.
Practice what you learned
1. What does basic slicing (e.g. arr[1:4]) return in NumPy?
2. What does boolean mask indexing (arr[arr > 5]) return?
3. Why does arr[arr > 5][0] = 100 fail to modify the original array arr?
4. If you modify an element in a view obtained via slicing, what happens to the original array?
5. How can you guarantee an independent array when slicing?
Was this page helpful?
You May Also Like
The ndarray: NumPy Basics
The ndarray is NumPy's core data structure — understanding its shape, dtype, strides, and axis conventions is essential to using arrays correctly and efficiently.
Vectorized Operations and Broadcasting
Vectorization applies operations across whole arrays without explicit loops, and broadcasting extends this to arrays of different but compatible shapes.
Indexing with loc and iloc
Master the two primary pandas selection accessors — .loc for label-based indexing and .iloc for integer-position-based indexing — and the pitfalls of chained indexing.
Boolean Filtering
Learn how to filter DataFrame rows using boolean masks built from comparison and logical operators, including combining conditions and using isin and query for cleaner syntax.