Pandas String Case: lower, upper, title and casefold

Use Pandas string case methods to transform text or build consistent comparison keys. Choose the operation that suits the task, preserve original spelling where it matters, and check missing or unknown labels before grouping data.

Convert names to uppercase 🔝

Case methods change string values, not a web page’s font styling. upper() returns transformed values; assign the result if it should become a column.

import pandas as pd
df = pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex"]})
print(df["NAME"].str.upper())
assert df["NAME"].str.upper().tolist() == ["RAVI", "RAJU", "ALEX"]

Expected output

0    RAVI
1    RAJU
2    ALEX
Name: NAME, dtype: str

Convert names to lowercase 🔝

lower() converts cased letters to lowercase. It preserves punctuation, digits and whitespace.

df = pd.DataFrame({"NAME": ["Ravi", "Raju", "Alex"]})
print(df["NAME"].str.lower())

Expected output

0    ravi
1    raju
2    alex
Name: NAME, dtype: str

Use title case for word-style display 🔝

title() uppercases word beginnings and lowercases the remaining cased letters. It is a mechanical transformation, not a name or brand formatting rule.

df = pd.DataFrame({"NAME": ["ravi ron", "raju geek", "alex john"]})
print(df["NAME"].str.title())

Expected output

0     Ravi Ron
1    Raju Geek
2    Alex John
Name: NAME, dtype: str

Capitalize the beginning of each string 🔝

capitalize() changes the first character to title case and lowercases the remaining letters. It does not capitalize every word.

df = pd.DataFrame({"NAME": ["ravi ron", "raju geek", "alex john"]})
print(df["NAME"].str.capitalize())
assert df["NAME"].str.capitalize().iloc[0] == "Ravi ron"

Expected output

0     Ravi ron
1    Raju geek
2    Alex john
Name: NAME, dtype: str

Swap uppercase and lowercase 🔝

swapcase() exchanges the cases of cased characters. It is useful for demonstrations, but usually is not a business-data normalization rule. Unicode transformations need not be reversible.

df = pd.DataFrame({"NAME": ["Ravi Ron", "Raju Geek", "Alex John"]})
print(df["NAME"].str.swapcase())

Expected output

0     rAVI rON
1    rAJU gEEK
2    aLEX jOHN
Name: NAME, dtype: str

Compare all five methods on the same input 🔝

Acronyms and mixed-case brands can lose their intended form with title() or capitalize(). Preserve source spelling when names or branding matter.

labels = pd.Series(["NASA report", "iPhone case", "BLUE pen"], dtype="string")
comparison = pd.DataFrame({"source": labels})
for method in ["upper", "lower", "title", "capitalize", "swapcase"]:
    comparison[method] = getattr(labels.str, method)()
print(comparison)

Expected output

        source        upper        lower        title   capitalize     swapcase
0  NASA report  NASA REPORT  nasa report  Nasa Report  Nasa report  nasa REPORT
1  iPhone case  IPHONE CASE  iphone case  Iphone Case  Iphone case  IpHONE CASE
2     BLUE pen     BLUE PEN     blue pen     Blue Pen     Blue pen     blue PEN

Preserve missing values and inspect mixed types 🔝

Nullable string storage preserves missing values through case conversion. Converting a number to text is a policy choice, not a default cleaning step. Detect unexpected input types before casting if the field must contain text. See astype().

raw = pd.Series(["Alex", None, 120], dtype="object")
unexpected = raw.notna() & ~raw.map(lambda value: isinstance(value, str))
print("Unexpected values:", raw.loc[unexpected].tolist())
text = raw.astype("string")
print(text.str.lower())
assert text.str.lower().isna().sum() == 1

Expected output

Unexpected values: [120]
0    alex
1    <NA>
2     120
dtype: string

Normalize whitespace separately from case 🔝

Case conversion does not remove spaces. strip() removes surrounding whitespace; a regex replacement can collapse repeated interior whitespace. Keep the raw value for comparison; see str.replace().

labels = pd.Series(["  BLUE   pen ", "Notebook A5", None], dtype="string")
clean = labels.str.strip().str.replace(r"\s+", " ", regex=True).str.lower()
print(clean)
assert clean.iloc[0] == "blue pen"

Expected output

0       blue pen
1    notebook a5
2           <NA>
dtype: string

Use casefold for Unicode caseless comparison 🔝

casefold() removes case distinctions more aggressively than lower(), including the German sharp s. It is useful for comparison keys when the domain treats these spellings as equivalent. It is not locale-specific display formatting.

labels = pd.Series(["Straße", "STRASSE"], dtype="string")
print("Lower:", labels.str.lower().tolist())
print("Casefold:", labels.str.casefold().tolist())
assert labels.str.casefold().nunique() == 1

Expected output

Lower: ['straße', 'strasse']
Casefold: ['strasse', 'strasse']

Normalize Unicode composition before comparison 🔝

Visually similar text may use different Unicode sequences. NFC normalization followed by casefold can provide a comparison key. Do not treat this as proof that records describe the same entity.

