Skip to content

Python/Data Cleanup

Python pandas recipes for cleaning business data

Practical pandas patterns for loading, standardising, deduplicating, and validating business data without hiding problems.

Talha Ansari··5 min read

Key takeaways

  • Load with explicit types and date parsing so pandas does not guess silently.
  • Inspect duplicates and missing values before choosing how to handle them.
  • Keep raw columns or raw files so every change can be traced.
  • Validate row counts, uniqueness, and totals before exporting the cleaned data.
On this page

Load with intent

Start by reading only the columns and rows needed. Use read_csv with usecols, dtype, and parse_dates where the schema is known. Explicit types prevent postcodes losing leading zeros and dates being interpreted differently on another machine.

Inspect the result with shape, head, dtypes, and info before transforming anything. A quick profile of null counts and distinct values will reveal most of the problems. If the file is large, read a sample first and confirm the delimiter, encoding, and header row. Record the source file name, extract date, and row count in the output or log, so a later discrepancy can be traced.

Load a business export with explicit typesPython
import pandas as pd

sales = pd.read_csv(
    "sales.csv",
    usecols=["invoice_id", "line_id", "invoice_date", "customer_id", "amount"],
    dtype={"invoice_id": "string", "customer_id": "string"},
    parse_dates=["invoice_date"],
)

print(sales.shape)
print(sales.dtypes)
print(sales.isna().sum().sort_values(ascending=False).head(10))

Standardise names and types

Rename columns to consistent snake_case, strip whitespace, and remove stray punctuation. Standardise category labels before grouping, because 'North', ' north', and 'NORTH' will otherwise become three segments. Keep a mapping table for aliases rather than burying replacements in a long script.

Convert dates deliberately. Where formats vary, parse with an explicit format, then use errors='coerce' and inspect the rows that failed. For money, avoid binary floating-point surprises; store minor units as integers or use a decimal type when exact arithmetic matters.

Deal with duplicates

Define the key that should be unique: invoice number, order line, customer ID plus date, or whatever the business uses. Then inspect duplicates with duplicated(subset=[...], keep=False) before dropping anything. Some repeated rows are legitimate line items.

When duplicates are genuine, choose keep='first', keep='last', or a rule based on updated_at. Record how many rows were removed and why. A silent drop_duplicates call is one of the easiest ways to lose revenue from a report. If duplicates must be aggregated rather than removed, make the aggregation rule explicit and test it against a known total.

Inspect duplicates before removing anythingPython
key = ["invoice_id", "line_id"]
duplicates = sales[sales.duplicated(key, keep=False)]

print(f"Rows: {len(sales):,}")
print(f"Duplicate-key rows: {len(duplicates):,}")
print(duplicates.sort_values(key).head(20))

clean = sales.drop_duplicates(subset=key, keep="first").copy()
print(f"Rows after review: {len(clean):,}")

Handle missing values deliberately

Distinguish a missing value from zero, not applicable, and unknown. Each has a different meaning and a different effect on an average. Use isna().sum() and inspect patterns by segment, because missingness is often concentrated in one source or period.

Filling every blank with zero or the median changes the distribution and can create false confidence. Drop rows only when the missing field is essential and the loss is documented. Otherwise keep a flag column so downstream users can see what was imputed. For important fields, report the missing rate alongside the result so readers can judge how much the finding depends on imputation.

Clean text and categories

Use str.strip(), str.lower(), and str.replace() for routine text cleanup, then map known variants to a controlled list. Normalise whitespace and watch for non-breaking spaces that look identical in a spreadsheet. Where encoding is uncertain, read the raw bytes or specify it explicitly.

Keep the original column alongside the cleaned one during development, then decide whether to retain it in the output. A category dtype can save memory and make valid values explicit, but it must be refreshed when new categories appear.

Validate the result

Check row counts before and after each step, uniqueness on the intended key, date ranges, and numeric bounds. Reconcile an important total with the source system or a trusted report. If a merge is involved, use validate='one-to-many' or the appropriate rule and indicator=True to catch unmatched rows.

Export the cleaned data to a stable format such as Parquet and write a short log of changes. Keep the script under version control and test it against a small fixture. The aim is not a perfect one-off file but a repeatable process that fails loudly when the input changes. A small test dataset with deliberate errors is useful for checking that the script catches what it should.

Validate before publishing the cleaned dataPython
assert clean["invoice_id"].notna().all()
assert clean["invoice_date"].between("2026-01-01", "2026-12-31").all()
assert not clean.duplicated(["invoice_id", "line_id"]).any()

source_total = round(sales["amount"].sum(), 2)
clean_total = round(clean["amount"].sum(), 2)
if source_total != clean_total:
    raise ValueError(f"Total changed: {source_total} -> {clean_total}")

clean.to_parquet("output/sales.parquet", index=False)

Frequently asked questions

How should dates in multiple formats be handled?+

Parse known formats explicitly, then coerce the rest and inspect the failures. Guessing with a single format can silently produce wrong dates. Keep the original value until the parse is verified.

Should data be cleaned in pandas or SQL?+

Use SQL when the source is a database and the transformations are set-based. Use pandas for files, complex text handling, and exploratory work. Avoid maintaining the same logic in both places.

How can a cleaning script be made robust?+

Validate the schema on load, fail with a clear message when columns or types change, test on a representative sample, and keep dependencies pinned. A script that stops is safer than one that publishes bad data.

How should a cleaning script be documented?+

Keep a short log of the source, row counts, rules applied, exclusions, and known issues. Point to the version-controlled script rather than pasting code into a document that will drift.

Need help applying this?

Improve your data cleanup & pipelines.

Review the service scope or tell us what you are trying to improve. We will reply with a clear next step.

Free KPI definition template

Related reading

Python·5 min read

Automating CSV cleanup with Python

A practical approach to cleaning CSV files with Python: explicit read settings, standardised names, duplicate rules, rejected rows, and repeatable output.

Read