NumPy where(): Conditional Values and Matching Positions

np.where(condition, x, y) chooses values from x where a condition is True and from y elsewhere. With only the condition, it returns matching index arrays instead. Learn the difference before filtering, replacing missing values or assigning conditional columns.

FormResult
np.where(condition, x, y)Chosen values; all inputs must broadcast to a common shape
np.where(condition)Tuple of matching index arrays; prefer nonzero directly for position finding
array[mask]Only matching values, using Boolean indexing

Choose Values without Mutating the Source

With three arguments, np.where(condition, x, y) chooses x for True and y for False. It returns an array rather than changing the input. Reassigning the variable only changes which result that variable refers to.

import numpy as np
import pandas as pd

ar = np.arange(6)
result = np.where(ar % 2 == 0, ar * 3, ar)
print("Original:", ar)
print("Chosen values:", result)
np.testing.assert_array_equal(result, [0, 1, 6, 3, 12, 5])
np.testing.assert_array_equal(ar, np.arange(6))

Expected output

Original: [0 1 2 3 4 5]
Chosen values: [ 0  1  6  3 12  5]

Replace Selected Values with np.nan

Use np.nan for a floating missing value. Mixing it with integer values produces a floating result. Here zero is also divisible by five, so its position becomes missing. See NaN and dtypes.

ar = np.arange(15)
result = np.where(ar % 5 == 0, np.nan, ar)
print(result)
print("dtype:", result.dtype)
np.testing.assert_array_equal(np.flatnonzero(np.isnan(result)), [0, 5, 10])
assert result.dtype.kind == 'f' 

Expected output

[nan  1.  2.  3.  4. nan  6.  7.  8.  9. nan 11. 12. 13. 14.]
dtype: float64

One Argument Returns Positions, Not Values

With only the condition, where returns a tuple containing one index array per dimension. For a 1D input, access its index array with positions[0]. Use np.nonzero() directly for finding nonzero positions, particularly with array subclasses.

ar = np.array([12, 2, 7, 1, 9, 3, 11])
positions = np.where(ar > 5)
print("Index tuple:", positions)
print("Index array:", positions[0])
print("Matching values:", ar[positions])
np.testing.assert_array_equal(positions[0], [0, 2, 4, 6])
np.testing.assert_array_equal(positions[0], np.nonzero(ar > 5)[0])

Expected output

Index tuple: (array([0, 2, 4, 6]),)
Index array: [0 2 4 6]
Matching values: [12  7  9 11]

Combine Conditions with & and Parentheses

Use elementwise & for AND. Parenthesize each comparison so operator precedence matches the intended logic. Python and tries to evaluate the truth value of the entire array and is unsuitable here.

ar = np.array([12, 2, 7, 1, 9, 3, 11])
mask = (ar > 5) & (ar < 10)
positions = np.where(mask)
print("Positions:", positions)
print("Second matching position:", positions[0][1])
print("Matching values:", ar[mask])
np.testing.assert_array_equal(positions[0], [2, 4])

Expected output

Positions: (array([2, 4]),)
Second matching position: 4
Matching values: [7 9]

Use | for OR and ~ for NOT

Each condition produces a Boolean mask. OR includes a value when either condition is True; NOT reverses a Boolean mask. Keep the original values available while inspecting positions.

ar = np.array([12, 2, 7, 1, 4, 3, 11])
mask = (ar > 5) | (ar % 2 == 0)
print("OR positions:", np.where(mask))
print("NOT values:", ar[~mask])
np.testing.assert_array_equal(np.where(mask)[0], [0, 1, 2, 4, 6])

Expected output

OR positions: (array([0, 1, 2, 4, 6]),)
NOT values: [1 3]

Choose between Two 2D Arrays

At each position, the condition chooses one value from the corresponding input. The output preserves the common broadcast shape. For this example all three inputs have shape (2, 2).

condition = np.array([[True, False], [True, True]])
x = np.array([[5, 2], [13, 42]])
y = np.array([[9, 18], [73, 16]])
result = np.where(condition, x, y)
print(result)
np.testing.assert_array_equal(result, [[5, 18], [13, 42]])

