5.4.3 · D4Scientific Computing (Python)

Exercises — Indexing and slicing — basic, boolean masking, fancy indexing

2,266 words10 min readBack to topic

Throughout, assume import numpy as np. Where an array is defined once, it carries to the next sub-part unless we redefine it.


Level 1 — Recognition

Goal: read a slice/mask/index and know what comes out, and whether it is a view or a copy.

Exercise 1.1 — Read a 1-D slice

Given a = np.arange(10) (so a = [0,1,2,3,4,5,6,7,8,9]), what is a[2:9:3], and how many elements does it have?

Recall Solution 1.1

WHAT we do: apply start=2, stop=9, step=3. Indices are (next would be , so we stop). Count from the formula: . Since a[i] == i here, the values equal the indices: View or copy? A view — a slice only changes offset/strides.

Exercise 1.2 — Negative step

For the same a, what is a[::-1]? What is a[8:2:-2]?

Recall Solution 1.2

a[::-1] means start=default-last, stop=default-before-first, step=-1 → the array reversed: a[8:2:-2]: start at index 8, walk down by 2, stop before index 2. Indices: (next is , excluded). Values: . Count (negative step): replace with . ✓ Both are views.

Exercise 1.3 — Classify each result

For each, say view or copy: (a) a[1:4], (b) a[a > 5], (c) a[[1,1,3]], (d) a[::2].

Recall Solution 1.3
  • (a) a[1:4] — basic slice → view.
  • (b) a[a > 5] — boolean mask → copy.
  • (c) a[[1,1,3]] — fancy (integer-list) indexing → copy.
  • (d) a[::2] — basic slice → view. Rule: Slices Stay (view); Masks & Fancy Make a copy.

Level 2 — Application

Goal: build masks and index arrays yourself and predict the output exactly.

Exercise 2.1 — Filter and count

Given x = np.array([3, -1, 4, -2, 5, -9, 0]). Give (a) the boolean mask x > 0, (b) x[x > 0], (c) how many elements survive.

Recall Solution 2.1

(a) Compare each element to 0, elementwise: (b) Keep positions where the mask is True, in order: . (c) Count of True = 3. (Note is False, so zero is dropped.)

Exercise 2.2 — Combining conditions

For the same x, compute x[(x < 0) & (x > -5)]. Then explain in one line why x[(x<0) and (x>-5)] errors.

Recall Solution 2.2

Two masks, then elementwise AND:

  • x < 0
  • x > -5 (only fails > -5)
  • & (elementwise AND) → Selecting True positions: values and Why and errors: Python's and wants a single True/False, but each side is a 7-element array; NumPy refuses to collapse it → ValueError: ambiguous truth value. Use & (elementwise) and wrap each side in parentheses because & binds tighter than <.

Exercise 2.3 — Reorder with fancy indexing

Given v = np.array([10, 20, 30, 40]), produce v[[3, 0, 0, 2]]. What is its length vs v's?

Recall Solution 2.3

Fancy indexing reads positions in the order given, repeats allowed:

  • position 3 → 40
  • position 0 → 10
  • position 0 → 10 (again!)
  • position 2 → 30 Length = 4 = length of the index array (which happens to be 4), even though it repeats element 0. The result shape follows the index array, not v.

Level 3 — Analysis

Goal: reason about 2-D shapes, the fancy-vs-block distinction, and view aliasing.

Let

Figure — Indexing and slicing — basic, boolean masking, fancy indexing

Exercise 3.1 — Block slice

What is A[0:2, 1:3]? What shape?

Recall Solution 3.1

First index 0:2 picks rows {0,1}; second index 1:3 picks cols {1,2}. The result is their rectangular intersection — the yellow block in the figure: Both indices are ranges → result stays 2-D.

Exercise 3.2 — Fancy pairs vs block

What is A[[0,2],[1,3]]? Contrast with the block interpretation and give the shape.

Recall Solution 3.2

Fancy index arrays are zipped pointwise, not crossed. Pair them up:

  • element 0: row 0, col 1 → A[0,1] = 1
  • element 1: row 2, col 3 → A[2,3] = 11 This is the magenta diagonal pick in the figure — a 1-D result, not the 2×2 block of rows {0,2} × cols {1,3}.

Exercise 3.3 — Make it an actual block

Using rows {0,2} and cols {1,3}, produce the true 2×2 block. What is it?

Recall Solution 3.3

Use np.ix_ to cross the index lists (it reshapes them so they broadcast into a grid):

A[np.ix_([0,2],[1,3])]

Rows {0,2} × cols {1,3} =

Exercise 3.4 — View aliasing

Run:

b = A[0]        # first row
b[0] = 99

