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

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  NYC

2 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 equivalent
import 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.

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

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.