ValueError: The truth value of a Series is ambiguous
1 · The lesson
readWhat this error means
Something asked a pandas Series for a single True or False, and a Series of many values can't give one. Python's and, or, not, if and while all need exactly one boolean. A Series like [True, False, True] could mean "yes" (some are true) or "no" (not all are), and pandas refuses to pick. The same thing happens with a DataFrame and with a NumPy array — only the wording changes.
When you see it
Filtering with and:
Traceback (most recent call last): File "main.py", line 8, in <module> big_uk = orders[(orders["total"] > 100) and (orders["country"] == "UK")] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".../pandas/core/generic.py", line 1577, in __nonzero__ raise ValueError( ...<2 lines>... ) ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
The NumPy version, from an if on an array:
Traceback (most recent call last): File "main.py", line 4, in <module> if readings > 30: ^^^^^^^^^^^^^ ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
On a whole DataFrame it reads The truth value of a DataFrame is ambiguous.
Why it happens
A comparison on a Series doesn't return one boolean. It returns a Series of booleans, one per row:
import pandas as pd orders = pd.DataFrame({ "customer": ["Ada", "Grace", "Linus", "Hedy"], "total": [250.0, 40.0, 180.0, 95.0], "country": ["UK", "UK", "FI", "AT"], }) print(orders["total"] > 100) # 0 True, 1 False, 2 True, 3 False
and, or and not are Python keywords, not operators that pandas can redefine. To decide what a and b means, Python calls bool(a) — and bool() of a four-row Series is exactly the question pandas won't answer:
import pandas as pd orders = pd.DataFrame({ "customer": ["Ada", "Grace", "Linus", "Hedy"], "total": [250.0, 40.0, 180.0, 95.0], "country": ["UK", "UK", "FI", "AT"], }) big_uk = orders[(orders["total"] > 100) and (orders["country"] == "UK")] # ValueError
if orders["total"] > 100: fails for the same reason, and so does if df: when you meant "is this DataFrame empty?".
There's a second trap that produces the same error even after you switch to &. In Python, & binds more tightly than > and >=. So this:
import pandas as pd orders = pd.DataFrame({ "customer": ["Ada", "Grace", "Linus", "Hedy"], "total": [250.0, 40.0, 180.0, 95.0], "qty": [3, 1, 2, 1], }) orders[orders["total"] > 100 & orders["qty"] >= 2] # ValueError, despite the &
is read as orders["total"] > (100 & orders["qty"]) >= 2 — a chained comparison, which Python evaluates with a hidden and. Hence the error. The brackets around each condition aren't style; they're required.
How to fix it
Element-wise logic: & for and, | for or, ~ for not — and wrap every condition in brackets.
import pandas as pd orders = pd.DataFrame({ "customer": ["Ada", "Grace", "Linus", "Hedy"], "total": [250.0, 40.0, 180.0, 95.0], "country": ["UK", "UK", "FI", "AT"], }) big_uk = orders[(orders["total"] > 100) & (orders["country"] == "UK")] print(big_uk) # Ada only big_or_uk = orders[(orders["total"] > 100) | (orders["country"] == "UK")] print(big_or_uk["customer"].tolist()) # ['Ada', 'Grace', 'Linus'] not_uk = orders[~(orders["country"] == "UK")] print(not_uk["customer"].tolist()) # ['Linus', 'Hedy']
Once there are three or four conditions, name them. It reads better and the brackets look after themselves:
import pandas as pd orders = pd.DataFrame({ "customer": ["Ada", "Grace", "Linus", "Hedy"], "total": [250.0, 40.0, 180.0, 95.0], "country": ["UK", "UK", "FI", "AT"], }) is_big = orders["total"] > 100 in_europe = orders["country"].isin(["UK", "FI", "AT"]) print(orders[is_big & in_europe])
For a range, orders["total"].between(50, 200) beats two comparisons joined with &.
A single yes/no about the whole Series: say which question you mean.
import pandas as pd totals = pd.Series([250.0, 40.0, 180.0, 95.0]) if (totals > 200).any(): print("at least one order over 200") if (totals > 0).all(): print("no zero or negative totals") refunds = totals[totals < 0] if refunds.empty: print("no refunds today")
Use .empty rather than if df: or len(df) == 0 when you're checking whether a filter matched anything. And if you know the Series has exactly one value — say, one row matched by ID — pull it out with .item(), which raises if there isn't exactly one.
Ignore the a.bool() the message suggests. It has been deprecated since pandas 2.1 (pandas 2.3 prints a FutureWarning when you call it) and is on its way out.
NumPy arrays: the same fixes. &, |, ~ with brackets for element-wise logic; .any() or .all() for a single answer:
import numpy as np readings = np.array([21.5, 33.0, 28.4, 35.2]) # temperatures, °C hot = readings[(readings > 30) & (readings < 35)] print(hot) # [33.] if (readings > 30).any(): print("at least one reading over 30 °C")
When you'd actually see this in real code
- Filtering a sales DataFrame with
andbecause it reads like English — almost everyone does this once. if df["status"] == "shipped":inside a function that was written for one row and later handed a whole column.df.apply(...)with a function that doesif row_value > threshold— fine per row, but the same function called on the full column raises this.- Checking
if not df:after loading a CSV to see if it was empty. - A sensor or price check written for one reading,
if price > limit: alert(), later fed a whole NumPy array of prices.
Related errors
- TypeError: Object of type datetime is not JSON serializable — includes
int64: another place where a NumPy value doesn't behave like the plain Python one it resembles. - AttributeError: 'DataFrame' object has no attribute 'append' — the other pandas error you'll meet while porting old tutorials.
- KeyError: 'X' — a misspelt column name in a filter fails here before the logic is even checked.
See Also
- All Python errors — the full index, by type and by when it happens.
- Pandas: Working with Tables
- Pandas DataFrames: Command Center
- NumPy: Working with Numbers
- Operators & Expressions
Found this useful? Share it with a friend who’s learning Python.