What is A[0,0] now, and why?

Recall Solution 3.4

A[0] is a view onto row 0 (basic indexing, shares memory). Writing b[0] = 99 writes into the same buffer, so: To edit independently: b = A[0].copy().


Level 4 — Synthesis

Goal: chain the three methods and mutate arrays in place.

Exercise 4.1 — Clip negatives in place

Given y = np.array([5, -3, 8, -1, 0, -7]), set every negative element to 0 in place, then report y.

Recall Solution 4.1

Boolean assignment: the mask on the left, a scalar on the right — the scalar broadcasts into every True slot:

y[y < 0] = 0

Mask y < 0 = → positions 1,3,5 become 0:

Exercise 4.2 — Top-3 by fancy indexing

Given s = np.array([7, 2, 9, 4, 6]), return its three largest values, largest first, using an index array.

Recall Solution 4.2

Step 1 (WHAT): np.argsort(s) returns indices that sort ascending: s = [7,2,9,4,6] → argsort [1,3,4,0,2] (values ). Step 2 (WHY reverse): we want largest first, so reverse and take the first three indices: np.argsort(s)[::-1][:3] = [2,0,4]. Step 3: fancy-index with those positions:

s[np.argsort(s)[::-1][:3]]

Positions 2,0,4 → values :

Exercise 4.3 — Combine mask then reorder

Given w = np.array([3, -2, 8, -5, 1, 6]): keep only the positive values, then return them sorted descending.

Recall Solution 4.3

Step 1 — mask (copy): pos = w[w > 0]. Step 2 — sort descending: np.sort is ascending, so reverse it: np.sort(pos)[::-1]. np.sort([3,8,1,6]) = [1,3,6,8]; reversed → .


Level 5 — Mastery

Goal: predict subtle shape/copy behaviour and design the correct tool.

Exercise 5.1 — Shape follows the index array

Given a = np.array([10,20,30,40]) and idx = np.array([[0,1],[2,3]]), what is a[idx] and its shape?

Recall Solution 5.1

Fancy indexing gives the result the shape of the index array, filling each slot with a[that index]: A 1-D source became a 2-D result — driven entirely by idx's shape.

Exercise 5.2 — Boolean mask flattens

Given A from Level 3, what is A[A % 2 == 0] and its shape?

Recall Solution 5.2

The mask A % 2 == 0 marks the even cells. Even values in row-major order across are : Always 1-D: the True cells don't form a rectangle, so NumPy cannot keep 2-D structure — it gathers them into a flat copy.

Exercise 5.3 — Fancy write with repeats

Given c = np.zeros(5, dtype=int), run c[[0, 1, 1, 2]] = [10, 20, 30, 40]. What is c?

Recall Solution 5.3

Assignment walks the index/value pairs left-to-right:

  • c[0] = 10
  • c[1] = 20
  • c[1] = 30overwrites the previous 20
  • c[2] = 40 Key insight: with a repeated index, the last write wins — no accumulation. (For accumulation you'd need np.add.at(c, [0,1,1,2], [10,20,30,40]), which would give c[1]=50.)

Exercise 5.4 — Choose the tool

You have M = np.arange(20).reshape(4,5) and want the sub-matrix at rows {0,3} and columns {1,4} as a 2×2 block. Write the expression and give the result.

Recall Solution 5.4

Two separate index lists zipped pointwise would give only M[0,1], M[3,4] (a 1-D diagonal). For the Cartesian block, use np.ix_:

M[np.ix_([0,3],[1,4])]

With :


Recall Self-test summary (reveal after all exercises)

Slice count a[2:9:3] ::: 3 elements → indices 2,5,8 a[8:2:-2] ::: [8,6,4] (stop excluded, downward) x[(x<0) & (x>-5)] for x=[3,-1,4,-2,5,-9,0] ::: [-1,-2] v[[3,0,0,2]] for v=[10,20,30,40] ::: [40,10,10,30] A[[0,2],[1,3]] ::: [1,11], shape (2,) — pointwise pairs A[np.ix_([0,2],[1,3])] ::: 1,3,[9,11]], the true block A[A%2==0] ::: [0,2,4,6,8,10], flattened to 1-D c[[0,1,1,2]] = [10,20,30,40] ::: [10,30,40,0,0] — last write wins


Connections

  • Views vs Copies — memory model (Exercises 1.3, 3.4)
  • Broadcasting (scalar-into-mask assignment, 4.1)
  • np.where and conditional selection (alternative to masked assignment)
  • Vectorization — replacing Python loops (why we never loop these)
  • NumPy arrays — shape, strides, dtype (why slices are cheap views)
  • Pandas .loc / .iloc indexing (same ideas at DataFrame level)