Indexing and Vectorization
Select and transform whole arrays at once
Fancy indexing, boolean masks and np.where replace row-scavenging Python code with array-native logic — the difference between a 40-line loop and a 4-line pipeline.
What you will learn
- Index with arrays, lists and boolean masks
- Apply conditional selection with np.where/np.select
- Replace Python loops with vectorized expressions
What is this?
Fancy indexing selects rows by an array of positions or booleans: mat[mask] keeps the True rows, mat[idx] gathers listed indices. np.where(cond, a, b) picks element-wise between two arrays, and np.select stacks multi-way rules. Together they encode 'if/else at scale' without a single Python loop.
Why do you need it?
- Conditions on 10M rows are one expression, not a loop-through-Python.
- Boolean logic composes into readable rules: (arr > t) & (arr < u).
- Vectorized where/select make rule engines concise and auditable.
- It is the syntax behind every pandas boolean mask.
Mental model
A security guard with a checklist scanning a crowd
Boolean masking
The guard marks each person in or out by the checklist — one pass, no per-person report. The mask is the checklist applied to every queue member simultaneously; np.where then routes each person down the 'admits' or 'denies' corridor.
How it works
- mat[idx] with an integer array gathers rows in that order.
- mat[mask] where mask is a same-length boolean array keeps True elements.
- np.where(cond, then_array, else_array) chooses element-wise; scalar inputs broadcast.
- np.select([c1, c2], [v1, v2], default=...) handles multiple ordered rules.
- Boolean ops &, |, ~ compose; comparisons return boolean masks as whole arrays.
- Where pandas would loop a column, numpy's np.where/np.select usually wins on speed.
Mask → select → assign.
Examples
Rule engine in three lines
import numpy as npusage = np.array([5, 95, 12, 300, 40])seg = np.select( [usage >= 90, usage < 15], ["upsell", "at-risk"], default="standard",)print(seg) # standard handled by defaultprint(usage[usage >= 90]) # fancy/mask selectionnp.select applies ordered rules without a loop; a boolean mask gathers the high-usage values directly.
In production
- A support triage vectorizes SLA breach flags with np.where on response-time arrays.
- A pricing engine applies tiered discounts with np.select across the whole price vector.
- A monitoring system masks anomaly windows via boolean selections feeding alarms in bulk.
Best practices
- Compose masks with & | ~, not Python's and/or.
- Put the most specific rule first in np.select — order decides winners.
- Operate on arrays; convert lists to arrays once at the boundary.
- For pandas columns, keep expressions in numpy space and assign results back once.
Common mistakes
- Using Python and/or on arrays — ambiguity errors.
- forgetting parens when combining masks with comparisons.
- Applying np.where(cond, a, b) with different-shaped a/b and being surprised by broadcasting.
- Treating np.where like a branch that short-circuits — both operands are evaluated first.
Debugging tips
- A mask that selects nothing → print the mask.sum() and the raw comparison values.
- np.select with overlapping rules: verify the first-wins order with boundary samples.
- Unexpected dtype after where (e.g., all strings because one branch is) — mention dtype=.
- When a loop version and vectorized version disagree, shrink to 10 rows and diff stepwise.
Interview checkpoint
Quick quiz
arr[arr > 5] returns:
np.select evaluates all rules but returns on the first match.