String Operations with .str
Reviewed & published by Brayan K
The .str accessor in pandas is a gateway that lets you apply familiar Python string methods, such as lower, strip, contains, and split, to every value in a text column at once, returning a new Series of results.
Part of the free Pandas course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Learn to clean messy text, search and filter rows by their contents, split fields into pieces, and chain operations into tidy pipelines.
What You'll Learn in This Lesson
- Change case with .str.lower() and .str.upper()
- Trim whitespace with .str.strip()
- Search text with .str.contains()
- Swap text with .str.replace()
- Split fields with .str.split(expand=True)
- Chain .str methods and handle missing values
1 Cleaning Text: case, strip, len
Real-world text is messy: mixed capitalisation and stray spaces are everywhere. The .str accessor exposes the usual Python methods. .str.lower() and .str.upper() change case, .str.strip() removes leading and trailing spaces, and .str.len() counts characters per value.
import pandas as pd
names = pd.Series([" Ava ", "BEN", "cara "])
print(names.str.strip()) # removes outer spaces
print(names.str.lower()) # all lowercase (spaces remain)
print(names.str.strip().str.title()) # clean + Title Case -> Ava, Ben, Cara
print(names.str.strip().str.len()) # character counts: 3, 3, 4
# ✅ Expected output:
# 0 Ava
# 1 BEN
# 2 cara
# dtype: object
# 0 ava
# 1 ben
# 2 cara
# dtype: object
# 0 Ava
# 1 Ben
# 2 Cara
# dtype: object
# 0 3
# 1 3
# 2 4
# dtype: int642 Searching and Replacing Text
.str.contains("x") returns a boolean Series you can use as a filter mask. .str.startswith() checks the beginning of each value, and .str.replace(old, new) swaps text everywhere it appears. These three power most text filtering and cleaning tasks.
import pandas as pd
df = pd.DataFrame({
"email": ["ava@gmail.com", "ben@yahoo.com", "cara@gmail.com"],
})
# Boolean mask: which emails contain 'gmail'?
mask = df["email"].str.contains("gmail")
print(df[mask]) # keeps Ava and Cara
# Does the address start with 'ava'?
print(df["email"].str.startswith("ava").tolist()) # [True, False, False]
# Replace the domain text
print(df["email"].str.replace("gmail.com", "company.com").tolist())
# ✅ Expected output:
# email
# 0 ava@gmail.com
# 2 cara@gmail.com
# [True, False, False]
# ['ava@company.com', 'ben@yahoo.com', 'cara@company.com']3 Splitting Columns and Chaining
.str.split(sep, expand=True) breaks one column into several. With expand=True you get a DataFrame with one column per piece, ready to assign to new names. Because each .str call returns a Series, you can chain several together in one readable line.
import pandas as pd
df = pd.DataFrame({"full_name": ["Ava Stone", "Ben Wood", "Cara Lee"]})
# Split into two new columns with expand=True
parts = df["full_name"].str.split(" ", expand=True)
df["first"] = parts[0]
df["last"] = parts[1]
print(df)
# Chaining: strip, lowercase, then build an email handle
emails = (df["full_name"]
.str.strip()
.str.lower()
.str.replace(" ", ".") + "@site.com")
print(emails.tolist())
# ['ava.stone@site.com', 'ben.wood@site.com', 'cara.lee@site.com']
# ✅ Expected output:
# full_name first last
# 0 Ava Stone Ava Stone
# 1 Ben Wood Ben Wood
# 2 Cara Lee Cara Lee
# ['ava.stone@site.com', 'ben.wood@site.com', 'cara.lee@site.com']🎯 Your Turn: Tidy the Tags
Replace the blank so every tag is lowercase with no surrounding spaces. The expected output is the list ['python', 'pandas', 'data'].
import pandas as pd
tags = pd.Series([" Python ", "PANDAS", " Data "])
# 🎯 YOUR TURN: strip the spaces, then lowercase every tag
clean = tags.str.strip().str.___()
print(clean.tolist())
# Expected output: ['python', 'pandas', 'data']📋 Quick Reference
| Method | What it does |
|---|---|
| .str.lower() / .str.upper() | Change case of every value |
| .str.strip() | Remove leading/trailing whitespace |
| .str.contains("x") | Boolean mask of matches |
| .str.startswith("x") | Check each value's prefix |
| .str.replace(a, b) | Swap text in every value |
| .str.split(sep, expand=True) | Split into multiple columns |
| .str.len() | Length of each value |
🎯 Mini Challenge: Username Generator
Clean a name column and turn it into lowercase usernames, then filter to names containing the letter "a".
import pandas as pd
df = pd.DataFrame({"name": [" Ava Stone ", "Ben Wood", " Cara Lee "]})
# 1. Build a username: strip, lowercase, spaces -> dots
df["username"] = df["name"].str.strip().str.lower().str.replace(" ", ".")
print(df["username"].tolist())
# ['ava.stone', 'ben.wood', 'cara.lee']
# 2. Keep only rows whose name contains 'a' (case-insensitive)
mask = df["name"].str.contains("a", case=False, na=False)
print(df[mask]["name"].tolist())
# ✅ Expected output:
# ['ava.stone', 'ben.wood', 'cara.lee']
# [' Ava Stone ', ' Cara Lee ']❓ Frequently Asked Questions
Lesson complete — text columns hold no fear!
You can clean casing and whitespace, search and filter with contains, replace text, split fields into columns, and chain it all together. Remember that missing values turn into NaN, so reach for na=False when filtering.
Practice quiz
What does the .str accessor let you do?
- Sort a column
- Convert a column to numbers
- Apply string methods to every value in a text column
- Rename columns
Answer: Apply string methods to every value in a text column. The .str accessor applies Python string methods across the whole Series.
Which call lowercases every value in a text column?
- s.lower()
- s.str.toLowerCase()
- s.casefold()
- s.str.lower()
Answer: s.str.lower(). s.str.lower() applies lower() to each element of the Series.
What does .str.strip() remove?
- Leading and trailing whitespace
- All spaces inside the text
- Every vowel
- Numeric characters
Answer: Leading and trailing whitespace. .str.strip() trims whitespace from the start and end of each value.
What does .str.contains('gmail') return?
- The matching substring
- A boolean Series usable as a filter mask
- The count of matches
- A new column
Answer: A boolean Series usable as a filter mask. It returns a boolean Series, True where the substring appears.
How do you split one column into several columns?
- s.str.split(' ')
- s.str.partition()
- s.str.split(' ', expand=True)
- s.str.divide(' ')
Answer: s.str.split(' ', expand=True). expand=True returns a DataFrame with one column per split piece.
Why might .str.contains return NaN for some rows?
- The pattern was too long
- The column was numeric
- contains is deprecated
- Those rows held missing (NaN) values
Answer: Those rows held missing (NaN) values. String methods cannot operate on NaN, so they return NaN there.
Which argument treats missing rows as False in .str.contains?
- na=False
- skipna=True
- fillna=False
- dropna=True
Answer: na=False. na=False makes missing rows count as False for clean filtering.
Which method swaps text everywhere it appears in each value?
- s.str.swap(a, b)
- s.str.replace(a, b)
- s.str.sub(a, b)
- s.str.change(a, b)
Answer: s.str.replace(a, b). .str.replace(old, new) substitutes text in every value.
What does .str.len() return for a text column?
- The number of rows
- The longest value
- The unique values
- The character count of each value
Answer: The character count of each value. .str.len() gives the length of each string in the Series.
Why can you chain .str.strip().str.lower() in one line?
- Each .str call returns a Series you can keep operating on
- strip resets the index
- pandas caches the result
- lower must always follow strip
Answer: Each .str call returns a Series you can keep operating on. Because every .str method returns a Series, calls chain naturally.
Continue this course
- Previous: apply, map & applymap
- Next: Working with Dates & Times — Parse, extract, and resample datetime data with the .dt accessor
- Quick reference: Pandas cheat sheet › Transforming
Frequently asked questions
What is the .str accessor in pandas?
The .str accessor lets you call Python string methods on an entire column of text at once. Instead of looping over each value, you write something like df['name'].str.lower() and pandas applies lower() to every element and returns a new Series.
Why does .str.contains return NaN for some rows?
If a column has missing values (NaN), string methods cannot operate on them, so the result is NaN for those rows. For boolean methods like contains you can pass na=False to treat missing values as False, which is handy when filtering.
How do I split one column into several columns in pandas?
Use df['col'].str.split(separator, expand=True). The expand=True option returns a DataFrame with one column per piece, which you can then assign to new column names instead of getting a single column of lists.