Module 4

Data Handling & Preprocessing

The most important step in machine learning: turning messy, real-world data into a masterpiece.

Welcome to what many data scientists consider the most critical part of any machine learning project. While building and training models is exciting, the truth is that **up to 80% of a data scientist's time is spent on data preparation.** Why? Because of a fundamental rule in AI: **"Garbage In, Garbage Out."**

Think of it like cooking. You can have the best recipe in the world (the ML algorithm) and the most advanced oven (the computer), but if your ingredients (the data) are rotten, dirty, or unprepared, the final dish will be terrible. This module is your "kitchen prep" course. You'll learn how to wash, chop, and prepare your raw data so that your machine learning models can produce amazing results.

Anatomy of a Dataset: Features, Labels, and Splits

Before we start cleaning, we need to understand the basic terminology for our data's structure and purpose. Let's use our familiar house price prediction example.

Features (Inputs / Predictors)

A **feature** is an individual measurable property or characteristic of a data point. In our Pandas DataFrame, these are the columns. They are the **inputs** we use to make a prediction. For our house dataset, the features would be:

  • Number of Bedrooms
  • Square Footage
  • Age of the House
  • Location (e.g., ZIP code)

Label (Output / Target)

The **label** (or target) is the value we are trying to predict. It's the "answer" we want our model to learn to output. In our example, the label is the **Price** of the house. In a supervised learning problem, our dataset must contain this label column for the model to learn from.

Training Data vs. Testing Data 🧑‍🏫📝

This is one of the most important concepts in all of machine learning. You should **never** evaluate your model on the same data it was trained on. Why?

Imagine you're studying for a final exam. The professor gives you a set of practice questions and the answers. You study these questions intensely. This is your **training data**. If the final exam consists of the *exact same questions*, you might get a perfect score. But does that mean you truly understand the subject? Not necessarily. You might have just memorized the answers.

The true test of your knowledge is when the professor gives you a **testing set**—new questions you've never seen before. If you do well on these, it proves you've actually learned the underlying concepts (generalized).

We do the exact same thing with our models. We split our dataset into two parts:

  • Training Set (usually 70-80% of the data): The model sees this data and learns the patterns.
  • Testing Set (usually 20-30% of the data): This data is held back. We use it only at the very end to evaluate how well our trained model performs on unseen data.

The `scikit-learn` library, a cornerstone of Python ML, makes this easy.

from sklearn.model_selection import train_test_split

# X contains our features (all columns except the price)
# y contains our label (the price column)

# This single line splits our data into four pieces
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# X_train, y_train -> Used to train the model
# X_test, y_test   -> Used to test the model after training

Data Cleaning: Taking Out the Trash

Real-world data is notoriously messy. It's plagued with errors, missing entries, and inconsistencies. Our first job is to clean it up.

Handling Missing Values

Your dataset will often have blank spots or "NaN" (Not a Number) values. This can happen for many reasons: a sensor failed, a user skipped a field in a form, or there was a data entry error. Most ML algorithms cannot work with missing data, so we have to deal with it.

First, let's find them. Pandas makes this simple.

import pandas as pd
import numpy as np

# Sample messy data
data = {'Age': [25, 30, np.nan, 35, 40, 30],
        'Salary': [50000, 60000, 70000, 80000, np.nan, 60000],
        'Experience': [5, 10, 7, 12, 15, 10]}
df = pd.DataFrame(data)

# Find the count of missing values in each column
print(df.isnull().sum())

We have two main strategies to handle these:

  1. Dropping:** The simplest approach is to remove the rows (or columns) that contain missing values. This is okay if you have a huge dataset and only a few missing entries. But be careful! If you drop too many rows, you lose valuable information.
  2. df_dropped = df.dropna() # Drops rows with any missing values
    print(df_dropped)
  3. Imputation (Filling):** A better strategy is often to fill the missing values with a calculated value. Common choices are the **mean**, **median**, or **mode** of the column.
