Q&A
NumPy where() method
Overview
np.where solves two related but distinct problems with one function: conditional selection across arrays (the three-argument form) and index lookup (the one-argument form). The three-argument form is the most common - it's the NumPy way to say "pick from A where condition is True, else from B," done over whole arrays at once without any loop.
graph TD
A["np.where(condition, x, y)"] --> B["Vectorized ternary:<br/>True positions → x, False → y"]
C["np.where(condition)"] --> D["Returns tuple of index arrays<br/>where condition is True"]
style A fill:#1e1b4b,color:#fff
Three-Argument Form: Conditional Selection
import numpy as np
arr = np.array([10, -5, 20, -3, 7])
result = np.where(arr > 0, arr, 0) # keep positives, zero out negatives
print(result) # [10 0 20 0 7]
# Equivalent Python loop (don't write this in NumPy code):
# [arr[i] if arr[i] > 0 else 0 for i in range(len(arr))]
x and y can be scalars, arrays, or broadcast-compatible combinations:
a = np.array([1, 2, 3, 4, 5])
b = np.array([10, 20, 30, 40, 50])
mask = np.array([True, False, True, False, True])
np.where(mask, a, b) # [1 20 3 40 5] - a where True, b where False
Replace values conditionally
grades = np.array([45, 72, 88, 31, 95])
labels = np.where(grades >= 60, "pass", "fail")
# ['fail' 'pass' 'pass' 'fail' 'pass']
Multiple conditions with np.select
For more than two categories, chain np.where or use np.select:
conditions = [grades >= 90, grades >= 70, grades >= 50]
choices = ["A", "B", "C"]
np.select(conditions, choices, default="D")
# ['D' 'B' 'A' 'D' 'A']
One-Argument Form: Finding Indices
np.where(condition) returns a tuple of arrays (one per dimension) of the indices where the condition is True:
arr = np.array([1, -2, 3, -4, 5])
indices = np.where(arr < 0)
print(indices) # (array([1, 3]),) - tuple with one array for 1-D
print(arr[indices]) # [-2 -4] - index back into the array
For 2-D arrays, the result tuple has two arrays (row indices, column indices):
m = np.array([[1, -2], [3, -4]])
rows, cols = np.where(m < 0)
# rows = [0, 1], cols = [1, 1] - positions (0,1) and (1,1)
Comparison with Boolean Masking
Boolean masking (arr[arr > 0]) is typically cleaner for filtering, but np.where shines when you want to replace values rather than select them:
arr[arr < 0] = 0 # boolean mask assignment - in-place
result = np.where(arr < 0, 0, arr) # same result, returns new array
Broadcasting
x and y broadcast normally with each other and with the condition:
matrix = np.arange(9).reshape(3, 3)
np.where(matrix % 2 == 0, matrix, -1)
# even values kept; odd values become -1
Performance
np.where executes in compiled C over contiguous arrays - typically 10–100× faster than an equivalent Python loop or list comprehension on large arrays. For ML and data processing workloads (replacing values, creating label arrays, masking anomalies), it's the standard tool.
Why This Matters in Interviews
Data-science/NumPy interviews use np.where to test vectorized thinking. Key points: the three-argument form is a vectorized ternary (avoid Python loops for this); the one-argument form returns indices (like nonzero); both operate over the whole array without explicit iteration; broadcasting applies to x and y; and np.select generalizes to multiple conditions. Showing you'd reach for np.where rather than a list comprehension for array conditionals signals NumPy fluency.
Common Pitfalls
- Confusing the one- and three-argument forms (one returns indices, three returns values).
- The one-argument form returns a tuple - index with
arr[np.where(cond)]or unpackrows, cols = np.where(cond2d). - Both
xandyare always evaluated beforewhereruns (no short-circuit), so expensive function calls in either branch still execute for all elements. - Chaining
np.wherefor multiple conditions -np.selectis cleaner.