Take a known ending off every value in a column, without taking anything else.
What it does
How do you get rid of an ending you did not ask for — the km after every distance, the % after every rate, the _airport after every category? The obvious answer is .str.strip(' km'), and it is wrong in a way that does not raise, does not warn, and shows up three steps later as a join that matches nothing.
.str.removesuffix() is the method that means what people think strip means. It removes one occurrence of a literal string from the end of each value, and if the value does not end with that string, hands it back untouched.
That second half is the whole point. strip takes a set of characters and keeps chewing until it hits one outside the set; removesuffix takes a string and either matches it exactly or does nothing. Doing nothing is the guarantee that lets you run the method over a whole column when only some rows carry the suffix.
Official documentation: Series.str.removesuffix
The argument
There is one, and it is the suffix. It is always literal — no regexps, no character sets, no case folding, nothing to configure — and there is exactly one of it, which surprises people, because its neighbor .str.endswith() happily accepts a tuple:
s.str.endswith(('_airport', '_base')) # fine
s.str.removesuffix(('_airport', '_base')) # TypeError: expected bytes, tuple found
For several suffixes, chain the calls. That is safe precisely because each one does nothing when it does not match.
A worked example, on real data
OurAirports classifies every airfield on the planet, and the category column is built out of a repeated word:
import pandas as pd
url = 'https://davidmegginson.github.io/ourairports-data/airports.csv'
df = pd.read_csv(url)
df['type'].value_counts()
type
small_airport 42705
heliport 23171
closed 13449
medium_airport 4105
seaplane_base 1274
large_airport 1174
balloonport 61
Name: count, dtype: int64
Seven categories, three of which end in _airport — 47,984 of the 85,939 rows. I want small, medium and large, and the other four left exactly as they are:
df['type'].str.removesuffix('_airport').value_counts()
type
small 42705
heliport 23171
closed 13449
medium 4105
seaplane_base 1274
large 1174
balloonport 61
Name: count, dtype: int64
Three categories shortened, four untouched, in one pass over the column. Now the same job with .str.strip():
df['type'].str.strip('_airport').value_counts()
type
small 42705
hel 23171
closed 13449
medium 4105
seaplane_base 1274
large 1174
balloon 61
Name: count, dtype: int64
heliport became hel, and balloonport became balloon. strip was handed the set {'_', 'a', 'i', 'r', 'p', 'o', 't'} and ate backward through heliport until it hit the l: 23,232 rows changed that should not have been, with no error and no warning. Notice too that small, medium and large came out identical either way, which is what makes this so easy to ship.
The cheapest way to confirm you took off what you meant to is to ask:
df['type'].str.removesuffix('_airport').str.endswith('_airport').sum()
0
Three mistakes people make
Reaching for .str.strip() because the suffix is short. On a one-character suffix the two agree — '%' is a set of one character — so everybody's first attempt works, and the habit sticks. Trouble starts at two characters and grows with the length of the suffix. The str.strip page has the other half of this story on Census data, where .str.strip(' Metro Area') turned .Albany, GA into .Albany, G across 100 of 393 place names.
Chaining suffixes in the wrong order. Because each call does nothing when it does not match, order usually does not matter — until one suffix ends with another:
s = pd.Series(['small_airport', 'heliport', 'balloonport', 'closed'])
# ['small', 'heli', 'balloon', 'closed']
s.str.removesuffix('_airport').str.removesuffix('port').tolist()
# ['small_air', 'heli', 'balloon', 'closed']
s.str.removesuffix('port').str.removesuffix('_airport').tolist()
Take 'port' off first and small_airport becomes small_air, which no longer ends in _airport, so the second call does nothing. Remove the longest suffix first.
Building a regexp for it. .str.replace(r'_airport$', '', regex=True) gives an identical result — I checked on all 85,939 rows — and costs you speed:
removesuffix 1.92 ms
replace regex 5.19 ms
More to the point, the moment your suffix contains a ., a (, a + or a $ you have to remember to escape it — and ' (unit/litre)' is a real Bamboo Weekly suffix. removesuffix has nothing to escape.
Where it shows up in Bamboo Weekly
Five Bamboo Weekly solutions use .str.removesuffix(), and four are doing the same thing: taking a unit off the end of a number so that astype can finish the job. That is the pattern to learn — this method is usually a chain's second-to-last step:
pd.Series(['4.25%', '3.80%', '11.10%']).str.removesuffix('%').astype(float)
Bamboo Weekly #16: Consumer oil prices is the introduction, and free to read. Every value in a PRODUCT column ends in (unit/litre), so the solution removes it and then adds .str.strip() for the whitespace left behind — the two methods doing their two different jobs.
Bamboo Weekly #40: Sovereign bonds is the pattern at its tersest, also free: rates_df['Rate'].str.removesuffix('%').astype('float'), then the same again with 'bp' for basis points, divided by 100.
Bamboo Weekly #123: Missiles is the full-length version. A Range column arrives as 2-100 km, so the chain removes the km, strips the thousands commas and the + signs, collapses the ranges with a captured regexp, and only then reaches astype.
Bamboo Weekly #54: Household debt is the odd one out, and free as well: one New York Fed cell had picked up a footnote marker, and df[84].str.removesuffix('.1').astype(float) was all that stood between the column and a numeric dtype.
Practice it
Work through a .str.removesuffix() exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/str-removesuffix/
Go deeper
str.removeprefix is the same method at the other end of the string, and str.endswith tells you how many rows carry the suffix before you remove it. When what you want gone really is a set of characters — trailing punctuation, stray periods, mixed whitespace — str.strip is the right method after all, and that page shows where its set behavior is an asset. Downstream, this chain almost always ends at astype; if the ending varies from row to row instead, you have outgrown both methods, and str.split is where to go next.
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
.str.strip()— which is what people reach for by mistake, and what it does to a suffix.astype()— which is usually the next step, once the unit is off the number
See it on real data
Below are the 5 Bamboo Weekly exercises that use str.removesuffix on real-world data — try each one, then study the worked solution.
- Bamboo Weekly #123: Missiles
- Bamboo Weekly #108: Measles
- Bamboo Weekly #54: Household debt
- Bamboo Weekly #40: Sovereign Bonds
- Bamboo Weekly #16: Consumer oil prices
Part of the Pandas Methods Index. See also practice by skill.