Select columns — or rows — by their labels, rather than by their values.
Have you ever called filter because the name sounded like exactly what you needed, and gotten back a data frame with all of your rows and none of your columns? You are not the first. The name is misleading, and I say that as someone who uses this method constantly: filter never looks at your data. It looks at the labels — your column names, or the values in your index — and keeps the ones that match a pattern you describe.
Once that clicks, filter earns its place, because real data sets love to hand you forty columns when you want the three whose names happen to start with the same word.
Official documentation: DataFrame.filter
Three ways to describe what you want
filter takes exactly one of three keyword arguments, plus an optional axis:
df.filter(items=['emissions_kg', 'land_use_kg']) # these exact labels
df.filter(like='Bananas') # labels containing this substring
df.filter(regex=r'_kg$') # labels matching this regular expression
df.filter(like='cheese', axis='rows') # match the index, not the columns
The three criteria are mutually exclusive. Pass two of them and Pandas says so, in as many words:
df.filter(like='Bananas', regex='^Bananas')
# TypeError: Keyword arguments `items`, `like`, or `regex` are mutually exclusive
Pass none of them and you get a matching complaint: TypeError: Must pass either items, like, or regex. There is no default pattern, which is the right decision on Pandas's part.
The axis argument accepts 'columns' — the default on a data frame — or 'rows', which you may also spell 'index' or 0. On a series there is only one axis, so filter goes straight to the index and you can leave axis alone.
A worked example, on real data
Bamboo Weekly #22 used the Economist's "banana index," which scores foods by how much carbon and land they consume compared with a banana. Every food is a row, and the columns measure that in several different ways:
import pandas as pd
url = ('https://github.com/TheEconomist/banana-index-data/'
'releases/download/1.0/bananaindex.csv')
df = pd.read_csv(url, index_col='entity')
That is 160 foods and 16 columns. Three of those columns are the index scores, and all three begin with the word "Bananas." I would much rather describe them than type them out:
df.filter(like='Bananas').columns
Index(['Bananas index (kg)', 'Bananas index (1000 kcalories)',
'Bananas index (100g protein)'], dtype='str')
filter returns a data frame, so it drops into a chain like any other method. Which foods score worst per kilogram?
(
df
.filter(like='Bananas')
.sort_values('Bananas index (kg)', ascending=False)
.head()
)
Bananas index (kg) Bananas index (1000 kcalories) Bananas index (100g protein)
entity
Beef steak 148.563324 77.752629 8.312805
Beef mince 108.816189 54.049393 6.878002
Beef meatballs 81.052853 35.782826 4.612929
Beef burger 61.803856 25.011498 3.730481
Lamb chops 35.383303 13.505518 2.225951
Beef, unsurprisingly. Now suppose I only care about cheese. The food names are in the index, not in a column, so str.contains is not available to me — but the index is made of labels, which is precisely what filter understands. I switch axes and chain a second call:
(
df
.filter(like='Bananas')
.filter(like='heese', axis='rows')
.sort_values('Bananas index (kg)', ascending=False)
.head()
)
Bananas index (kg) Bananas index (1000 kcalories) Bananas index (100g protein)
entity
Cottage cheese 28.944313 33.508657 2.892767
Parmesan cheese 27.499275 7.272295 1.092785
Cheddar cheese 23.758006 6.172845 1.129409
Blue cheese 23.021427 6.980327 1.819096
Goat’s cheese 22.112650 7.102344 1.625015
Note that I asked for 'heese', not 'cheese'. Matching with like is case-sensitive, and this data set contains both "Cheddar cheese" and "Cheesecake." Dropping the first letter catches both. If that feels like a hack, regex='[Cc]heese' says the same thing more honestly.
Four mistakes people make
The first one is the big one, and it is the reason this page exists.
filter selects by label, never by value. If you want the rows where a number is above some threshold, that is loc and a boolean series, not filter. The two look deceptively similar in a chain, and the failure is silent:
df.filter(like='cheese') # 160 rows x 0 columns -- no error!
df.loc[pd.col('land_use_kg') > 30] # what you probably meant
The first call searched your column names for "cheese," found nothing, and handed back an empty frame without complaint. There is a clean way to hold the distinction in your head, and it is the same rule that governs pd.col: pd.col describes a value, and filter describes a name. Anything about the contents of your data belongs to loc.
The axis default is columns on a data frame and the index on a series. This catches people going in both directions. Matching row labels on a data frame requires axis='rows' and is easy to forget; then you call the same method on a single column, pass axis='columns' out of habit, and get ValueError: No axis named columns for object type Series.
items silently ignores labels that do not exist. Ask for a column you spelled wrong and Pandas simply leaves it out, where the bracket syntax would have stopped you:
list(df.filter(items=['emissions_kg', 'nope'])) # ['emissions_kg']
df[['emissions_kg', 'nope']] # KeyError: "['nope'] not in index"
That silence is occasionally what you want — items is a safe way to request columns that may or may not be present. Just do not lean on it to catch typos.
items also expects a collection. Passing one bare string gets you TypeError: Index(...) must be called with a collection of some kind, because Pandas will not guess whether you meant one label or a list of characters.
Where it shows up in Bamboo Weekly
#22: Banana index is the source of the example above — the Economist's first release of the index, where we hunted for foods with a bigger footprint than a banana, and used like, regex, and axis='rows' in a single issue.
#140: Stack Overflow survey asked what proportion of developers hold a degree. After value_counts, the education levels are index labels on a series, so counting them is .filter(regex='Bachelor|Master|Professional').sum() — about 74 percent.
#167: Oil prices compared WTI and Brent crude. Joining the two FRED series with lsuffix and rsuffix produced a wide frame, and .filter(regex='^value_') kept just the two price columns without naming either one.
#70: Moon missions counted 21st-century missions carrying a cubesat, scraped from Wikipedia. The marker was a symbol at the start of the index label, so .filter(regex='^⚀', axis='rows') did the selecting.
Practice it
Work through a .filter() exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/filter/
Go deeper
Two of those four issues lean on regular expressions, and regex is the argument I reach for most. If patterns are still a mystery, my free 14-part crash course is at RegexpCrashCourse.com.
More Pandas videos on Python and Pandas with Reuven Lerner.
Bamboo Weekly is the practice. If you want the structured version — full courses with downloadable Jupyter notebooks, plus live Pandas office hours when you get stuck — that is LernerPython+Data. A paid Bamboo Weekly subscription is included with it.
Related methods
.query()— when you are selecting rows by their values rather than columns by their labels.isin()— when you want rows whose values are in a set rather than columns by label.rename()— when the names themselves are the problem rather than which ones you want.drop()— when it is easier to name the columns you do not want
See it on real data
Below are the 37 Bamboo Weekly exercises that use filter on real-world data — try each one, then study the worked solution.
- Bamboo Weekly #179: Krakow tourism
- Bamboo Weekly #178: Harmful algal bloom
- Bamboo Weekly #167: Oil prices
- Bamboo Weekly #164: Fertilizer
- Bamboo Weekly #163: Daylight saving time
- Bamboo Weekly #157: Government corruption
- Bamboo Weekly #156: Winter Olympics
- Bamboo Weekly #155: Gold
- Bamboo Weekly #144: Museum Heists
- Bamboo Weekly #140: Stack Overflow survey
- Bamboo Weekly #139: Chinese exports
- Bamboo Weekly #132: JetBrains survey
- Bamboo Weekly #127: European comparisons
- Bamboo Weekly #125: Shrinking dollars
- Bamboo Weekly #124: NATO Spending
- Bamboo Weekly #116: Philadelphia Fed survey
- Bamboo Weekly #113: US airport traffic
- Bamboo Weekly #110: Credit access
- Bamboo Weekly #109: Cacao nibs
- Bamboo Weekly #107: Consumer confidence
- Bamboo Weekly #104: Aviation accidents
- Bamboo Weekly #98: Retail sales
- Bamboo Weekly #83: Gasoline prices
- Bamboo Weekly #81: School
- Bamboo Weekly #80: Inflation
- Bamboo Weekly #75: Refugees
- Bamboo Weekly #70: Moon missions
- Bamboo Weekly #59: Long covid
- Bamboo Weekly #58: NATO
- Bamboo Weekly #56: Rent increases
- Bamboo Weekly #50: Red Sea shipping
- Bamboo Weekly #41: Wine production
- Bamboo Weekly #38: Telework
- Bamboo Weekly #35: Terrorism
- Bamboo Weekly #31: Poverty
- Bamboo Weekly #28: Pret a Manger
- Bamboo Weekly #22: Banana index
Part of the Pandas Methods Index. See also practice by skill.