Indexing, slicing and masks

Basic slicing, views versus copies, boolean masks and fancy indexing — plus the assignment rule that catches everyone.

Slicing returns views

A slice of an array is a view onto the same memory, not a copy. Changing the slice changes the original. .copy() breaks the link.

a = np.arange(10)
a[2:5]           # array([2, 3, 4])
a[::-1]          # reversed
a[::2]           # every other element

v = a[0:3]
v[0] = 99
a[0]             # 99  -> the slice was a view

c = a[0:3].copy()
c[0] = -1
a[0]             # still 99
m = np.arange(12).reshape(3, 4)
m[1, 2]          # row 1, column 2
m[1]             # entire row 1
m[:, 1]          # entire column 1
m[0:2, 1:3]      # block: rows 0-1, columns 1-2

Boolean masks and fancy indexing

A comparison produces an array of booleans; using it as an index keeps the True positions. This is how filtering is meant to be written — no loops.

scores = np.array([88, 42, 95, 67, 71])

scores > 70                      # array([ True, False,  True, False,  True])
scores[scores > 70]              # array([88, 95, 71])
scores[(scores > 60) & (scores < 90)]   # use & | ~ with parentheses

scores[scores < 70] = 0          # conditional assignment
np.where(scores > 70, "pass", "fail")
idx = np.array([0, 2, 4])
scores[idx]                      # fancy indexing -> a COPY
scores[[0, 0, 1]]                # duplicates allowed

np.argmax(scores)                # index of the largest value
np.count_nonzero(scores > 70)
⚠️
Fancy indexing (an integer array as index) returns a copy, while slicing returns a view. If you assign through a fancy index back to the array — a[idx] += 1 — the behaviour is well defined, but assigning into a computed copy silently does nothing. When a change "does not stick", check whether you are working on a view or a copy.

axis is a direction, not a row/column

axis=0 means "collapse the first dimension" — for a 2-D array, operate down the columns. Reading it as "rows" is what makes people get it backwards.

m = np.array([[1, 2, 3],
              [4, 5, 6]])

m.sum()            # 21  - everything
m.sum(axis=0)      # array([5, 7, 9])  - one value per column
m.sum(axis=1)      # array([ 6, 15])   - one value per row
m.mean(axis=0)     # column means

FAQ

How do I test whether two arrays have the same values?
np.array_equal(a, b), or np.allclose(a, b) for floats where tiny rounding differences are expected. == gives you an array of booleans, not one answer.
Why did a[idx] = value not change the original array?
Almost always because the array you modified was a temporary copy created by fancy indexing or a filtering expression. Assign to the original name, or use np.put/boolean masks which do write through.

NumPy arrays Vectorised maths and broadcasting

Last refreshed 2026-09-18.