labels = pd.Series(["CAFÉ", "cafe\u0301"], dtype="string")
keys = labels.str.normalize("NFC").str.casefold()
print(keys.tolist())
assert keys.nunique() == 1

Expected output

['café', 'café']

Practical workflow: map inconsistent sales regions 🔝

This synthetic extract contains case variations, spaces, a blank and an unknown label. Create a normalized key, then map a controlled vocabulary to canonical labels. Do not title-case every value and assume it belongs to an approved category.

sales = pd.DataFrame({"order_id": [101, 102, 103, 104, 105, 106], "region": [" NORTH ", "north", "South", "", None, "Northeast"], "amount": [10, 20, 30, 4, 5, 6]})
sales["region_key"] = sales["region"].astype("string").str.strip().str.casefold().replace("", pd.NA)
approved = {"north": "North", "south": "South"}
sales["canonical_region"] = sales["region_key"].map(approved).astype("string")
unknown = sales["region_key"].notna() & sales["canonical_region"].isna()
print(sales)
print("Unknown labels:", sales.loc[unknown, "region"].tolist())
assert sales.loc[unknown, "region"].tolist() == ["Northeast"]

Expected output

   order_id     region  amount region_key canonical_region
0       101     NORTH       10      north            North
1       102      north      20      north            North
2       103      South      30      south            South
3       104                  4       <NA>             <NA>
4       105        NaN       5       <NA>             <NA>
5       106  Northeast       6  northeast             <NA>
Unknown labels: ['Northeast']

Report missing and unmapped labels separately 🔝

An unknown label differs from a missing label. Keep both visible during aggregation instead of silently losing their rows. Case normalization should not alter amounts or remove legitimate orders. See count() and sum().

print("Missing region keys:", sales["region_key"].isna().sum())
print("Unknown labels:", unknown.sum())
print("Totals including review group:")
totals = sales.groupby("canonical_region", dropna=False)["amount"].sum()
print(totals)
assert totals.sum() == sales["amount"].sum()
# Optional Colab export:
# sales.to_csv("sales_region_review.csv", index=False)

Expected output

Missing region keys: 2
Unknown labels: 1
Totals including review group:
canonical_region
North    30
South    30
<NA>     15
Name: amount, dtype: int64

Review collisions created by normalized keys 🔝

Several raw labels can map to one key. This may be intended, but deduplicating whole records on that key would remove legitimate sales. Inspect variants before deciding; see drop_duplicates().

variants = sales.dropna(subset=["region_key"]).groupby("region_key")["region"].nunique()
print("Raw variants per normalized key:")
print(variants)
assert variants.loc["north"] == 2

Expected output

Raw variants per normalized key:
region_key
north        2
northeast    1
south        1
Name: region, dtype: int64

Filter case-insensitively without overwriting display text 🔝

For a search, str.contains() with case=False may be enough. There is no need to rewrite stored product names solely to perform a case-insensitive search.

products = pd.Series(["Blue Pen", "PEN refill", "Notebook", None], dtype="string")
mask = products.str.contains("pen", case=False, regex=False, na=False)
print(products.loc[mask].tolist())
assert products.iloc[0] == "Blue Pen"

Expected output

['Blue Pen', 'PEN refill']

Common questions and mistakes 🔝

Do these methods change font color? No; they change text values. Does lower() remove spaces? No; strip or replace whitespace separately. Why did NASA become Nasa? Title casing is mechanical and does not preserve acronyms. Can casefold affect length? Yes; some characters expand, so check str.len() after conversion when necessary. Should identifiers be lowercased? Only when their specification is case-insensitive; URLs and product codes may be case-sensitive.

Exercise: standardize payment statuses 🔝

Create statuses " PAID ", "paid", "Pending", None and "other". Normalize a comparison key, map paid and pending to canonical labels, and report the unknown non-missing label. Keep the original status values.

Exercise solution 🔝

Mapping a defined vocabulary keeps unknown input visible instead of assuming every title-cased value is valid.

orders = pd.DataFrame({"status": [" PAID ", "paid", "Pending", None, "other"]})
orders["key"] = orders["status"].astype("string").str.strip().str.casefold()
orders["canonical"] = orders["key"].map({"paid": "Paid", "pending": "Pending"}).astype("string")
unknown = orders["key"].notna() & orders["canonical"].isna()
print(orders)
print("Unknown:", orders.loc[unknown, "status"].tolist())
assert orders.loc[unknown, "status"].tolist() == ["other"]

Expected output

    status      key canonical
0    PAID      paid      Paid
1     paid     paid      Paid
2  Pending  pending   Pending
3      NaN     <NA>      <NA>
4    other    other      <NA>
Unknown: ['other']

Practice in Google Colab 🔝

Open in Google Colab View on GitHub
Run the examples in order, change source labels and compare normalized keys, canonical values and review rows. Save your own copy to keep edits. All sample tables are included in the notebook.

Continue with data cleaning, splitting text and string slicing.

Reference: Pandas string casefold documentation.




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer