Data Analysis with pandas · pandas Fundamentals

Cleaning Messy Data

Handle missing values, duplicates, inconsistent text and wrong types: the cleaning steps almost every real dataset needs.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

A cleaning pipeline for a customer export with every common problem: inconsistent capitalisation and spaces, duplicate rows, missing values, numbers stored as text with currency symbols, and invalid dates.

The idea

  • Missing values (NaN, NaT, None): find with isna(), fill with fillna(), or drop with dropna(); decide per column.
  • Text cleanup with the .str accessor: strip(), lower(), replace().
  • Types: pd.to_numeric(..., errors="coerce") and pd.to_datetime(..., errors="coerce") turn bad values into missing values instead of crashing.
  • Duplicates: duplicated() and drop_duplicates(subset=...).

The code

From messy to cleanTry it yourself
from io import StringIO
import pandas as pd

raw = pd.read_csv(StringIO('''email,country,spent,signup
 [email protected] ,de,€120,2026-01-03
[email protected],US,€45.5,2026-01-05
[email protected],DE,€120,2026-01-03
[email protected],,unknown,2026-02-30
[email protected],fr,€80,
'''))
print("missing per column:", raw.isna().sum().to_dict())

clean = (
    raw.assign(
        email=raw["email"].str.strip().str.lower(),
        country=raw["country"].str.upper().fillna("UNKNOWN"),
        spent=pd.to_numeric(raw["spent"].str.replace("€", ""), errors="coerce"),
        signup=pd.to_datetime(raw["signup"], errors="coerce"),
    )
    .drop_duplicates(subset="email")
    .reset_index(drop=True)
)
print(clean)
print(clean.dtypes)

Output

missing per column: {'email': 0, 'country': 1, 'spent': 0, 'signup': 1}
              email  country  spent     signup
0   [email protected]       DE  120.0 2026-01-03
1   [email protected]       US   45.5 2026-01-05
2  [email protected]  UNKNOWN    NaN        NaT
3  [email protected]       FR   80.0        NaT
email                 str
country               str
spent             float64
signup     datetime64[us]
dtype: object

Common mistakes

  • dropna() on the whole table, silently deleting rows that are only missing an unimportant column.
  • Filling missing numbers with 0 when "unknown" isn't the same as "zero"; it skews averages.
  • Removing duplicates before normalising text, so [email protected] and [email protected] both survive.
  • Using errors="ignore", which leaves the column as text; use "coerce" and inspect the resulting NaNs.

Key points

  • Count missing values per column and decide how to handle each.
  • Normalise text before deduplicating.
  • Convert types with errors="coerce" and check what became missing.

Further reading: Working with missing data · Working with text data

Practice questions

What does errors='coerce' do in pd.to_numeric?

Answer

Values that can't be converted become NaN instead of raising an error, so you can count and inspect them.

Why normalise text before drop_duplicates?

Answer

Values that differ only in case or whitespace are different strings, so duplicates survive unless you normalise first.

When is filling missing values with 0 wrong?

Answer

When missing means unknown rather than zero; it biases sums and averages. Fill with a sensible value or keep NaN and handle it explicitly.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?