Filtering with query() & eval()
Reviewed & published by Brayan K
df.query() filters rows using a plain-English string expression — like "age > 30 and city == 'NYC'" — instead of stacking bracketed boolean masks.
Part of the free Pandas course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Learn to write readable multi-condition filters, reference Python variables with @, compute new columns with eval(), and know when string expressions beat boolean masks.
What You'll Learn in This Lesson
- Filter rows with df.query("age > 30")
- Combine conditions with readable and / or
- Reference Python variables with @name
- Compute new columns with df.eval()
- Compare query() vs boolean masks for clarity
- Quote string values correctly inside the expression
1 Readable Filters — df.query("...")
df.query() takes a string and keeps the rows where it is true. Inside the string you write column names bare — no df["..."] — and you may use the friendly words and, or, and not instead of the bitwise &, |, ~. A filter that needs three sets of brackets as a mask becomes one clean sentence.
import pandas as pd
df = pd.DataFrame({
"name": ["Ann", "Bob", "Cy"],
"age": [30, 35, 28],
"city": ["NYC", "LA", "NYC"],
})
df.query("age > 30 and city == 'NYC'") # readable!
# same result as the mask:
df[(df["age"] > 30) & (df["city"] == "NYC")]import pandas as pd
df = pd.DataFrame({
"name": ["Ann", "Bob", "Cy", "Dee"],
"age": [30, 35, 28, 41],
"city": ["NYC", "LA", "NYC", "NYC"],
})
print(df.query("age > 30 and city == 'NYC'"))
# The OR form reads just as cleanly:
print(df.query("city == 'LA' or age < 29"))
# name age city
# 1 Bob 35 LA
# 2 Cy 28 NYC
# ✅ Expected output:
# name age city
# 3 Dee 41 NYC
# name age city
# 1 Bob 35 LA
# 2 Cy 28 NYC2 Using Variables — the @ Reference
Filters are rarely hard-coded — the threshold often lives in a variable. Inside a query string, prefix a Python variable with @ to use its value. df.query("age > @cutoff") reads cutoff from your code. Without the @, query assumes you mean a column called cutoff and raises an error when it cannot find one.
cutoff = 30
cities = ["NYC", "SF"]
df.query("age > @cutoff") # use the number from your code
df.query("city in @cities") # membership against a list
df.query("age > @cutoff and city in @cities")import pandas as pd
df = pd.DataFrame({
"name": ["Ann", "Bob", "Cy", "Dee"],
"age": [30, 35, 28, 41],
"city": ["NYC", "LA", "SF", "NYC"],
})
cutoff = 30
cities = ["NYC", "SF"]
print(df.query("age > @cutoff and city in @cities"))
# @ pulls the value from your code, 'in' checks the list
print(df.query("city in @cities")["name"].tolist())
# ['Ann', 'Cy', 'Dee']
# ✅ Expected output:
# name age city
# 3 Dee 41 NYC
# ['Ann', 'Cy', 'Dee']3 Computing Columns — df.eval()
Where query() selects rows, eval() calculates values. Hand it an arithmetic expression over your columns and it returns the result; use the "new = ..." form to attach it as a column. It keeps long formulas readable and, on big frames, can run faster by avoiding temporary intermediate arrays.
df = pd.DataFrame({"price": [10, 20], "qty": [3, 5]})
df.eval("price * qty") # Series: 30, 100
df = df.eval("total = price * qty") # add a computed column
df = df.assign(total=df["price"] * df["qty"]) # the classic equivalentimport pandas as pd
df = pd.DataFrame({"price": [10, 20, 5], "qty": [3, 5, 8]})
# Compute a value column with a readable expression
df = df.eval("total = price * qty")
print(df)
# eval can also just return the Series
print(df.eval("price * qty").tolist())
# [30, 100, 40]
# ✅ Expected output:
# price qty total
# 0 10 3 30
# 1 20 5 100
# 2 5 8 40
# [30, 100, 40]import pandas as pd
df = pd.DataFrame({
"name": ["Ann", "Bob", "Cy", "Dee"],
"age": [30, 35, 28, 41],
})
min_age = 32
# 🎯 YOUR TURN:
# Keep people older than min_age using query().
# Fill in the symbol that pulls a Python variable into the expression.
print(df.query("age > ___min_age"))
# Expected output:
# name age
# 1 Bob 35
# 3 Dee 41! Common Errors (And How to Fix Them)
❌ "name 'NYC' is not defined"
An unquoted string value is read as a variable name:
df.query("city == NYC") # ❌ NYC looks like a variable✅ Fix: quote the text value:
df.query("city == 'NYC'") # ✅❌ A Python variable is treated as a column
cutoff = 30
df.query("age > cutoff") # ❌ looks for a column 'cutoff'✅ Fix: prefix it with @:
df.query("age > @cutoff") # ✅🎯 Mini Challenge: Filter and Compute Orders
Use query and eval together on an orders table.
- Add a total column with eval
- Keep orders where total exceeds a @threshold
- Limit it to chosen regions with in @regions
import pandas as pd
df = pd.DataFrame({
"region": ["N", "S", "E", "N"],
"price": [10, 20, 5, 15],
"qty": [3, 5, 8, 10],
})
threshold = 50
regions = ["N", "S"]
df = df.eval("total = price * qty")
result = df.query("total > @threshold and region in @regions")
print(result)
# ✅ Expected output:
# region price qty total
# 1 S 20 5 100
# 3 N 15 10 150❓ Frequently Asked Questions
Lesson complete — your filters read like English!
You can filter with df.query(), combine clauses with and/or, inject variables with @, and compute new columns with df.eval().
Practice quiz
What does df.query() return?
- The rows where the expression is true
- A single boolean value
- The column names only
- A sorted copy of the frame
Answer: The rows where the expression is true. query() keeps the rows for which the string expression evaluates to True.
Inside a query string, how do you reference a Python variable named cutoff?
- #cutoff
- @cutoff
- $cutoff
- %cutoff
Answer: @cutoff. Prefix the variable with @, e.g. df.query('age > @cutoff').
Which method computes a new column from an expression?
- df.filter()
- df.select()
- df.eval()
- df.where()
Answer: df.eval(). df.eval('total = price * qty') evaluates the expression and can attach it as a column.
How must a string value be written inside a double-quoted query?
- Left unquoted, like NYC
- With backticks, like `NYC`
- In brackets, like [NYC]
- In single quotes, like 'NYC'
Answer: In single quotes, like 'NYC'. Wrap text values in single quotes so they are not read as variable names: city == 'NYC'.
Which words can replace &, |, ~ inside a query expression?
- and, or, not
- AND, OR, NOT only
- plus, minus, no
- with, without, none
Answer: and, or, not. query() accepts the readable keywords and, or, and not.
What does df.query('city in @cities') do?
- Adds a cities column
- Keeps rows whose city is in the list cities
- Sorts by city
- Renames the city column
Answer: Keeps rows whose city is in the list cities. in with @list is the readable equivalent of .isin() on a boolean mask.
By default, df.eval('total = price * qty') returns what?
- Nothing, it mutates in place silently
- A plain Python list
- A new DataFrame with the extra column
- Only the total Series
Answer: A new DataFrame with the extra column. It returns a new DataFrame with the computed column, so assign it back.
What error appears for df.query('city == NYC')?
- SyntaxError: invalid token
- ValueError: bad expression
- KeyError: NYC
- name 'NYC' is not defined
Answer: name 'NYC' is not defined. Unquoted NYC is treated as a variable name; quote it as 'NYC'.
Why might query()/eval() run faster on large frames?
- They can use the numexpr engine and avoid temporary arrays
- They skip type checking entirely
- They cache every result to disk
- They run on the GPU automatically
Answer: They can use the numexpr engine and avoid temporary arrays. pandas can evaluate the expression with numexpr, avoiding intermediate arrays.
Given df with age column, what does df.query('age > 30')['name'] return?
- The whole DataFrame
- The name values for rows where age exceeds 30
- A count of matching rows
- The age column instead
Answer: The name values for rows where age exceeds 30. query() filters rows first, then ['name'] selects that column from the result.
Continue this course
- Previous: Counting & Uniques
- Next: Replacing Values: replace, where & mask — Swap and conditionally overwrite values with replace, where, mask, and clip
- Quick reference: Pandas cheat sheet › Selecting & Filtering
Frequently asked questions
When should I use query() instead of a boolean mask?
Use query() when readability matters most — long filters with several conditions read like a sentence: query('age > 30 and city == "NYC"'). Boolean masks are still better when you need the mask object itself, or when the condition uses methods that query cannot express. For everyday multi-condition filtering, query() is cleaner.
How do I use a Python variable inside query()?
Prefix it with the @ symbol. If you have min_age = 30 in your code, write df.query('age > @min_age'). Without the @, pandas would look for a COLUMN named min_age and fail. The @ tells query to reach into your local variables instead of the DataFrame.
What does df.eval() do that query() does not?
query() filters rows and returns a subset. eval() evaluates an expression to produce VALUES — typically a new computed column. df.eval('total = price * qty') adds a total column using a fast, readable expression. Think of query() for selecting rows and eval() for calculating columns.
Are query() and eval() faster than normal pandas?
They can be on large frames, because pandas can evaluate the expression with the numexpr engine, avoiding temporary intermediate arrays. On small data the speed difference is negligible and the real benefit is readability. Do not reach for them purely for speed on modest datasets.