5.4.3 · D5Scientific Computing (Python)
Question bank — Indexing and slicing — basic, boolean masking, fancy indexing
Before the traps, one refresher so every term below is earned:
Recall The three vocabulary words you must own
- View ::: a new array object that points at the same numbers in memory as the original — editing one edits both.
- Copy ::: a new array with its own fresh block of memory — editing it never touches the original.
- Broadcast ::: NumPy stretching a smaller shape to match a bigger one so an operation lines up (see Broadcasting).
True or false — justify
Basic slicing a[1:4] always returns a copy.
False. Basic slicing returns a view — only the offset and strides change, the data buffer is shared, so it is nearly free even for huge arrays.
Boolean masking a[a > 0] returns a view into the original.
False. It returns a copy: the True elements are scattered irregularly and can't be described by one stride, so NumPy gathers them into fresh memory.
b = A[0]; b[0] = 99 leaves A unchanged.
False.
A[0] is a view (basic slicing), so b shares memory with A; setting b[0] also sets A[0,0]. Use A[0].copy() for independence.For a Python list, lst[1:3] and for a NumPy array, arr[1:3] behave the same way about copying.
False. A list slice makes a fresh copy; a NumPy slice makes a view. This mismatch is the single most common beginner bug.
a[[3,0,0,2]] can contain the same element more than once.
True. Fancy indexing reads whatever positions you list, in that order, so repeats like
0,0 legitimately duplicate an element in the result.A boolean mask on a 2-D array preserves its 2-D shape.
False. The selection always flattens to 1-D, because the chosen cells generally do not form a rectangle.
a[a > 0] = 0 is allowed and modifies a in place.
True. Even though reading through a mask makes a copy, assigning through a mask writes back to the True positions of the original array.
The result of a[idx] where idx is a 2×2 integer array is always 1-D.
False. Fancy indexing takes the shape of the index array, so a 2×2
idx yields a 2×2 result.A[np.ix_([0,2],[1,3])] and A[[0,2],[1,3]] return the same thing.
False.
np.ix_ builds the true 2×2 block of rows {0,2} × cols {1,3}; the plain form zips the indices pointwise into just A[0,1] and A[2,3].Using and between two array conditions works if you add parentheses.
False. Parentheses fix operator precedence but not the core issue:
and needs a single boolean and raises "ambiguous truth value" on an array. You need elementwise &.a[-1] and a[len(a)-1] select the same element.
True. Negative indices count from the end, so
-1 is exactly the last element, len(a)-1.Spot the error
A[0:2][1:3] to get rows 0–1 and columns 1–2 of a 2-D array.
Error: the second bracket slices rows of the already-sliced array, not columns. Use one bracket with a comma:
A[0:2, 1:3].a[(a < 0) & (a > -2)] written as a[a < 0 & a > -2].
Missing parentheses.
& binds tighter than <, so 0 & a evaluates first and the whole thing is wrong/errors. Wrap each comparison: (a < 0) & (a > -2).mask = a > 0; a[mask] where mask has a different length than a.
Error: a boolean mask must have the same shape as the axis it indexes (or be broadcastable), otherwise NumPy raises an index/shape error.
b = A[0] then editing b "because it's a copy of the row."
The reasoning is wrong:
A[0] is basic slicing → a view. To actually copy you need b = A[0].copy().A[[0,2],[1,3]] expecting the 4 corner values A[0,1],A[0,3],A[2,1],A[2,3].
Error in expectation: fancy index arrays are zipped pointwise, giving only
A[0,1] and A[2,3]. For all four use A[np.ix_([0,2],[1,3])].np.arange(10)[2:9:0] to step through the array.
Error: a step of
0 is illegal (it would never advance and asks for infinite elements); NumPy raises "slice step cannot be zero."Why questions
Why does basic slicing return a view instead of a copy?
Because a range of positions can be captured by just changing the offset and strides of the same buffer — no data needs moving, so a view is both correct and instant.
Why must boolean masking and fancy indexing return copies?
The selected elements are scattered: there's no single constant stride that walks them, so NumPy has to gather them into a fresh contiguous block.
Why do we use &, |, ~ instead of and, or, not?
&/|/~ are elementwise operators that produce a whole boolean array; and/or/not demand one truth value and can't decide on an array. (See Vectorization — replacing Python loops.)Why does a[a > 0] = 0 modify a even though a[a > 0] reads as a copy?
Assignment is a different operation: NumPy interprets the mask on the left side as "write to these True positions," so it targets the original storage directly.
Why does the result of a[idx] follow idx's shape rather than a's?
Each entry of
idx is a lookup that produces one value, so the output has one value per index slot — i.e. exactly the shape of idx.Why is A[0:2, 1:3] a rectangular block but A[[0,2],[1,3]] is not?
Slices describe independent ranges per axis (all row×col combos → a block); integer arrays are paired position-by-position (row[i] with col[i] → a diagonal-style pick).
Edge cases
What does a[5:2] return when start > stop and step > 0?
An empty array. The count formula clamped to gives elements.
What does a[::-1] do, and is it a view?
It reverses the array using
step = -1, and it is still a view — reversal is expressible as a negative stride, so no copy is made.What does an all-False mask a[a > 999] return?
An empty 1-D array (length 0) of the same dtype — no element passed the condition, so nothing is gathered.
What does an all-True mask a[a == a] return?
A flattened copy of every element in row-major order; even though all are selected, masking still flattens and copies.
What happens with an empty index list a[[]]?
You get an empty array of the same dtype; fancy indexing with zero positions selects nothing but is perfectly legal.
What does a[[10]] do on a length-5 array?
Raises an IndexError: fancy indexing bounds-checks each integer, and
10 is out of range (unlike slicing, which silently clamps).How does a slice like a[2:100] behave when stop exceeds the length?
It's clamped to the end of the array — slicing never raises for out-of-range stops, it just stops at the last element.
What is a[:] and what does it return?
A full-range slice → a view of the whole array (shares memory). To get an independent duplicate use
a.copy().Connections
- Indexing and slicing — basic, boolean masking, fancy indexing (index 5.4.3) (parent)
- Views vs Copies — memory model (the view/copy backbone of every trap here)
- NumPy arrays — shape, strides, dtype (why offset/strides make views free)
- Broadcasting (masks and index arrays broadcasting to shape)
- np.where and conditional selection
- Pandas .loc / .iloc indexing