filter() selects by label, not by value — it's the SQL LIKE of pandas column selection.
You've been writing df[[c for c in df.columns if 'eur' in c]] for years — pandas shipped the one-liner in 2009.
df.filter() keeps columns or rows whose LABELS match. Three mutually exclusive matchers: items (exact label list), like (substring), regex (regular expression). By default it works on the column axis; pass axis=0 to filter row labels instead. Nothing is removed from the source — you get a new DataFrame back. Missing labels in items are silently skipped rather than raising a KeyError.
Wide tables with generated column names are everywhere: sensor_2024_01, sensor_2024_02, revenue_eur, revenue_usd, 47 one-hot dummies. df.filter(regex=...) grabs exactly the family you want in one line, with no list comprehension. It also searches MultiIndex levels — the only column selector that understands hierarchical names natively — and it composes cleanly in method chains, where bracket indexing does not.
You got a sales report and only need two of its four columns before sending it on. Pick them by exact name with items:
order_id revenue_eur 0 1001 144.0 1 1002 55.3 2 1003 384.0 3 1004 27.9
items takes any list-like of exact labels. Try to keep a column that doesn't exist (say "discount") and filter just skips it — no KeyError, no warning. Verified: df.filter(items=["order_id", "discount"]) returns just order_id.
All metric columns share a prefix or suffix. like does substring matching, regex does full pattern matching — both in one call. df.filter(like="revenue") keeps just revenue_eur; the regex form below grabs the id and revenue family at once:
order_id revenue_eur 0 1001 144.0 1 1002 55.3 2 1003 384.0 3 1004 27.9
That's df.filter(regex="^(order|rev)"). regex uses re.search, not re.match — "eur" alone would also match anything containing those letters mid-name. Anchor with ^ or $ when the pattern should sit at an edge.
Rows are labels too. On a quarterly city table, grab one row by name with axis=0:
q1 q2 Berlin 810 902
That's df.filter(like="Berlin", axis=0); df.filter(regex="^V", axis=0) similarly keeps only Vienna (both verified). Bonus, also verified: on a MultiIndex frame, df.filter(like="revenue", axis=1) keeps every ('berlin','revenue') and ('vienna','revenue') column at once — and df.filter(items=[("berlin","revenue")]) selects one tuple exactly.
| Flag | Meaning |
|---|---|
items=[...] | exact labels to keep; missing ones are silently ignored |
like="revenue" | keep labels containing this substring |
regex="^rev" | keep labels where re.search matches (not re.match) |
axis=0 / axis=1 | which axis to match on: 1/'columns' (default) filters columns, 0/'index' filters rows |
mutually exclusive | items, like, regex: pass exactly one; passing none raises TypeError |
lenient items | df.filter(items=["a", "zzz"]) never raises KeyError — unlike df["zzz"] |
case sensitivity | like is a plain Python substring check; use regex="(?i)eur" for case-insensitive |
The first commit adding DataFrame.filter landed on December 31, 2009, when pandas still lived on Google Code under Wes McKinney's original SVN import — it shipped in the 0.3.0 era as a columns-only helper (items, like, regex), with no axis argument at all.
In January 2014, 0.13.0 moved filter up into NDFrame (pandas/core/generic.py) so Series, DataFrame, and Panel all shared one implementation, and added the axis parameter — that's the moment rows became filterable too.
After PR #52941 (April 2023, pandas 2.1), all three match modes funnel into a single fast path over the axis Index. items does a direct isin() lookup, like builds a boolean via a Python-level substring test, and regex compiles once with re.compile then runs search over the labels — labels are matched as strings, so a numeric column named 2024 matches regex="^2" through its str form. The result is a take-based selection, so filter() never copies more than the selected blocks.