The friendliest-looking method in the string accessor, and the one most likely to put a typo into your chart.
What it does
Have you ever fixed a column's capitalization and then found the mistake in the published version? .str.title() capitalizes the first letter of every word and lowercases the rest, one call, no arguments. It looks like the polite way to tidy up a shouting column of text.
It is not a display method. It is a folding method that happens to produce readable output, and the difference matters. Its one real job is the job str.lower does: making values that ought to be equal compare as equal, in value_counts, in groupby, in a merge. The only thing it adds over lowercasing is that the folded key is still legible on an axis label. Everything that goes wrong with this method goes wrong because somebody wanted that second property and got the first one's accuracy.
Three words are enough to see the problem, and all three are common in real data:
s = pd.Series(["MCDONALD'S", 'KFC', 'USA'])
pd.DataFrame({'original': s, 'title': s.str.title(),
'capitalize': s.str.capitalize()})
original title capitalize
0 MCDONALD'S Mcdonald'S Mcdonald's
1 KFC Kfc Kfc
2 USA Usa Usa
Python treats the apostrophe as a word break, so Mcdonald'S. It has no idea that KFC and USA are acronyms, or that Mc is a prefix. .str.capitalize() — the sibling that capitalizes only the first letter of the whole value — gets the apostrophe right and is otherwise no better.
Official documentation: Series.str.title and Series.str.capitalize
A worked example, on real data
New York City publishes every 311 service request. Here is January 2025, where the complaint categories have been typed in by hand for years:
import pandas as pd
url = ('https://data.cityofnewyork.us/resource/erm2-nwe9.csv'
'?$select=complaint_type,agency,agency_name'
"&$where=date_trunc_ym(created_date)='2025-01'"
'&$limit=300000')
df = pd.read_csv(url)
df['complaint_type'].value_counts().head(8)
complaint_type
HEAT/HOT WATER 60567
Noise - Residential 54301
Illegal Parking 37570
Blocked Driveway 12797
UNSANITARY CONDITION 8864
PLUMBING 6673
Water System 6140
Snow or Ice 5144
Name: count, dtype: int64
Some categories shout and some do not, which is the classic reason to fold. And here .str.title() earns its keep, because two categories are split in two:
df['complaint_type'].value_counts().reindex(['PLUMBING', 'Plumbing',
'ELEVATOR', 'Elevator'])
complaint_type
PLUMBING 6673
Plumbing 231
ELEVATOR 107
Elevator 1432
Name: count, dtype: int64
Neither 6,673 nor 231 is the number of plumbing complaints. Fold the column and they merge, into 6,904 and 1,539 — and unlike .str.lower(), the result is still something you can put on a chart.
Now look at what the same call did to the rest of the column:
(df['complaint_type']
.drop_duplicates()
.to_frame()
.assign(title=lambda df_: df_['complaint_type'].str.title())
.loc[lambda df_: df_['complaint_type'].isin(
['HEAT/HOT WATER', 'PLUMBING', 'Smoking or Vaping',
'Noise - House of Worship'])]
.reset_index(drop=True))
complaint_type title
0 HEAT/HOT WATER Heat/Hot Water
1 Smoking or Vaping Smoking Or Vaping
2 Noise - House of Worship Noise - House Of Worship
3 PLUMBING Plumbing
Smoking Or Vaping. House Of Worship. English title case lowercases short function words; Python's does not, because Python is not doing title case at all — it is uppercasing the letter after every non-letter. The one call that fixed PLUMBING broke or and of in the same pass.
Three mistakes people make
Titling a column of acronyms. The agency column is nothing but acronyms, and .str.title() destroys every one of them:
(df['agency'].value_counts().head(4)
.to_frame().reset_index()
.assign(title=lambda df_: df_['agency'].str.title()))
agency count title
0 NYPD 125494 Nypd
1 HPD 99366 Hpd
2 DSNY 23849 Dsny
3 DOT 14681 Dot
Nobody has ever called them Nypd. Where the values are codes, fold with .str.lower() and keep the original for display.
Titling names that already have correct capitalization. agency_name is written properly in the source, and .str.title() makes it worse:
names = ['Department of Buildings',
'Department of Health and Mental Hygiene',
'Taxi and Limousine Commission']
(df.loc[df['agency_name'].isin(names), 'agency_name']
.drop_duplicates().sort_values().to_frame()
.assign(title=lambda df_: df_['agency_name'].str.title())
.reset_index(drop=True))
agency_name title
0 Department of Buildings Department Of Buildings
1 Department of Health and Mental Hygiene Department Of Health And Mental Hygiene
2 Taxi and Limousine Commission Taxi And Limousine Commission
If the column is already consistent, there is nothing to fold, and the only thing this call can do is introduce errors.
Trusting it in published output. This is not a hypothetical. All three Bamboo Weekly solutions below print .str.title() results, and all three carry the bug into the answer: Weapons And Equipment, District Of Columbia, Harvard School Of Public Health. I did not notice at the time either.
Where it shows up in Bamboo Weekly
Four Bamboo Weekly solutions use .str.title(), and none uses .str.capitalize() — which tells you something.
Bamboo Weekly #63: Ukraine aid is the honest case, free to read. A hand-typed Type of Aid Specific column held 34 distinct values; .str.title() brought it to 28 and adding .str.strip() brought it to 26. That is the method working. The published output is headed Weapons And Equipment, which is the method failing, in the same chain.
Bamboo Weekly #33: Fracking is where it is unambiguously right, and also free. A StateName column typed by hand gave 75 distinct values including Texas, tx and Ok. State names are ordinary words with no function words in them, so .str.title() folds them safely — and the leftovers, Texasa and Pennsylvanya, then need replace and a dictionary, because no string method will ever guess those.
Bamboo Weekly #105: Federal employees runs .str.title() on a location column of all-caps state names, and its results table is topped by District Of Columbia with 162,144 employees.
Practice it
Work through a .str.title() exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/str-title/
Go deeper
My rule: use .str.title() only when you need a folded key that will be read by a human, and only on values that are ordinary English words — no acronyms, no possessives, no of and no and. Everywhere else, str.lower folds just as well and cannot embarrass you, and a small dictionary through replace gives you display labels that are actually correct.
Before blaming case for anything, check the two problems that masquerade as it: str.strip for invisible whitespace, and str.replace for the punctuation that no amount of folding will fix.
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.lower()— which is the safe way to make values comparable, since title mangles names.replace()— when a handful of names need fixing by hand after a bulk conversion
See it on real data
Below are the 4 Bamboo Weekly exercises that use str.title on real-world data — try each one, then study the worked solution.
- Bamboo Weekly #121: Research funding
- Bamboo Weekly #105: Federal employees
- Bamboo Weekly #63: Ukraine aid
- Bamboo Weekly #33: Fracking
Part of the Pandas Methods Index. See also practice by skill.