# Fill missing Age with the median age (robust to outliers)
median_age = df['Age'].median()
df['Age'].fillna(median_age, inplace=True)

# Fill missing Salary with the mean salary
mean_salary = df['Salary'].mean()
df['Salary'].fillna(mean_salary, inplace=True)

print(df.isnull().sum()) # Check if any missing values remain

Handling Duplicates

Duplicate rows can also appear, especially when data is combined from multiple sources. These can bias your model, making it think certain patterns are more common than they are. Finding and removing them is straightforward.

# Check for duplicate rows
print(f"Number of duplicate rows: {df.duplicated().sum()}")

# Remove duplicate rows
df_no_duplicates = df.drop_duplicates()
print(f"Shape of DataFrame after dropping duplicates: {df_no_duplicates.shape}")

Feature Scaling: Creating a Level Playing Field

Imagine a dataset with two features: a person's `Age` (ranging from 10 to 90) and their `Income` (ranging from $20,000 to $200,000). Because the `Income` numbers are so much larger, many ML algorithms will mistakenly assume that `Income` is a more important feature than `Age` simply due to its scale. **Feature scaling** prevents this by putting all features onto a similar scale.

Normalization (Min-Max Scaling)

Normalization rescales the data to a fixed range, usually **0 to 1**. It's calculated as: $(X - X_{min}) / (X_{max} - X_{min})$. This is a good choice when your data does not follow a normal (bell-curve) distribution.

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
# Fit and transform the data
scaled_data = scaler.fit_transform(df) 
# The result is a NumPy array, so we can convert it back to a DataFrame
df_normalized = pd.DataFrame(scaled_data, columns=df.columns)
print(df_normalized)

Standardization (Z-score Scaling)

Standardization rescales the data so that it has a **mean (μ) of 0** and a **standard deviation (σ) of 1**. It's calculated as: $(X - μ) / σ$. This is the most common scaling technique and is the default choice for many ML algorithms that assume your data is normally distributed (like Logistic Regression and SVMs). It is also less sensitive to outliers than normalization.

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
# Fit and transform the data
scaled_data = scaler.fit_transform(df)
df_standardized = pd.DataFrame(scaled_data, columns=df.columns)
print(df_standardized)

Data Visualization: Letting Your Data Speak

Before and after cleaning, it's vital to visualize your data. Visualizations can help you understand distributions, find relationships, spot outliers, and validate your cleaning steps. While Matplotlib is powerful, **Seaborn** is a library built on top of it that makes creating beautiful and informative statistical plots much easier.

Histograms: Understanding Distributions

A histogram shows the frequency distribution of a single numerical variable. It helps you see if the data is symmetric, skewed, or bimodal.

import seaborn as sns
import matplotlib.pyplot as plt

sns.histplot(df['Salary'], kde=True) # kde adds a smooth density curve
plt.title('Distribution of Salary')
plt.show()

Box Plots: Spotting Outliers

A box plot is fantastic for visualizing the spread of your data and identifying potential outliers—data points that are significantly different from the rest.

sns.boxplot(x=df['Age'])
plt.title('Box Plot of Age to Identify Outliers')
plt.show()

Correlation Heatmap: Finding Relationships

A correlation matrix shows how strongly different numerical features are related to each other. A heatmap makes this matrix easy to interpret with colors. This is incredibly useful for seeing which features are most related to your target variable (the label).

# Calculate the correlation matrix
corr_matrix = df.corr()

# Plot the heatmap
plt.figure(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm')
plt.title('Correlation Matrix of Features')
plt.show()

Your Data is Ready for Battle! ⚔️

Excellent work! You've just performed the most fundamental and impactful tasks in a data scientist's toolkit. By cleaning, scaling, and visualizing your data, you've ensured that your machine learning model will have the best possible chance of success.

Now that our ingredients are perfectly prepared, it's time to start cooking. In the next module, we will finally train our first machine learning models, diving headfirst into the world of **Supervised Learning**.

© 2025 AI & Machine Learning Course. All Rights Reserved.