Data Analytics
Hands-onBeginner 40 min· Analyst foundations · 4 of 10

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.

messy.csv
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

ProblemExample hereTypical fix
DuplicatesOrder 1004 appears twiceRemove exact duplicates (after checking they are really duplicates)
Extra spaces" Asha "Trim whitespace
Inconsistent spelling or caseravi, cappuccino, BENGALURUStandardise case; map variants to one value
Wrong type"₹180" in the price columnRemove symbols and convert to a number
Mixed date formats01/10/2026 and 04-10-2026Parse each format into a real date
Missing valuesqty and city blankDecide: fill, flag or drop
Impossible valuesqty of -1Investigate, 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

inspect.py
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 rows

info() 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.

clean.py
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.

What you should see
 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         130

Why each step is there

The reasoning behind the fixes

  1. 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.
  2. 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 and str.title() standardises the case.
  3. 3Map variants: Bangalore and Bengaluru are one city. replace maps known variants. Check value_counts() afterwards for any variant you missed.
  4. 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.
  5. 5Parse dates explicitly: giving a format stops pandas guessing wrongly. Trying two formats and combining with fillna handles a mixed column.
  6. 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.
  7. 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

TaskExcel or Google Sheets
Remove spaces=TRIM(B2)
Fix case=PROPER(B2) for names; =LOWER(B2) or =UPPER(B2)
Replace variantsFind and replace (Ctrl+H), or =SUBSTITUTE(G2,"Bangalore","Bengaluru")
Remove a symbol=VALUE(SUBSTITUTE(F2,"₹",""))
Split textData, Text to columns
Remove duplicatesData, Remove duplicates (work on a copy)
Find blanksFilter the column and select (Blanks)
Build a real date=DATE(2026,10,1) from separate day, month and year cells
Prompt · Ask AI to review a cleaning plan
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

  1. Profile data first: info(), missing counts, duplicate counts and value lists.
  2. Typical problems are duplicates, stray spaces, inconsistent spellings, wrong types, mixed date formats, missing and impossible values.
  3. Script your cleaning so it is repeatable and visible; never edit the original file.
  4. Missing values are a judgement call: fill, flag or drop, and write down the assumption.
  5. Parse dates with an explicit format; day-first versus month-first mistakes are silent.
  6. 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.

Start free