Expected output

[[ 5 18]
 [13 42]]

Read 2D Positions as Row and Column Pairs

The coordinate arrays describe paired positions, not a rectangular row-by-column selection. For indexing, use the tuple from nonzero or where. argwhere provides pairs that are convenient to display. See indexing and slicing.

a = np.array([[2, 8, 1], [9, 3, 7]])
positions = np.where(a > 5)
print("Rows:", positions[0])
print("Columns:", positions[1])
print("Coordinate pairs:")
print(np.argwhere(a > 5))
print("Values:", a[positions])
np.testing.assert_array_equal(a[positions], [8, 9, 7])

Expected output

Rows: [0 1 1]
Columns: [1 0 2]
Coordinate pairs:
[[0 1]
 [1 0]
 [1 2]]
Values: [8 9 7]

Broadcast a Row Condition across Columns

condition, x and y must have compatible shapes. A row-level condition should have shape (rows, 1) to expand across columns. A scalar replacement broadcasts to the whole result. Review broadcasting and shape errors.

a = np.arange(12).reshape(3, 4)
keep_rows = np.array([True, False, True])[:, None]
result = np.where(keep_rows, a, -1)
print(result)
assert result.shape == (3, 4)
np.testing.assert_array_equal(result[1], [-1, -1, -1, -1])

Expected output

[[ 0  1  2  3]
 [-1 -1 -1 -1]
 [ 8  9 10 11]]

where Replacement versus Boolean Filtering

Three-argument where retains a broadcast-shaped result by filling rejected positions. Boolean indexing selects only matching elements. Use filtering when you want fewer records, and replacement when positions must remain aligned.

a = np.array([12, 2, 7, 1, 9])
mask = a > 5
replaced = np.where(mask, a, -1)
filtered = a[mask]
print("Replacement:", replaced)
print("Filter:", filtered)
assert replaced.shape == (5,) and filtered.shape == (3,)

Expected output

Replacement: [12 -1  7 -1  9]
Filter: [12  7  9]

Both Branches Determine the Output dtype

Even when a condition selects only one branch, the two branch dtypes influence the output type. Use compatible numeric replacements to keep numeric data numeric. A textual status is usually better stored separately. See dtype.

a = np.array([2, -1, 5])
numeric = np.where(a >= 0, a, np.nan)
status = np.where(a >= 0, 'Valid', 'Review')
print("Numeric:", numeric, numeric.dtype)
print("Status:", status)
assert numeric.dtype.kind == 'f'
assert np.isnan(numeric[1])

Expected output

Numeric: [ 2. nan  5.] float64
Status: ['Valid' 'Review' 'Valid']

where Does Not Skip Evaluation of Its Branches

Python evaluates both branch expressions before calling where. Therefore a division inside a branch can still divide by zero. For guarded division, initialize an output and use the ufunc where argument of np.divide(). It is a different API from np.where.

numerator = np.array([10.0, 20.0, 30.0])
denominator = np.array([2.0, 0.0, 5.0])
try:
    with np.errstate(divide='raise', invalid='raise'):
        np.where(denominator != 0, numerator / denominator, np.nan)
except FloatingPointError:
    print("The division branch was evaluated before selection.")
else:
    raise AssertionError('Expected a division failure')
safe = np.full(numerator.shape, np.nan)
np.divide(numerator, denominator, out=safe, where=denominator != 0)
print("Guarded result:", safe)
np.testing.assert_allclose(safe, [5, np.nan, 6], equal_nan=True)

Expected output

The division branch was evaluated before selection.
Guarded result: [ 5. nan  6.]

Nested where: Department Allowances

Nested conditions assign 50 for marketing, 65 for production and 45 for planning. Unknown departments retain NaN rather than receiving a guessed allowance. This follows the department mapping used in the Pandas exercise.

my_data = pd.DataFrame({'dept': ['mktg', 'production', 'planning', 'unknown']})
my_data['allowed'] = np.where(my_data['dept'] == 'mktg', 50,
    np.where(my_data['dept'] == 'production', 65,
        np.where(my_data['dept'] == 'planning', 45, np.nan)))
