Cleaning messy data
Find and fix duplicates, stray spaces, mixed date formats, wrong types and missing values in a messy file using pandas, and learn the spreadsheet equivalents and the judgement calls behind each fix.
Real data is never tidy. Names have extra spaces, dates come in two formats, a price has a rupee sign in it, the same order is entered twice. If you analyse such data without cleaning it, your totals are wrong and nobody notices. Analysts often spend more time cleaning than analysing, so being good at it is a real advantage.
In this tutorial you will find and fix seven typical problems in a small messy Brew & Bean export, first by looking, then with Python and pandas. The same ideas work in Excel and Google Sheets, and the equivalents are listed too. You need Python 3 with pandas installed (pip install pandas) and a text editor. If you have not used Python, read the steps with the spreadsheet equivalents and come back later.
The messy file
Save the text below as messy.csv (encoding UTF-8) in a new folder. Read it carefully first. Before you run any code, try to spot the problems yourself: that habit is what makes an analyst good.
order_id,customer_name,order_date,item,qty,price,city
1001, Asha ,01/10/2026,Cappuccino,2,180,Bengaluru
1002,ravi,02/10/2026,cappuccino,1,"₹180",bangalore
1003,Meena,03/10/2026,Cold Brew,,220,Bengaluru
1004,Kiran,04-10-2026,masala chai,2,90,Bengaluru
1004,Kiran,04-10-2026,masala chai,2,90,Bengaluru
1005,Divya,05/10/2026,Espresso,-1,120,BENGALURU
1006,Asha,06/10/2026,Banana Bread,1,130,7 data rows. Problems: extra spaces around Asha; lower-case names and items; a rupee sign in a price; Bangalore and Bengaluru and BENGALURU for the same city; two date formats; an exact duplicate (order 1004); a missing qty (1003); a negative qty (1005); a missing city (1006).
Common data problems
| Problem | Example here | Typical fix |
|---|---|---|
| Duplicates | Order 1004 appears twice | Remove exact duplicates (after checking they are really duplicates) |
| Extra spaces | " Asha " | Trim whitespace |
| Inconsistent spelling or case | ravi, cappuccino, BENGALURU | Standardise case; map variants to one value |
| Wrong type | "₹180" in the price column | Remove symbols and convert to a number |
| Mixed date formats | 01/10/2026 and 04-10-2026 | Parse each format into a real date |
| Missing values | qty and city blank | Decide: fill, flag or drop |
| Impossible values | qty of -1 | Investigate, then fix or exclude |
Day first or month first?
01/10/2026 means 1 October in India and the UK but 10 January in the USA. Wrong guesses silently swap days and months. Always ask or check with a date that cannot be ambiguous (such as 25/10/2026), and give the format explicitly when you parse.
Step 1: look before you touch
import pandas as pd
df = pd.read_csv("messy.csv", encoding="utf-8")
df.info() # column types and non-null counts
print(df.isna().sum()) # missing values per column
print(df.duplicated().sum()) # number of exact duplicate rowsinfo() shows 7 rows, qty has 6 non-null values and city has 6, and price and order_date are text, not numbers or dates. isna().sum() shows 1 missing in qty and 1 in city. duplicated().sum() prints 1. In an older pandas the type may be called object instead of str.
Profile first
Always run info(), isna().sum(), duplicated().sum() and a look at unique values (for example df['city'].value_counts()) on every new dataset. Ten minutes of profiling saves hours of wrong answers. Spreadsheet equivalent: filter each column and read the list of distinct values, and use Data, Remove duplicates (after saving a copy).
Step 2: clean it
The script below does every fix in order and explains each choice in a comment. Keep it as a script (not manual edits) so you can rerun it when a new file arrives, and so a colleague can see exactly what you changed. Save it as clean.py next to messy.csv.
import pandas as pd
df = pd.read_csv("messy.csv", encoding="utf-8")
# 1. Remove exact duplicate rows
df = df.drop_duplicates()
# 2. Fix text: trim spaces and make capitalisation consistent
df["customer_name"] = df["customer_name"].str.strip().str.title()
df["item"] = df["item"].str.strip().str.title()
df["city"] = df["city"].str.strip().str.title().replace({"Bangalore": "Bengaluru"})
# 3. Fix types: price has a rupee sign in one row
df["price"] = pd.to_numeric(
df["price"].astype(str).str.replace("₹", "", regex=False), errors="coerce"
)
# 4. Parse dates written in two different formats
d1 = pd.to_datetime(df["order_date"], format="%d/%m/%Y", errors="coerce")
d2 = pd.to_datetime(df["order_date"], format="%d-%m-%Y", errors="coerce")
df["order_date"] = d1.fillna(d2)
# 5. Missing and impossible values
df["qty"] = df["qty"].fillna(1) # assumption: a missing qty means 1
df = df[df["qty"] > 0].copy() # drop the row with qty -1
df["qty"] = df["qty"].astype(int)
df["city"] = df["city"].fillna("Unknown")
# 6. Add a calculated column and save
df["line_total"] = df["qty"] * df["price"]
df.to_csv("clean.csv", index=False)
print(df.to_string(index=False))Prints 5 clean rows (orders 1001, 1002, 1003, 1004 and 1006) with columns order_id, customer_name, order_date, item, qty, price, city, line_total. Line totals are 360, 180, 220, 180 and 130. Order 1006 has city Unknown.
order_id customer_name order_date item qty price city line_total
1001 Asha 2026-10-01 Cappuccino 2 180 Bengaluru 360
1002 Ravi 2026-10-02 Cappuccino 1 180 Bengaluru 180
1003 Meena 2026-10-03 Cold Brew 1 220 Bengaluru 220
1004 Kiran 2026-10-04 Masala Chai 2 90 Bengaluru 180
1006 Asha 2026-10-06 Banana Bread 1 130 Unknown 130Why each step is there
The reasoning behind the fixes
- 1Duplicates: counting order 1004 twice would overstate revenue.
drop_duplicates()removes rows identical in every column. If two rows share an order_id but differ elsewhere, they are not exact duplicates; investigate instead of deleting. - 2Trim and standardise text: "Asha " and "Asha" would count as two customers and "cappuccino" and "Cappuccino" as two items in any grouped total.
str.strip()removes spaces andstr.title()standardises the case. - 3Map variants: Bangalore and Bengaluru are one city.
replacemaps known variants. Checkvalue_counts()afterwards for any variant you missed. - 4Convert types: you cannot add "₹180". Remove the symbol, then
pd.to_numeric(..., errors="coerce")turns anything unparseable into NaN, which you can then inspect rather than crash on. - 5Parse dates explicitly: giving a
formatstops pandas guessing wrongly. Trying two formats and combining withfillnahandles a mixed column. - 6Handle missing and impossible values: this is a judgement call. A blank qty was filled with 1 here, which is an assumption you must write down. A negative qty was dropped because a negative quantity could be a refund or a typing error; in real work you would ask the owner of the data.
- 7Derive columns last: compute line_total only after qty and price are clean.
Judgement: fill, flag or drop?
There is no single right way to treat missing values. Dropping rows loses information and can bias results if the missing rows are different from the rest. Filling with a guess can invent facts. A safer habit: keep a flag column such as qty_was_missing, mention the choice in your report, and compare results with and without those rows. If only a few percent of rows are affected, the choice rarely matters. If many are, fix the source of the problem.
Never clean silently or in place
Keep the original file untouched, write cleaned data to a new file, and note what you changed and how many rows it affected (for example "removed 1 duplicate row, dropped 1 row with negative qty"). Cleaning decisions are part of the analysis and your reader should be able to challenge them.
The same cleaning in a spreadsheet
Spreadsheet equivalents
| Task | Excel or Google Sheets |
|---|---|
| Remove spaces | =TRIM(B2) |
| Fix case | =PROPER(B2) for names; =LOWER(B2) or =UPPER(B2) |
| Replace variants | Find and replace (Ctrl+H), or =SUBSTITUTE(G2,"Bangalore","Bengaluru") |
| Remove a symbol | =VALUE(SUBSTITUTE(F2,"₹","")) |
| Split text | Data, Text to columns |
| Remove duplicates | Data, Remove duplicates (work on a copy) |
| Find blanks | Filter the column and select (Blanks) |
| Build a real date | =DATE(2026,10,1) from separate day, month and year cells |
I am cleaning a dataset of [describe it, e.g. coffee shop orders]. Here are the column names and 5 sample rows (no personal data):
[paste columns and rows]
List the data quality problems you can see, ranked by how much they could change totals. For each, suggest a pandas fix and tell me which assumptions I must confirm with the data owner. Do not invent values.What to expect: A good answer lists concrete issues from your sample (spaces, formats, duplicates) and flags assumptions such as how to treat blanks. Do not paste real customer names, phone numbers or other personal data into AI tools; use fake or anonymised rows.
Try it: Clean a second file and verify
1. Add two more rows to messy.csv: one with the city written as bengaluru (lower case, trailing space) and one with price Rs. 90. Rerun and fix whatever breaks.
2. After cleaning, check that df['city'].value_counts() shows only Bengaluru and Unknown.
3. Print the revenue per item with df.groupby('item')['line_total'].sum() and confirm by hand that Cappuccino is 540.
Key takeaways
- Profile data first: info(), missing counts, duplicate counts and value lists.
- Typical problems are duplicates, stray spaces, inconsistent spellings, wrong types, mixed date formats, missing and impossible values.
- Script your cleaning so it is repeatable and visible; never edit the original file.
- Missing values are a judgement call: fill, flag or drop, and write down the assumption.
- Parse dates with an explicit format; day-first versus month-first mistakes are silent.
- Report how many rows each fix touched.
Check yourself
Finishing the quiz marks the lesson as done.
1. Two rows are identical except for the qty column, and share the same order_id. What should you do?
2. Why does "Asha " with a trailing space cause a problem?
3. What does errors="coerce" do in pd.to_numeric?
4. Which is the safest way to handle a missing qty?
5. 01/10/2026 in an Indian file most likely means which date?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.