Python for Data course menu
Cleaning Text Data in pandas with .str Methods
Clean messy text columns in pandas: strip spaces, change case, replace characters, split columns, extract values with regular expressions, check text with contains and startswith, and standardise inconsistent labels.
On this page
Text typed by people is inconsistent: extra spaces, mixed capitals, different spellings of the same thing. pandas gives every text column a .str accessor with vectorised versions of Python's string methods, applied to every row at once and skipping missing values.
Spaces and capitals#
import pandas as pd
cities = pd.Series([" Mumbai", "delhi ", "PUNE", " new delhi", "Mumbai"])
print(cities.str.strip().str.title().tolist())
print(cities.str.strip().str.lower().value_counts())['Mumbai', 'Delhi', 'Pune', 'New Delhi', 'Mumbai']
mumbai 2
delhi 1
pune 1
new delhi 1
Name: count, dtype: int64Without cleaning, " Mumbai" and "Mumbai" would count as different cities. strip, lower, upper and title fix most of this.
Replacing characters#
import pandas as pd
phones = pd.Series(["98450-12345", "(080) 2345 6789", "+91 99001 23456"])
digits = phones.str.replace(r"\D", "", regex=True) # \D = any non-digit
print(digits.tolist())
print(digits.str.len().tolist())
print(digits.str[-10:].tolist()) # last 10 digits['9845012345', '08023456789', '919900123456']
[10, 11, 12]
['9845012345', '8023456789', '9900123456']With regex=True, the pattern is a regular expression. .str[...] slices every string.
Splitting into columns#
import pandas as pd
people = pd.DataFrame({"full_name": ["Asha Rao", "Ravi Kumar", "Meera"],
"email": ["asha@shop.in", "ravi@mail.com", "meera@shop.in"]})
people[["first", "last"]] = people["full_name"].str.split(" ", n=1, expand=True)
people["domain"] = people["email"].str.split("@").str[1]
print(people) full_name email first last domain
0 Asha Rao asha@shop.in Asha Rao shop.in
1 Ravi Kumar ravi@mail.com Ravi Kumar mail.com
2 Meera meera@shop.in Meera None shop.inexpand=True returns separate columns. "Meera" has no surname, so that cell is missing (None). n=1 splits only at the first space, keeping names like "Ravi Kumar Singh" as first name plus the rest.
Extracting with patterns#
str.extract pulls out the part of each string that matches a regular expression group (the part in parentheses):
import pandas as pd
items = pd.Series(["Rice 5kg", "Oil 1L", "Sugar 2kg", "Salt"])
size = items.str.extract(r"(\d+)\s*(kg|L)")
size.columns = ["amount", "unit"]
print(pd.concat([items.rename("item"), size], axis=1))
codes = pd.Series(["ORD-2026-0045", "ORD-2025-1102", "REF-9"])
print(codes.str.extract(r"ORD-(\d{4})-\d+")[0].tolist()) item amount unit
0 Rice 5kg 5 kg
1 Oil 1L 1 L
2 Sugar 2kg 2 kg
3 Salt NaN NaN
['2026', '2025', nan]Rows that do not match get NaN.
Checking text#
import pandas as pd
emails = pd.Series(["asha@shop.in", "ravi(at)mail.com", "meera@shop.in", None])
valid = emails.str.contains("@", na=False)
print(valid.tolist())
print(emails.str.endswith(".in", na=False).sum(), "Indian domains")
print(emails[~valid].tolist())[True, False, True, False]
2 Indian domains
['ravi(at)mail.com', None]na=False treats missing values as "does not match"; without it the result contains missing values and cannot be used directly as a filter.
Standardising labels#
The same category is often written several ways. Clean the text, then map the variants to one label:
import pandas as pd
payment = pd.Series(["UPI", "upi ", "Card", "credit card", "CASH", "Debit Card", "G-Pay"])
cleaned = payment.str.strip().str.lower()
standard = cleaned.replace({
"credit card": "card",
"debit card": "card",
"g-pay": "upi",
})
print(standard.value_counts())upi 3
card 3
cash 1
Name: count, dtype: int64After cleaning, seven messy values reduce to three real payment methods.
Common mistakes#
Practice#
Practice exercise
Product codes look like [" ab-101 ", "CD-202", "ab-303", " EF-404"]. Clean them to uppercase without spaces, then create a prefix column (the letters) and a number column (as integers). Print the table and how many codes start with "AB".
Show hint
str.strip().str.upper(), then str.split("-", expand=True). Convert the number part with astype(int).
Show solution
import pandas as pd
codes = pd.Series([" ab-101 ", "CD-202", "ab-303", " EF-404"]).str.strip().str.upper()
table = codes.str.split("-", expand=True)
table.columns = ["prefix", "number"]
table["number"] = table["number"].astype(int)
table.insert(0, "code", codes)
print(table)
print(codes.str.startswith("AB").sum()) code prefix number
0 AB-101 AB 101
1 CD-202 CD 202
2 AB-303 AB 303
3 EF-404 EF 404
2Key takeaway#
Next: Duplicates and Outliers.
Finished this lesson?