ValueError: Cannot mask with non-boolean array containing NA / NaN values happens when Pandas receives a filter mask that contains missing values. Boolean indexing needs a mask made only of True and False. If the mask also contains NA, NaN, or None, Pandas cannot safely decide which rows to keep.
The fix is to make the mask fully boolean before using it to filter a DataFrame or Series. Common fixes include passing na=False to str.contains(), filling missing values in the mask with fillna(False), or filtering valid rows first with notna().
This error is not saying your DataFrame is broken. It is saying the filtering condition is incomplete. A row with a missing value did not produce a definite yes-or-no answer, so Pandas refuses to guess.
Why the Error Happens
The error often appears when string methods run on a column that contains missing values. The comparison returns True or False for normal strings, but it returns a missing value for missing input.
import pandas as pd
df = pd.DataFrame({"name": ["Python", None, "Pandas"]})
mask = df["name"].str.contains("Py")
print(df[mask])
The mask is not purely boolean because the row with None creates a missing mask value. Pandas refuses to use that ambiguous mask for row selection. This protects you from accidentally dropping or keeping rows without an explicit rule.
Use na=False With str.contains()
The simplest fix for str.contains() is usually na=False. Missing values are treated as non-matches, so the mask contains only booleans.
import pandas as pd
df = pd.DataFrame({"name": ["Python", None, "Pandas"]})
mask = df["name"].str.contains("Py", na=False)
print(df[mask])
Use this when missing values should not match the search pattern. It is concise and keeps the missing-value decision close to the string operation. In search filters, this is often the most readable fix because a blank or missing cell simply does not match.
Fill Missing Mask Values Explicitly
If the mask has already been created, use fillna(False) before filtering. This is helpful when the mask comes from a multi-step expression.
import pandas as pd
df = pd.DataFrame({"name": ["Python", None, "Pandas"]})
mask = df["name"].str.contains("a")
clean_mask = mask.fillna(False)
print(df[clean_mask])
Use False when missing rows should be excluded. Use True only when missing rows should intentionally remain in the result. Make that choice deliberately because it changes the final filtered data.
Filter Non-Missing Rows First
Sometimes the correct business rule is to ignore missing values before applying a string condition. Use notna() to keep valid rows first.
import pandas as pd
df = pd.DataFrame({"name": ["Python", None, "Pandas"]})
valid_names = df["name"].notna()
contains_py = df["name"].str.contains("Py", na=False)
print(df[valid_names & contains_py])
This makes the rule explicit: the row must have a real value and it must match the pattern. Pandas uses & for element-wise boolean AND. Wrap each condition in parentheses when the expression becomes longer.
Check the Mask Before Filtering
If the source of the error is unclear, inspect the mask before using it. Look for missing values and confirm the dtype.
import pandas as pd
df = pd.DataFrame({"name": ["Python", None, "Pandas"]})
mask = df["name"].str.contains("a")
print(mask)
print(mask.isna())
This shows which rows introduced missing mask values. For related missing-value cleanup outside Pandas, see PythonPool’s remove NaN from list guide and Python null guide.
Build a Safe Reusable Filter
If you repeat the same filter in several places, wrap the logic in a helper that always returns a boolean mask. This avoids reintroducing missing values later.
import pandas as pd
def contains_text(series, text):
return series.str.contains(text, na=False)
df = pd.DataFrame({"name": ["Python", None, "Pandas"]})
print(df[contains_text(df["name"], "Py")])
The helper keeps the missing-value policy consistent. It also makes tests simpler because one function owns the rule for missing text values. Add test rows with None, NaN, and normal strings so the behavior stays clear.
Common Causes
The most common cause is a text column that mixes real strings with missing values. Another cause is combining several conditions where one condition produces NA. Before filtering, each part of the condition should return a clean boolean Series, and the final combined mask should also be clean.
Pandas also has nullable boolean data, which can contain pd.NA. That is useful for representing unknown values, but it still needs to be resolved before ordinary row filtering.
Checklist
- Use
str.contains(pattern, na=False)for string filters with missing values. - Use
mask.fillna(False)when the mask already exists. - Use
series.notna()when rows must be valid before matching. - Inspect
mask.isna()when the failing condition is not obvious.
The key rule is that a Pandas mask must be unambiguous. Before passing a mask into df[mask], make sure every value is either True or False. For broader type-debugging patterns, see PythonPool’s check data type in Python guide.
Once the mask is clean, the DataFrame filtering step is straightforward and predictable. Most fixes are one line, but that line should encode your real missing-value policy.