Pandas mask(): Replace Values by Condition

Use mask() to replace values where a condition is True. Choose whether to change one column, selected cells or whole rows, and keep invalid source values available for review.

Replace values where the condition is True 🔝

mask(cond, other) replaces values where cond is True and preserves them where it is False. It keeps the shape; it does not filter rows. For row selection, use loc. Use where() when the condition describes values to keep. Current arguments include cond, other, inplace, axis and level; omit obsolete errors and try_cast arguments.

Replace cells in one column 🔝

Start with the student table. Assign the returned Series back to MATH to change only that column. The score 80 is preserved because the comparison is strictly greater than 80.

import pandas as pd
def student_data():
    return pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex", "Ron", "King", "Jack"], "ID": [1, 2, 3, 4, 5, 6], "MATH": [80, 40, 70, 70, 82, 30], "ENGLISH": [81, 70, 40, 50, 60, 30]})
df = student_data()
df["MATH"] = df["MATH"].mask(df["MATH"].gt(80), -5)
print(df)
assert df["MATH"].tolist() == [80, 40, 70, 70, -5, 30]
assert df.loc[4, "NAME"] == "King"

Expected output

   NAME  ID  MATH  ENGLISH
0  Ravi   1    80       81
1  Raju   2    40       70
2  Alex   3    70       40
3   Ron   4    70       50
4  King   5    -5       60
5  Jack   6    30       30

Apply a row condition to a whole DataFrame 🔝

A row condition can affect every column. Use a compatible replacement for each column so a string column does not receive a numeric value. Replacing a whole row also changes its ID, so choose this deliberately. For scores alone, target a Series or numeric subset.

df = student_data()
replacement = pd.DataFrame({"NAME": ["review"] * len(df), "ID": [-5] * len(df), "MATH": [-5] * len(df), "ENGLISH": [-5] * len(df)}, index=df.index)
result = df.mask(df["MATH"].gt(80), replacement)
print(result)
assert result.loc[4].tolist() == ["review", -5, -5, -5]
assert df.loc[4, "NAME"] == "King"

Expected output

     NAME  ID  MATH  ENGLISH
0    Ravi   1    80       81
1    Raju   2    40       70
2    Alex   3    70       40
3     Ron   4    70       50
4  review  -5    -5       -5
5    Jack   6    30       30

Combine conditions with parentheses 🔝

Use & for AND and | for OR, with parentheses around comparisons. This fresh table gives Alex a score of 73. Assign the result back to the column; do not rely on df["MATH"].mask(..., inplace=True) to update its parent DataFrame.

df = student_data()
df.loc[2, "MATH"] = 73
condition = (df["MATH"] > 70) & (df["MATH"] < 75)
df["MATH"] = df["MATH"].mask(condition, -7)
print(df)
assert df["MATH"].tolist() == [80, 40, -7, 70, 82, 30]

Expected output

   NAME  ID  MATH  ENGLISH
0  Ravi   1    80       81
1  Raju   2    40       70
2  Alex   3    -7       40
3   Ron   4    70       50
4  King   5    82       60
5  Jack   6    30       30

Mark invalid scores as missing 🔝

Omitting other introduces a missing value. A nullable integer column keeps integer values and uses pd.NA. Ordinary NumPy integer columns may become floating point when a missing value is introduced. A missing score should not automatically become zero.

scores = pd.Series([80, -1, 105, 70], dtype="Int64", name="score")
invalid = scores.lt(0) | scores.gt(100)
cleaned = scores.mask(invalid)
print(cleaned)
print("dtype:", cleaned.dtype)
assert str(cleaned.dtype) == "Int64"
assert cleaned.isna().tolist() == [False, True, True, False]

Expected output

0      80
1    <NA>
2    <NA>
3      70
Name: score, dtype: Int64
dtype: Int64

Compare mask() and where() 🔝

For a complete Boolean condition, mask(invalid) and where(~invalid) produce the same result. Define whether the condition means replace or keep before choosing a method.

values = pd.Series([10, -2, 0, 8], dtype="Int64")
invalid = values.lt(0)
masked = values.mask(invalid, 0)
kept = values.where(~invalid, 0)
print(masked)
assert masked.equals(kept)
assert masked.tolist() == [10, 0, 0, 8]

Expected output

0    10
1     0
2     0
3     8
dtype: Int64

Align replacement values by label 🔝

A replacement Series aligns by index labels, not its displayed order. axis="index" makes row alignment explicit for a DataFrame. Provide replacements for every affected label; missing replacement labels can introduce missing values.

df = pd.DataFrame({"amount": [10, -2, -3]}, index=["A", "B", "C"])
replacement = pd.Series([300, 200, 100], index=["C", "B", "A"])
result = df.mask(df.lt(0), replacement, axis="index")
print(result)
assert result["amount"].tolist() == [10, 200, 300]

Expected output

   amount
A      10
B     200
C     300

Use a callable condition 🔝

