Introduction
Data cleaning is one of the most important — and most time-consuming — steps in data analysis. Real-world data is often messy:
Missing Values → Duplicate Records → Incorrect Data Types → Inconsistent Values → Clean Dataset → Reliable Analysis
In this chapter, you’ll learn how to:
- Identify missing values
- Handle missing values
- Find and remove duplicates
- Correct incorrect data
- Convert data types
- Standardize text values
1. Create a Messy Dataset
Let’s create a dataset containing common data problems:
import pandas as pd
import numpy as np
data = {
"Name": ["Amit", "Priya", "Rahul", "Sneha", "Amit", "Vikram"],
"Department": ["IT", "HR", "Finance", "it", "IT", "Sales"],
"Salary": [75000, 60000, np.nan, 90000, 75000, -5000],
"Experience": [5, 3, 7, np.nan, 5, 4],
"Email": [
"amit@example.com",
"priya@example.com",
None,
"sneha@example.com",
"amit@example.com",
"vikram@example.com"
]
}
df = pd.DataFrame(data)
df
This dataset intentionally contains several common problems:
| Problem | Example |
|---|---|
| Missing salary | NaN |
| Missing experience | NaN |
| Missing email | None |
| Duplicate record | Amit appears twice |
| Inconsistent text | “IT” and “it” |
| Incorrect value | Salary = -5000 |
2. Identify Missing Values
Check whether values are missing:
df.isnull()
This returns True for missing values and False where a value exists.
Count missing values:
df.isnull().sum()
Example output:
Name 0
Department 0
Salary 1
Experience 1
Email 1
This is one of the most useful commands in data cleaning — it’s usually the very first thing you run on any new dataset.
Alternative (identical) syntax:
df.isna().sum()
isna() and isnull() are equivalent in Pandas — isna() is technically the “canonical” name, but both work everywhere.
3. Handling Missing Values
There are several strategies, depending on the situation:
Option 1: Remove rows
df.dropna()
This removes any row containing a missing value. Use with caution — you may lose valuable data if it removes more rows than expected.
Option 2: Fill missing numerical values
For salary, use the average:
df["Salary"] = df["Salary"].fillna(df["Salary"].mean())
For experience, use the median:
df["Experience"] = df["Experience"].fillna(df["Experience"].median())
Mean vs. Median — why the difference?
- Mean = Sum of values ÷ number of values
- Median = the middle value when data is sorted
For data with extreme outliers, the median is usually the safer choice, since a single very large or very small value can pull the mean far from what’s “typical.”
Option 3: Fill missing text values
For a missing email:
df["Email"] = df["Email"].fillna("Not Available")
Or for department:
df["Department"] = df["Department"].fillna("Unknown")
4. Find Duplicate Records
Check for duplicates:
df.duplicated()
Count duplicates:
df.duplicated().sum()
Remove duplicate rows:
df = df.drop_duplicates()
Important: df.drop_duplicates() returns a cleaned copy — it doesn’t modify df in place. You must reassign it (df = df.drop_duplicates()) for the change to stick, which is a common trap for beginners.
5. Standardize Text Values
Our dataset contains both "IT" and "it" — technically different strings to Python, even though they mean the same thing to a human.
Convert everything to uppercase:
df["Department"] = df["Department"].str.upper()
Now both become "IT".
You can also use lowercase or title case:
df["Department"] = df["Department"].str.lower()
df["Department"] = df["Department"].str.title()
6. Detect Incorrect Values
Our data contains Salary = -5000 — a negative salary is almost certainly invalid data (perhaps a data-entry error or a placeholder for “unknown”).
Find negative salaries:
df[df["Salary"] < 0]
Replace negative salaries with missing values, so they can be handled properly:
df.loc[df["Salary"] < 0, "Salary"] = np.nan
Now fill the missing value:
df["Salary"] = df["Salary"].fillna(df["Salary"].median())
7. Correct Incorrect Data Types
Check data types:
df.dtypes
Example:
Name object
Department object
Salary float64
Experience float64
Email object
If a column should be numeric but isn’t, convert it:
df["Salary"] = pd.to_numeric(df["Salary"], errors="coerce")
What does errors="coerce" do?
If Pandas encounters a value it can’t convert to a number, it replaces it with NaN instead of crashing the whole operation. For example:
pd.to_numeric(["50000", "60000", "invalid"], errors="coerce")
Result: 50000, 60000, NaN
8. A Complete Data Cleaning Workflow
Putting it all together — recreate the messy data, then clean it step by step:
import pandas as pd
import numpy as np
data = {
"Name": ["Amit", "Priya", "Rahul", "Sneha", "Amit", "Vikram"],
"Department": ["IT", "HR", "Finance", "it", "IT", "Sales"],
"Salary": [75000, 60000, np.nan, 90000, 75000, -5000],
"Experience": [5, 3, 7, np.nan, 5, 4],
"Email": [
"amit@example.com",
"priya@example.com",
None,
"sneha@example.com",
"amit@example.com",
"vikram@example.com"
]
}
df = pd.DataFrame(data)
print("Original Data:")
display(df)
Step 1: Remove duplicate records
df = df.drop_duplicates()
Step 2: Standardize department names
df["Department"] = df["Department"].str.upper()
Step 3: Detect invalid salary values
df.loc[df["Salary"] < 0, "Salary"] = np.nan
Step 4: Fill missing salary values
df["Salary"] = df["Salary"].fillna(df["Salary"].median())
Step 5: Fill missing experience
df["Experience"] = df["Experience"].fillna(df["Experience"].median())
Step 6: Fill missing email
df["Email"] = df["Email"].fillna("Not Available")
Step 7: Check the cleaned data
display(df)
Step 8: Verify no missing values remain
df.isnull().sum()
Before and After
Before cleaning: Amit (IT, 75000), Priya (HR, 60000), Rahul (Finance, NaN), Sneha (it, 90000), Amit (IT, 75000) ← duplicate, Vikram (Sales, -5000) ← invalid
After cleaning: Amit (IT, 75000), Priya (HR, 60000), Rahul (FINANCE, median salary), Sneha (IT, 90000), Vikram (SALES, median salary)
Quick Reference: Data Cleaning Functions
| Function | Purpose |
|---|---|
isnull() / isna() | Find missing values |
fillna() | Fill missing values |
dropna() | Remove rows with missing values |
duplicated() | Find duplicate rows |
drop_duplicates() | Remove duplicates |
astype() | Change data type |
pd.to_numeric() | Convert values to numbers |
.str.upper() / .str.lower() | Standardize text case |
.str.strip() | Remove extra spaces |
Practice Exercise
data = {
"Name": ["Amit", "Priya", "Rahul", "Sneha", "Amit"],
"Age": [25, 30, None, 28, 25],
"City": ["Mumbai", "Delhi", "mumbai", None, "Mumbai"],
"Salary": [50000, 60000, -10000, 70000, 50000]
}
df = pd.DataFrame(data)
Try to: find missing values, remove the duplicate Amit record, standardize city names, replace the invalid salary with NaN, fill missing age with the median, fill missing city with “Unknown,” and calculate the average salary.
