Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
To filter a pandas DataFrame on several conditions, write each condition as its own boolean Series, wrap it in parentheses, and join the pieces with & (AND), | (OR), or ~ (NOT). Pass the combined mask inside df[...] or df.loc[...]. For example, df[(df["A"] > 2) & (df["B"] < 3)] keeps only the rows where column A is greater than 2 and column B is less than 3.
The rest of this guide explains why the parentheses matter, which operator to use for each logic case, how to select columns in the same step, and how to handle missing values, which are the most common reasons a multi-condition filter returns the wrong rows.
Why the Python and and or keywords fail on Series
Python’s and, or, and not keywords expect a single true or false value. A pandas column comparison returns a whole Series of True and False values, so Python cannot decide its truth value on its own. Using and between two Series typically raises a ValueError stating that the truth value of a Series is ambiguous. The element-wise operators &, |, and ~ are the ones pandas expects, as shown in the pandas indexing and selecting guide.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Why every comparison needs parentheses
The & and | operators bind more tightly than comparison operators such as > and <. Without parentheses, df["A"] > 2 & df["B"] < 3 is not read as two separate conditions. Python instead evaluates a chained comparison that mixes the numbers and columns in an unintended order. Wrapping each comparison in its own parentheses forces the intended grouping:
#1 Best Overall
good = df[(df["A"] > 2) & (df["B"] < 3)]
wrong = df[df["A"] > 2 & df["B"] < 3] # unintended precedence
The wrong version often raises an error or produces a result you did not intend, so treat the missing parentheses as the first thing to check when a multi-condition filter behaves strangely.
Combining conditions with AND, OR, and NOT
The three operators cover every logical combination you need. The table below uses this small sample, which you can paste into a session to test each pattern:
import pandas as pd
df = pd.DataFrame({"A": [1, 3, 5, -2], "B": [2, 4, 1, 12]})
| Goal | Pattern | Rows kept from the sample |
|---|---|---|
| Both conditions true (AND) | df[(df["A"] > 2) & (df["B"] < 3)] |
Row 2 only (A = 5, B = 1) |
| Either condition true (OR) | df[(df["A"] < 0) | (df["B"] > 10)] |
Row 3 only (A = -2, B = 12) |
| Condition is false (NOT) | df[~(df["A"] > 2)] |
Rows 0 and 3 (A is 2 or less) |
Each row in this table was checked by hand against the sample values above. With a real dataset, the same patterns apply, but the rows returned depend entirely on your data.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallRank #2
Choosing a filtering form
Several forms can express the same multi-condition filter. The right one depends on whether you also need to pick columns and how readable the expression needs to be.
Boolean indexing
Boolean indexing keeps the mask visible as a named variable, which makes it the best default for examples, reusable conditions, and longer Python expressions:
mask = (df["A"] > 2) & (df["B"] < 3)
filtered = df[mask]
Filtering rows and selecting columns with .loc
Use .loc when you want the row mask and a column selection in one step. Pass the mask first and the column list second:
result = df.loc[mask, ["A", "B"]]
.loc is label-aware, so a boolean Series is accepted as the row indexer as long as it aligns with the DataFrame index. The indexing guide notes that .iloc does not accept a boolean Series; a boolean NumPy array works with .iloc, so convert the mask with .to_numpy() if you must use position-based selection.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Using query for readable expressions
DataFrame.query() accepts a string expression, which can be easier to scan when the conditions are simple and column-based:
filtered = df.query("A > 2 and B < 3")
The string uses the keywords and, or, and not, not the operators &, |, and ~. To reference a Python variable inside the string, prefix it with @, for example df.query("A > @threshold").
Do not pass untrusted user input directly into query(). The pandas.DataFrame.query API reference warns that query expressions can run arbitrary code, so build such strings only from values you control, or use boolean indexing for user-supplied criteria.
Handling missing values in masks
Missing values change what a mask means, and they are the most common cause of a filter that silently drops rows. Work through these steps when a mask contains missing entries:
- Check the dtype of the mask. A mask stored with the nullable
booleandtype can holdpd.NAin addition to True and False. - Decide what a missing condition should mean for your task: drop the row, keep the row, or handle it as a separate case.
- Apply the decision explicitly. Per the pandas nullable Boolean data type guide, missing values in a boolean indexer are treated as False.
# Drop rows where the condition is missing (the default behavior)
kept = df[mask]
# Keep rows where the condition is missing
kept_including_unknown = df[mask.fillna(True)]
# Drop them explicitly, which makes the intent visible in code
dropped = df[mask.fillna(False)]
Choose the fill value from the meaning of your data rather than by habit. A missing age in a filter for “under 30” may reasonably be kept for manual review, while a missing price in a filter for “under 50” may reasonably be dropped.
Best Value
Row filtering versus conditional values
Filtering removes rows that fail a condition. If you instead want to assign a label or value based on several ordered conditions and keep every row, use numpy.select. Its signature takes a list of conditions, a matching list of choices, and a default:
import numpy as np
df["group"] = np.select(
[df["A"] > 4, df["A"] > 2],
["high", "medium"],
default="low",
)
The first matching condition wins, so order the list from most specific to least specific. This pattern creates a new column and does not reduce the DataFrame.
Quick Recap
Troubleshooting common symptoms
- ValueError about the truth value of a Series: you combined masks with
and,or, ornot. Replace them with&,|, and~. - Rows returned that do not match the logic: check each comparison is wrapped in parentheses before the
&or|operator. - Fewer rows than expected: look for missing values in the mask. Nullable Boolean entries that are
pd.NAare excluded unless you fill them. - Filter fails with
.iloc: pass a NumPy boolean array rather than a boolean Series, or switch to.loc.
,
body_html_placeholder_removed”
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