print(my_data)
np.testing.assert_allclose(my_data['allowed'], [50, 65, 45, np.nan], equal_nan=True)

Expected output

         dept  allowed
0        mktg     50.0
1  production     65.0
2    planning     45.0
3     unknown      NaN

Pandas Pass or Fail: Keep Missing Marks for Review

These embedded synthetic students make practice independent of a local CSV path. A mark of 60 passes. Missing or out-of-range marks are flagged for review before classification, rather than accidentally treated as failures. See student sample data.

df = pd.DataFrame({'name': ['Alex', 'Ron', 'Ravi', 'King', 'Mira', 'Sam'],
                   'mark': [60.0, 80.0, 49.0, 70.0, np.nan, 105.0]})
valid = df['mark'].between(0, 100) & np.isfinite(df['mark'])
df['status'] = np.where(valid, np.where(df['mark'] >= 60, 'Pass', 'Fail'), 'Review')
print(df)
assert df['status'].tolist() == ['Pass', 'Pass', 'Fail', 'Pass', 'Review', 'Review']

Expected output

   name   mark  status
0  Alex   60.0    Pass
1   Ron   80.0    Pass
2  Ravi   49.0    Fail
3  King   70.0    Pass
4  Mira    NaN  Review
5   Sam  105.0  Review

Nested Grades and Boundary Checks

Use the stated grade rule: A from 80, B from 70, C from 50 and D below 50. This is separate from the pass threshold of 60. Check the boundaries, and retain Review for invalid marks.

marks = np.array([49, 50, 69, 70, 79, 80, 100, np.nan])
valid = np.isfinite(marks) & (marks >= 0) & (marks <= 100)
grade = np.where(valid, np.where(marks >= 80, 'A',
    np.where(marks >= 70, 'B', np.where(marks >= 50, 'C', 'D'))), 'Review')
print("Marks:", marks)
print("Grades:", grade)
assert grade.tolist() == ['D', 'C', 'C', 'B', 'B', 'A', 'A', 'Review']

Expected output

Marks: [ 49.  50.  69.  70.  79.  80. 100.  nan]
Grades: ['D' 'C' 'C' 'B' 'B' 'A' 'A' 'Review']

Many Conditions: Consider select or a Mapping

For several ordered thresholds, np.select can be easier to read than deeply nested where. The first True condition wins, so put higher thresholds first. For exact department labels, a mapping can be simpler. No selection API removes the need to handle missing values.

marks = np.array([49, 50, 70, 80, np.nan])
valid = np.isfinite(marks) & (marks >= 0) & (marks <= 100)
grade = np.select([~valid, marks >= 80, marks >= 70, marks >= 50],
                  ['Review', 'A', 'B', 'C'], default='D')
print(grade)
assert grade.tolist() == ['D', 'C', 'B', 'A', 'Review']

Expected output

['D' 'C' 'B' 'A' 'Review']

Sort Two Subjects while Keeping Their Marks Together

This synthetic student exercise orders subjects alphabetically for each row. Compute one swap mask, then use separate where calls for text and marks. Keep the original columns until every new value is calculated; mixing text and numbers in one branch bundle can cause dtype coercion. Names are nonmissing and consistently capitalized here.

df = pd.DataFrame({'Name': ['Alex', 'Ron', 'Ravi', 'King'],
    'Subject_1': ['Science', 'Social', 'History', 'English'],
    'Mark_1': [30, 90, 10, 100],
    'Subject_2': ['Chemistry', 'Math', 'Physics', 'Geography'],
    'Mark_2': [40, 80, 60, 90]})
before = df.copy()
swap = df['Subject_1'] > df['Subject_2']
result = df.assign(
    Subject_1=np.where(swap, df['Subject_2'], df['Subject_1']),
    Mark_1=np.where(swap, df['Mark_2'], df['Mark_1']),
    Subject_2=np.where(swap, df['Subject_1'], df['Subject_2']),
    Mark_2=np.where(swap, df['Mark_1'], df['Mark_2']))
