pandas multiple conditions while indexing data frame - unexpected behavior
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
When filtering a pandas DataFrame with multiple conditions, unexpected behavior almost always comes from using Python's and/or keywords instead of bitwise operators &/|, or from forgetting parentheses around individual conditions. Python's and and or evaluate the truth value of entire arrays, which is ambiguous for arrays with multiple elements, triggering the infamous ValueError: The truth value of a Series is ambiguous. The correct approach uses & (and), | (or), and ~ (not) with parentheses around each condition.
The Problem: and/or vs &/|
Python's and calls bool() on each operand. A pandas Series with multiple elements cannot be reduced to a single True/False, so Python raises the error. The & operator performs element-wise AND on the boolean Series.
Operator Precedence: Why Parentheses Matter
& has higher operator precedence than >, <, == in Python. Without parentheses, df['age'] > 25 & df['salary'] is parsed as df['age'] > (25 & df['salary']), producing wrong results.
Using .query() for Cleaner Syntax
df.query() uses and/or/not (the readable Python keywords) instead of &/|/~. It is often cleaner for complex conditions and avoids the parentheses issue entirely.
Using .loc with Multiple Conditions
.loc with a boolean mask is the most explicit approach. Separating the mask creation from the indexing makes complex logic easier to read and debug.
Multiple Conditions with .isin() and .between()
Filtering with np.where and np.select
Common Pitfalls
- Using
and/orinstead of&/|: Python'sandandorevaluate the truth value of the entire Series, which is ambiguous for multi-element arrays. Always use&for AND,|for OR, and~for NOT when filtering DataFrames with boolean indexing. - Forgetting parentheses around conditions:
df[df['a'] > 1 & df['b'] < 5]is parsed asdf[df['a'] > (1 & df['b']) < 5]due to&having higher precedence than comparison operators. Always wrap each condition in parentheses:df[(df['a'] > 1) & (df['b'] < 5)]. - Chaining comparisons like pure Python:
df[25 < df['age'] < 35]does not work as expected with pandas Series because Python's chained comparison usesandinternally. Write it asdf[(df['age'] > 25) & (df['age'] < 35)]or usedf[df['age'].between(26, 34)]. - Modifying a filtered DataFrame without
.loc:df[df['age'] > 30]['salary'] = 0triggers aSettingWithCopyWarningand may not modify the original DataFrame. Usedf.loc[df['age'] > 30, 'salary'] = 0for safe in-place modification. - Using
==withNoneinstead of.isna():df[df['column'] == None]does not reliably detect NaN values. Usedf[df['column'].isna()]for null checks anddf[df['column'].notna()]for non-null filtering.
Summary
- Use
&,|,~(notand,or,not) for combining conditions in pandas boolean indexing - Always wrap each condition in parentheses due to operator precedence
- Use
df.query()for readable multi-condition filtering with Python keywords - Use
.loc[mask]for explicit boolean mask indexing and safe column assignment - Use
.isin()for membership checks and.between()for range filtering - Use
.isna()instead of== Nonefor null value detection

