Skip to content

pandas str.removesuffix

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.

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.

Part of the Pandas Methods Index. See also practice by skill.