print("Input:")
print(before)
print("Ordered:")
print(result)
assert result['Subject_1'].tolist() == ['Chemistry', 'Math', 'History', 'English']
assert result['Mark_1'].tolist() == [40, 80, 10, 100]
for i in df.index:
    original = sorted([(df.loc[i, 'Subject_1'], df.loc[i, 'Mark_1']), (df.loc[i, 'Subject_2'], df.loc[i, 'Mark_2'])])
    reordered = [(result.loc[i, 'Subject_1'], result.loc[i, 'Mark_1']), (result.loc[i, 'Subject_2'], result.loc[i, 'Mark_2'])]
    assert original == reordered
pd.testing.assert_frame_equal(df, before)

Expected output

Input:
   Name Subject_1  Mark_1  Subject_2  Mark_2
0  Alex   Science      30  Chemistry      40
1   Ron    Social      90       Math      80
2  Ravi   History      10    Physics      60
3  King   English     100  Geography      90
Ordered:
   Name  Subject_1  Mark_1  Subject_2  Mark_2
0  Alex  Chemistry      40    Science      30
1   Ron       Math      80     Social      90
2  Ravi    History      10    Physics      60
3  King    English     100  Geography      90

Practical Sales Rule: Discount Only Valid Orders

This synthetic example gives a 10 percent discount when quantity is at least five. Missing or invalid quantity/price values remain unknown. The rule checks whole-number nonnegative quantities and finite nonnegative prices. Keep review counts alongside known subtotals.

quantity = np.array([2.0, 5.0, np.nan, -1.0, 6.0, 0.0])
price = np.array([10.0, 20.0, 10.0, 12.0, np.nan, 8.0])
valid = (np.isfinite(quantity) & (quantity >= 0) & (quantity == np.trunc(quantity))
         & np.isfinite(price) & (price >= 0))
discount = np.where(quantity >= 5, 0.10, 0.0)
net = np.where(valid, quantity * price * (1 - discount), np.nan)
print("Net amounts:", net)
print("Known subtotal:", np.nansum(net))
print("Orders needing review:", np.count_nonzero(~valid))
np.testing.assert_allclose(net, [20, 90, np.nan, np.nan, np.nan, 0], equal_nan=True)
assert np.nansum(net) == 110 and (~valid).sum() == 3

Expected output

Net amounts: [20. 90. nan nan nan  0.]
Known subtotal: 110.0
Orders needing review: 3

Common where Mistakes

One-argument where returns coordinates, not filtered values. Three-argument where selects a new array rather than mutating the source. Use & and | with parenthesized comparisons. Check broadcasting and output dtype. Keep missing values separate from known zero. Use guarded ufunc operations when a branch calculation is invalid for some inputs. NumPy results assigned to Pandas columns are positional; verify row order and length before assignment.

Exercise: Flag Stock for Reordering

For stock [0, 4, 5, 8, NaN, -1], classify nonnegative finite whole counts below five as Reorder and the others as Enough. Invalid counts receive Review. Predict positions 0 and 1 as reorder candidates. Return both status labels and reorder positions.

Exercise Solution

Build one validity mask and reuse it for classification and positions. This prevents missing or negative stock from being treated as a valid reorder measurement.

stock = np.array([0.0, 4.0, 5.0, 8.0, np.nan, -1.0])
valid = np.isfinite(stock) & (stock >= 0) & (stock == np.trunc(stock))
status = np.where(valid, np.where(stock < 5, 'Reorder', 'Enough'), 'Review')
positions = np.nonzero(valid & (stock < 5))[0]
print("Status:", status)
print("Reorder positions:", positions)
assert status.tolist() == ['Reorder', 'Reorder', 'Enough', 'Enough', 'Review', 'Review']
np.testing.assert_array_equal(positions, [0, 1])

Expected output

Status: ['Reorder' 'Reorder' 'Enough' 'Enough' 'Review' 'Review']
Reorder positions: [0 1]

Practice in Google Colab

Open in Google Colab View on GitHub
All sample data is embedded. Run the examples and practise conditional replacement, indices and data validation. Save a copy in Drive to keep your changes.

Continue with NumPy tutorials, arange(), eye(), ones(), bincount() and linspace(). Reference: NumPy where documentation.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer