← All cheatsheets
Data Analyst · #030 · August 22, 2026 · 2 min read

How do you clean a dataset before analysis?

Five rows that look fine and hide four problems: a soft duplicate, two date formats, a fake null and a text number. The 26-point cleaning pass that catches what a quick glance never does.

Get the free PDF

One page, print-ready, free to share. No signup needed.

Download the PDF

This table looks fine. It is not. Five rows from sales_export_final_v2.csv: an "Ada Lovelace" and an "ada lovelace" that are the same person, a 2026-03-14 next to a 14/03/2026, an amount of N/A, an empty amount, and a 1,204 that is a string. Four problems in five rows, and a quick glance catches none of them.

The fix is not talent, it is a checklist. Here is the 26-point pass.

1. Structure

  • Row count: matches the source system?
  • One row = one entity, stated out loud.
  • Id unique: COUNT(*) = COUNT(DISTINCT id).
  • Dtypes: numbers numeric, dates dates.

2. Missing values

  • Real NULLs: count them per column.
  • Fake NULLs: N/A, "", "none", 0, -999.
  • Pattern: random, or one source/period?
  • The rule: drop, impute or flag: write it down.

3. Duplicates

Exact duplicates are easy. The soft ones are the ones that survive:

SELECT LOWER(TRIM(customer)) AS c,
       COUNT(*) AS n
FROM sales
GROUP BY 1
HAVING COUNT(*) > 1
ORDER BY n DESC;

Normalize first, then group. "Ada Lovelace" and "ada lovelace " are the same person. Also check the business key (same email, two customer ids), and document the keep rule: latest? richest?

4. Values and types

  • Ranges: age 214, amount -50: possible?
  • Categories: FR, fr, France = one value.
  • String numbers: strip "1,204" and "$" then cast.
  • Units: cents or euros? k or absolute?
  • Outliers: inspect before deleting. Ever.

5. Dates

  • One format: parse all to ISO before anything.
  • Impossible: signups in 1970 or next year.
  • Timezones: UTC or local? Pick and convert.
  • Gaps: missing days = missing loads.

6. Sanity

  • Totals: reconcile one number to the source.
  • Eyeball 20: random rows, read them slowly.
  • Log it: rows in, rows out, rows dropped and why.
  • Rerunnable: script, not hand edits in Excel.

The trap: the silent cast

The column loaded fine. As text.

Looks likeActually isSo
1,204a stringSUM() returns garbage or errors
N/Aa stringnever counted as missing
007a stringjoins fail against the int 7
14/03/2026a stringsorts as text: April before March

One df.dtypes or one information_schema look costs ten seconds and catches all four.

The takeaway

Cleaning is not a chore before the analysis. It usually IS the analysis: the fake nulls, soft duplicates and silent casts you catch here are exactly the errors that would have shipped in the chart. The full 26-point checklist is a print-ready PDF above. Run it on every dataset before the first chart.

Frequently asked questions

What should I check first when cleaning a dataset?
Structure: does the row count match the source system, what does one row represent (say it out loud), is the id actually unique (COUNT(*) = COUNT(DISTINCT id)), and are the dtypes right, meaning numbers are numeric and dates are dates. Everything downstream depends on these four.
What are fake nulls in data?
Values that mean missing but are not NULL: the strings N/A, empty string and none, plus sentinel numbers like 0 and -999. They pass every IS NULL check, get counted and summed as real values, and quietly poison averages. Count real NULLs per column, then hunt the fakes separately.
How do you find duplicate records that are not exact duplicates?
Normalize before comparing: TRIM and LOWER the key column, then GROUP BY it and keep groups with COUNT(*) > 1. That catches soft duplicates like Ada Lovelace and ada lovelace with a trailing space. Also check business keys: the same email under two customer ids is a duplicate no row comparison will find.
Why does SUM return the wrong result on a numeric-looking column?
Because the column loaded as text. A value like 1,204 with a thousands separator is a string, so SUM() returns garbage or errors, 007 fails to join against the integer 7, and 14/03/2026 sorts as text with April before March. One look at df.dtypes or information_schema costs ten seconds and catches all four.

Get the free PDF

One page, print-ready, free to share. No signup needed.

Download the PDF

More cheatsheets