A callable receives the object being masked. Keep it free of side effects. If the replacement depends on the data, other can also be a callable.

values = pd.Series([1, 20, 3], dtype="Int64")
result = values.mask(lambda s: s.gt(10), lambda s: s.clip(upper=10))
print(result)
assert result.tolist() == [1, 10, 3]

Expected output

0     1
1    10
2     3
dtype: Int64

Use inplace only on the intended object 🔝

inplace=True changes the object on which mask is called. Use a standalone Series for this demonstration and inspect that object after the call. For a DataFrame column, explicit assignment is clearer and works with Pandas 3 Copy-on-Write.

scores = pd.Series([80, 40, 70, 70, 82, 30], name="MATH")
condition = scores.gt(80)
scores.mask(condition, -5, inplace=True)
print(scores)
assert scores.iloc[4] == -5

Expected output

0    80
1    40
2    70
3    70
4    -5
5    30
Name: MATH, dtype: int64

Clean a synthetic sales extract and keep review rows 🔝

The rule for this example is a non-negative numeric amount and a non-blank product. Preserve raw values, turn invalid amounts into missing values, and retain rejected rows for inspection. Trimming blank products uses string inspection; missing-value checks identify review rows.

raw = pd.DataFrame({"order_id": [101, 102, 103, 104, 105], "product": [" Pen ", "Book", "Notebook", " ", "Pencil"], "amount": ["5.50", "-3", "bad", "8", "0"]})
cleaned = raw.copy()
amount = pd.to_numeric(raw["amount"], errors="coerce").astype("Float64")
# A missing comparison is not evidence of a negative amount.
negative = amount.lt(0).fillna(False)
cleaned["amount"] = amount.mask(negative)
product = raw["product"].astype("string").str.strip()
cleaned["product"] = product.mask(product.eq(""))
needs_review = cleaned[["product", "amount"]].isna().any(axis=1)
accepted = cleaned.loc[~needs_review].copy()
review = raw.loc[needs_review].copy()
print("Accepted:")
print(accepted)
print("Review original values:")
print(review)
assert accepted["order_id"].tolist() == [101, 105]
assert review["order_id"].tolist() == [102, 103, 104]
assert accepted["amount"].sum() == 5.5
assert raw.loc[1, "amount"] == "-3"

Expected output

Accepted:
   order_id product  amount
0       101     Pen     5.5
4       105  Pencil     0.0
Review original values:
   order_id   product amount
1       102      Book     -3
2       103  Notebook    bad
3       104                8

Count reasons before exporting 🔝

Reason counts can overlap; use the combined row mask for the total number needing review. A zero amount is allowed by this sample rule. For export options, follow to_excel() and Input and Output.

print("Negative amount:", int(negative.sum()))
print("Unparseable or missing amount:", int(amount.isna().sum()))
print("Blank or missing product:", int(cleaned["product"].isna().sum()))
print("Review rows:", int(needs_review.sum()))
print(accepted.to_csv(index=False).rstrip())
# Optional Colab export:
# accepted.to_csv("accepted_sales.csv", index=False)

Expected output

Negative amount: 1
Unparseable or missing amount: 1
Blank or missing product: 1
Review rows: 3
order_id,product,amount
101,Pen,5.5
105,Pencil,0.0

Common questions 🔝

Does mask() remove rows? No; it replaces values. Why did every column change? A row condition was applied to the whole DataFrame. Can I replace text with a number? The replacement must be compatible with the target dtype; prefer a text marker or missing value for text. How should missing conditions behave? Set an explicit policy, such as fillna(False), when unknown should mean do not replace. Why did inplace on a selected column not update the table? Assign the returned Series back to the DataFrame. When should I use loc? Use it to set particular columns or select rows. Should invalid amounts become zero? Only if your data rules establish that they represent zero; otherwise keep them missing for review.

Exercise: hide out-of-range temperatures 🔝

For temperatures 18, -99, 22 and 85, define valid readings as -20 through 60 inclusive. Keep the raw Series, replace invalid readings with missing values and confirm that two readings remain.

Exercise solution 🔝

The nullable Float64 dtype supports missing readings while preserving numeric values.

raw_temperature = pd.Series([18, -99, 22, 85], dtype="Float64")
invalid = raw_temperature.lt(-20) | raw_temperature.gt(60)
answer = raw_temperature.mask(invalid)
print(answer)
print("Valid readings:", answer.count())
assert answer.count() == 2
assert answer.dropna().tolist() == [18, 22]
assert raw_temperature.tolist() == [18, -99, 22, 85]

Expected output

0    18.0
1    <NA>
2    22.0
3    <NA>
dtype: Float64
Valid readings: 2

Practice in Google Colab 🔝

Open in Google Colab View on GitHub
Run the examples in order, change the replacement rules and inspect the cleaned values and review rows. Save your own copy to keep edits. All sample tables are included in the notebook.

Continue with the Data Cleaning hub, where(), loc, at, NumPy where(), fillna() and iloc.

Reference: Pandas DataFrame.mask 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