Overfitting is the universal enemy — too complex models (deep trees, too many features) memorize noise.
📚 Prerequisites — Key Terms You Must Know
Before diving into algorithms, make sure you understand these foundational terms. Every concept below appears repeatedly throughout the module.
Model
A mathematical formula or set of rules that learns patterns from data and uses them to make predictions on new, unseen data.
A model that learned from 10,000 house sales can predict the price of a new house based on its features (size, location, bedrooms).
Features (X) — also called Independent Variables or Predictors
The input columns your model uses to learn. Each feature describes one characteristic of a data point.
For predicting house price: square footage, number of bedrooms, zip code, year built are all features.
Target (y) — also called Dependent Variable or Response
The output column you want the model to predict. This is the "answer" the model is trying to learn.
If predicting house price, the price column is the target. If predicting spam/not spam, the label column is the target.
Training Data vs Test Data
You split your data into two parts: training data (model learns from this, typically 70-80%) and test data (model is evaluated on this, 20-30%). The model never sees test data during training.
Like studying from 80 practice questions (training) and then taking a 20-question exam (test). Your exam score tells you how well you actually learned.
Supervised Learning
A type of ML where the training data includes both features (X) and the correct answers (y). The model learns the mapping from X to y.
Training a model with historical data where you know both the house features AND the actual sale price.
Unsupervised Learning
A type of ML where the training data has only features (X) — no target column. The model finds hidden patterns, groups, or structure on its own.
Grouping customers into segments based on purchasing behavior — nobody told the model what the groups should be.
Regression vs Classification
Regression predicts a continuous number (price = $245,000). Classification predicts a category (spam or not spam, cat or dog).
Predicting tomorrow's temperature (72.4°F) is regression. Predicting whether it will rain (yes/no) is classification.
Overfitting
When a model learns the training data TOO well — it memorizes noise and random patterns instead of the true underlying relationship. It performs great on training data but poorly on new data.
A student who memorizes every practice exam word-for-word but can't solve new problems. They "overfit" to the practice material.
Underfitting
When a model is too simple to capture the underlying patterns. It performs poorly on BOTH training and test data.
Using a straight line to model a clearly curved relationship. The model is too simple for the data.
Bias-Variance Tradeoff
Bias = error from overly simple models (underfitting). Variance = error from overly complex models (overfitting). The goal is to find the sweet spot in between.
A straight line through curved data has high bias. A wiggly line through every single point has high variance. The best model is somewhere in between.
Cross-Validation (k-fold)
Instead of a single train/test split, you split data into k parts (folds). Train on k-1 folds, test on 1 fold, and rotate. Average all k scores for a more reliable estimate.
With 5-fold CV: train on 80% test on 20%, five different ways. Average those 5 scores to get a stable performance estimate.
Think of machine learning like teaching a child. Supervised learning = showing flashcards with questions AND answers. Unsupervised learning = giving the child a pile of mixed LEGO bricks and saying "sort these into groups" — they'll figure out groups by color, shape, or size on their own.
Quick Check: Prerequisites
Q1: You want to predict whether an email is spam or not. What type of problem is this?
Correct! Spam/not-spam is a binary classification problem — predicting a category, not a number.
Q2: Your model scores 99% on training data but 52% on test data. What is this called?
Correct! Large gap between train and test scores = overfitting. The model memorized training data instead of learning general patterns.
📈 1.1 Linear Regression — Introduction
Week 1
The Business Problem
Imagine you're a real estate company. A client asks: "What should I price my 2,000 sq ft house?" You have data on thousands of past sales. Linear regression finds the mathematical relationship between house features and price, then uses it to predict prices for new houses.
Linear regression answers the question: "How does a change in X affect Y?"
You're at a farmer's market. You notice that the more apples you buy, the more you pay. If 1 apple = $1.50, then 5 apples should cost around $7.50. You just did linear regression in your head — you found a linear pattern (price per apple) and used it to predict a new total.
Understanding Correlation
Before building a regression model, we first check: is there even a relationship between X and Y? This is measured by correlation.
Correlation Coefficient (r)
A number between -1 and +1 that measures the strength and direction of a linear relationship between two variables.
Height and weight have a positive correlation (r ≈ 0.7): taller people tend to weigh more.
Types of Correlation
Positive (r ≈ +0.8) As X ↑, Y ↑ ⬆️ Study hours → ⬆️ Grades
Negative (r ≈ -0.7) As X ↑, Y ↓ ⬆️ Exercise → ⬇️ Weight
No Correlation (r ≈ 0) No linear pattern Shoe size → IQ
Correlation Strength Guide
|r| Range
Strength
Interpretation
0.00 – 0.19
Very Weak
Almost no linear relationship
0.20 – 0.39
Weak
Slight trend, lots of scatter
0.40 – 0.59
Moderate
Noticeable trend
0.60 – 0.79
Strong
Clear trend, some scatter
0.80 – 1.00
Very Strong
Points cluster tightly around a line
⚠️
Correlation ≠ Causation! Just because two variables are correlated doesn't mean one causes the other. Ice cream sales and drowning deaths are correlated — but ice cream doesn't cause drowning. Both increase in summer (confounding variable).
💡
Scatter Plots First! Always visualize your data with a scatter plot before calculating correlation. Correlation only measures linear relationships. A perfect U-shape curve could have r ≈ 0 even though there's a strong (non-linear) relationship.
When to Use Linear Regression
Target variable is continuous (numbers, not categories)
Relationship between X and Y appears roughly linear (check scatter plot)
You want to understand how much each feature affects the target
You want interpretable results (coefficients tell a clear story)
Quick Check: Correlation
Q: A researcher finds r = -0.85 between hours of TV watched and exam scores. What can we conclude?
Correct! r = -0.85 means strong negative correlation. But we CANNOT say TV causes lower scores — correlation is not causation.
📉 1.2 Simple Linear Regression
Week 1
The Equation
ŷ = β₀ + β₁x
ŷ = predicted value | β₀ = intercept (y value when x = 0) | β₁ = slope (change in y per unit change in x) | x = feature
Think of a taxi ride. The intercept (β₀) is the base fare you pay just for getting in ($3.00). The slope (β₁) is the per-mile rate ($2.50/mile). So the fare = $3.00 + $2.50 × miles. Linear regression finds the β₀ and β₁ that best fit your data.
What Does "Best Fit" Mean?
There are infinitely many lines you could draw through a scatter plot. The best fit line is the one that minimizes the total error — the distances between each actual data point and the line.
Residual
The difference between the actual value and the predicted value for a single data point: residual = y - ŷ. A positive residual means the model under-predicted; negative means it over-predicted.
If the actual house price is $300K and the model predicts $280K, the residual is +$20K (the model was $20K too low).
Ordinary Least Squares (OLS)
OLS is the method used to find the best β₀ and β₁. It minimizes the sum of squared residuals.
Minimize: Σ(y_i - ŷ_i)² = Σ(y_i - β₀ - β₁x_i)²
We square the residuals so positive and negative errors don't cancel out, and so large errors are penalized more.
How OLS Finds the Best Line
Scatter plot of (x, y) data points
▼
Try a line: ŷ = β₀ + β₁x
▼
Calculate residual for each point: (y_i - ŷ_i)
▼
Square each residual: (y_i - ŷ_i)²
▼
Sum them all: Σ(y_i - ŷ_i)²
▼
Find β₀ and β₁ that make this sum SMALLEST
Interpreting Coefficients
Example: House Price = 50,000 + 200 × SquareFeet
Intercept (β₀ = 50,000): A house with 0 sq ft would cost $50,000 (the base cost — land, infrastructure). Often not literally meaningful, but needed for the math.
Slope (β₁ = 200): For every additional 1 sq ft, the house price increases by $200. This is the key business insight!
🎯
Interpreting the Slope: "For every 1-unit increase in X, Y changes by β₁ units, holding all else constant." This "holding all else constant" part matters in multiple regression.
Assumptions of Linear Regression
For your model to be reliable, these assumptions should hold (LINE acronym):
L
Linearity: The relationship between X and Y is linear (check scatter plot)
I
Independence: Observations are independent of each other (one row doesn't influence another)
N
Normality: Residuals are normally distributed (check histogram of residuals)
E
Equal Variance (Homoscedasticity): Residuals have constant spread across all X values (check residual vs fitted plot)
Think of these assumptions like the rules of a board game. If you break the rules, the game (model) might still "work," but the results aren't trustworthy. Checking assumptions is like making sure everyone is playing fair.
Quick Check: Simple Linear Regression
Q1: In the equation ŷ = 10 + 3x, what does the 3 represent?
Correct! The coefficient 3 is the slope — it tells you y increases by 3 for every 1-unit increase in x.
Q2: Why do we square the residuals in OLS?
Correct! Without squaring, a residual of +5 and -5 would sum to 0. Squaring ensures all errors contribute positively and heavily penalizes large errors.
📊 1.3 Multiple Linear Regression
Week 1
From Simple to Multiple
Simple linear regression uses one feature. Multiple linear regression uses two or more features. Real-world predictions almost always involve multiple factors.
ŷ = β₀ + β₁x₁ + β₂x₂ + ... + βₚxₚ
p = number of features | each β_j = change in y for 1-unit change in x_j, holding other features constant
Predicting house price with just square footage is like estimating a recipe with only one ingredient. Sure, flour matters for bread, but you also need water, yeast, and salt. Multiple regression uses ALL the ingredients (features) together for a better prediction.
Handling Categorical Variables: One-Hot Encoding
Linear regression works with numbers, not categories. If you have a categorical feature like "City = {Austin, Dallas, Houston}", you convert it to binary dummy variables.
Example: One-Hot Encoding "City"
Original
→
City_Dallas
City_Houston
Austin
→
0
0
Dallas
→
1
0
Houston
→
0
1
Notice: Austin is represented by both dummies being 0. We only need (k-1) dummies for k categories.
Dummy Variable Trap
If you create k dummy variables for k categories (instead of k-1), they become perfectly correlated — one can be perfectly predicted from the others. This causes multicollinearity and breaks the model. Always drop one category.
For 3 cities, if you know City_Dallas=0 and City_Houston=0, you automatically know it's Austin. The third dummy adds zero new information.
Multicollinearity
Multicollinearity
When two or more features are highly correlated with each other (not just with the target). The model can't tell which feature is actually driving the prediction, making coefficients unstable and uninterpretable.
Including both "temperature in Celsius" and "temperature in Fahrenheit" — they carry the same information.
Detecting Multicollinearity: VIF
VIF_j = 1 / (1 - R²_j)
R²_j = R² from regressing feature j against all other features
VIF Value
Interpretation
Action
1
No correlation with other features
No concern
1 – 5
Moderate correlation
Usually acceptable
5 – 10
High correlation
Investigate, consider removing
> 10
Severe multicollinearity
Must address — remove or combine features
Dealing with Multicollinearity
High VIF detected (> 5)
▼
Option 1: Drop one of the correlated features
Option 2: Combine them (e.g., PCA)
Option 3: Use regularization (Ridge/Lasso)
Feature Selection: What to Include?
💡
Not all features improve the model. Adding irrelevant features increases complexity without improving predictions. Use Adjusted R² (penalizes extra features) instead of R² to compare models with different numbers of features.
Simple vs Multiple: Comparison
Aspect
Simple LR
Multiple LR
Features
1 (x)
2+ (x₁, x₂, ...)
Equation
ŷ = β₀ + β₁x
ŷ = β₀ + β₁x₁ + ... + βₚxₚ
Visualization
Line on 2D plot
Hyperplane in multi-D space
Risk
Underfitting (too simple)
Overfitting, multicollinearity
Evaluation
R²
Adjusted R² (accounts for # features)
Quick Check: Multiple Linear Regression
Q1: You have a feature "Color" with values Red, Blue, Green. How many dummy variables do you need?
Correct! For k categories, you need k-1 = 3-1 = 2 dummy variables. The dropped category becomes the "reference" (encoded as all zeros).
Q2: A feature has VIF = 12. What should you do?
Correct! VIF > 10 indicates severe multicollinearity. Remove the feature, combine it with the correlated one, or use regularization.
🎯 1.4 Regression Evaluation Metrics
Week 1
After building a model, you need to measure how good its predictions are. These metrics quantify prediction quality.
R-Squared (R²) — Coefficient of Determination
R² = 1 - (SS_res / SS_tot)
SS_res = Σ(y_i - ŷ_i)² (residual sum of squares) | SS_tot = Σ(y_i - ȳ)² (total sum of squares)
Interpretation: "What percentage of the variance in y does my model explain?"
Range: 0 to 1 (can be negative for terrible models)
R² = 0.85: The model explains 85% of the variation in y. The remaining 15% is unexplained (noise, missing features).
R² = 0: The model is no better than just predicting the mean of y for everything.
Imagine the total "error" of always guessing the average price for every house. R² tells you how much of that error your model eliminates. R² = 0.85 means your model removed 85% of the "just guess the average" error.
Adjusted R²
Adj R² = 1 - [(1 - R²)(n - 1) / (n - p - 1)]
n = number of observations | p = number of features
Why use it? R² always increases (or stays the same) when you add more features — even useless ones. Adjusted R² penalizes unnecessary features. If Adjusted R² drops when you add a feature, that feature is not helping.
MAE — Mean Absolute Error
MAE = (1/n) Σ|y_i - ŷ_i|
Average of the absolute differences between actual and predicted values
Interpretation: "On average, my predictions are off by MAE units."
Pros: Penalizes large errors more heavily (due to squaring)
Cons: Sensitive to outliers, units are squared
RMSE — Root Mean Squared Error
RMSE = √MSE = √[(1/n) Σ(y_i - ŷ_i)²]
Square root of MSE — brings units back to target variable scale
Units: Same as target variable (e.g., dollars)
Pros: Interpretable units, penalizes large errors
Most commonly used regression metric in practice
When to Use Which?
Metric
Best When
Interpretation
Sensitive to Outliers?
R²
Comparing models on same dataset
% of variance explained
Moderate
Adjusted R²
Comparing models with different # of features
Penalized % of variance explained
Moderate
MAE
You want robustness to outliers
Avg absolute error (in units)
No
MSE
You want to heavily penalize big mistakes
Avg squared error (units²)
Yes
RMSE
General-purpose, interpretable
Typical prediction error (in units)
Yes
🎯
Rule of Thumb: Report R² to explain model quality to stakeholders. Use RMSE to communicate practical error size. Use Adjusted R² when doing feature selection.
Notice RMSE (14.14) > MAE (13.33) — this is ALWAYS true. The gap between RMSE and MAE tells you about the variance of errors. A larger gap means some predictions are much worse than others.
Quick Check: Evaluation Metrics
Q: You added 3 new features to your model. R² went from 0.78 to 0.79, but Adjusted R² went from 0.77 to 0.75. What happened?
Correct! R² always goes up with more features (even random ones). The drop in Adjusted R² means the new features are not worth the added complexity.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from statsmodels.stats.outliers_influence import variance_inflation_factor
import warnings
warnings.filterwarnings('ignore')
Step 2: Load & Explore Data
# Load data
df = pd.read_csv('housing.csv')
# Quick look
print(df.shape) # (rows, columns)
print(df.info()) # dtypes, null counts
print(df.describe()) # summary statistics
print(df.isnull().sum()) # missing values per column
# Visualize target distribution
plt.figure(figsize=(8, 4))
sns.histplot(df['Price'], bins=30, kde=True)
plt.title('Distribution of House Prices')
plt.show()
# Correlation heatmap
plt.figure(figsize=(10, 8))
sns.heatmap(df.corr(), annot=True, cmap='coolwarm', center=0, fmt='.2f')
plt.title('Feature Correlation Matrix')
plt.show()
# Scatter plots of top features vs target
sns.pairplot(df[['SquareFeet', 'Bedrooms', 'Age', 'Price']])
plt.show()
Step 3: Preprocess
# Handle missing values
df['Age'].fillna(df['Age'].median(), inplace=True)
# One-hot encode categorical features (drop_first avoids dummy trap)
df = pd.get_dummies(df, columns=['City', 'Type'], drop_first=True)
# Define features (X) and target (y)
X = df.drop('Price', axis=1)
y = df['Price']
Step 4: Check VIF & Train/Test Split
# Check multicollinearity
vif_data = pd.DataFrame()
vif_data['Feature'] = X.columns
vif_data['VIF'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
print(vif_data.sort_values('VIF', ascending=False))
# Drop features with VIF > 10 if needed
# Split data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
print(f"Train: {X_train.shape[0]} rows | Test: {X_test.shape[0]} rows")
Step 5: Fit the Model
# Create and train model
model = LinearRegression()
model.fit(X_train, y_train)
# View coefficients
coef_df = pd.DataFrame({
'Feature': X_train.columns,
'Coefficient': model.coef_
}).sort_values('Coefficient', ascending=False)
print(f"Intercept: {model.intercept_:.2f}")
print(coef_df)
Step 6: Predict & Evaluate
# Predictions
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)
# Evaluation metrics
print("=== Training Set ===")
print(f"R²: {r2_score(y_train, y_train_pred):.4f}")
print(f"RMSE: {np.sqrt(mean_squared_error(y_train, y_train_pred)):.2f}")
print(f"MAE: {mean_absolute_error(y_train, y_train_pred):.2f}")
print("\n=== Test Set ===")
print(f"R²: {r2_score(y_test, y_test_pred):.4f}")
print(f"RMSE: {np.sqrt(mean_squared_error(y_test, y_test_pred)):.2f}")
print(f"MAE: {mean_absolute_error(y_test, y_test_pred):.2f}")
# If train R² >> test R², you may be overfitting!
Pro Tip: If residuals show a curved pattern, try polynomial features or a non-linear model. If residuals fan out (funnel shape), try log-transforming the target variable.
🌳 2.1 Decision Trees
Week 2
What is a Decision Tree?
A decision tree is a flowchart-like model that makes predictions by asking a series of yes/no questions about features, splitting the data at each step until it reaches a prediction.
Think of the game "20 Questions." You ask questions like "Is it alive?" → "Is it bigger than a car?" → "Does it live in water?" Each question narrows down the possibilities until you reach an answer. A decision tree works exactly like this — each question is a feature split.
A Simple Decision Tree: Should I Play Tennis?
Outlook?
Sunny ↙ ↓ Overcast ↘ Rain
Humidity?
YES ✓
Windy?
High→NO
Low→YES
T→NO
F→YES
Classification Tree vs Regression Tree
Aspect
Classification Tree
Regression Tree
Target Type
Categorical (Yes/No, A/B/C)
Continuous (price, temperature)
Leaf Node Output
Most common class (majority vote)
Mean of all values in that leaf
Split Criterion
Gini Impurity or Entropy
MSE (minimize variance in each split)
Example
Will a customer churn? (Yes/No)
What will their next purchase amount be?
How Does a Tree Decide Where to Split?
The tree tests every feature at every possible threshold and picks the split that creates the purest child nodes. Purity is measured by Entropy or Gini Impurity.
Entropy
H(S) = -Σ p_i × log₂(p_i)
p_i = proportion of class i in the set | Entropy = 0 means perfectly pure | Entropy = 1 means max impurity (50/50 split for binary)
Worked Example: A set has 9 "Yes" and 5 "No" (14 total).
p_yes = 9/14 = 0.643
p_no = 5/14 = 0.357
H = -(0.643 × log₂(0.643)) - (0.357 × log₂(0.357))
H = -(0.643 × -0.637) - (0.357 × -1.486)
H = 0.410 + 0.531
H = 0.940 (close to 1 = quite impure)
Gini Impurity
Gini(S) = 1 - Σ p_i²
Probability of incorrectly classifying a random sample | Gini = 0 means pure | Gini = 0.5 means max impurity (binary)
IG = Information Gain | A = the feature being tested | Higher IG = better split
Process:
1
Calculate entropy/gini of the parent node (before split)
2
For each possible feature/threshold, calculate weighted average entropy/gini of the resulting child nodes
3
Information Gain = Parent impurity - Weighted child impurity
4
Choose the feature/threshold with the HIGHEST information gain
5
Repeat recursively for each child node until stopping criteria are met
Decision Tree Building Process
Full Dataset
▼
Test every feature at every threshold
▼
Pick split with highest Information Gain
▼
Left Child (condition TRUE)
Right Child (condition FALSE)
▼
Repeat for each child until...
▼
Pure node (all same class)
Max depth reached
Too few samples to split
💡
Decision trees are greedy: They pick the best split at each step without looking ahead. This means they don't always find the globally optimal tree, but they're fast and usually good enough.
Quick Check: Decision Trees
Q1: A leaf node has 100% of one class. What is its Gini impurity?
Correct! Gini = 1 - (1.0²) = 0. A pure node has zero impurity — it doesn't need further splitting.
Q2: What does a higher information gain for a feature mean?
Correct! Higher information gain means this feature creates the most "order" from "disorder" — it's the best question to ask at this point in the tree.
✂️ 2.2 Overfitting & Pruning
Week 2
The Overfitting Problem with Decision Trees
An unpruned decision tree will keep splitting until every leaf is pure. This creates a very deep, very complex tree that has memorized every quirk in the training data — including noise and outliers.
Result: 100% accuracy on training data, but terrible accuracy on new data.
Imagine studying for an exam by memorizing every single word of the textbook, including typos, page numbers, and coffee stains. You'd "perfectly" know the book but fail any question that's phrased differently. Pruning is like focusing on the key concepts instead.
Bias-Variance Tradeoff in Decision Trees
Shallow Tree (depth=2) High Bias Underfitting Train: 65% | Test: 63%
Balanced Tree (depth=5) Sweet Spot Good generalization Train: 88% | Test: 85%
Deep Tree (depth=50) High Variance Overfitting Train: 100% | Test: 62%
Pre-Pruning (Early Stopping)
Stop the tree from growing too deep before it overfits. Set constraints during training.
Hyperparameter
What It Does
Typical Values
max_depth
Maximum levels the tree can have
3–10 (start with 5)
min_samples_split
Minimum samples needed to split a node
2–20 (default: 2)
min_samples_leaf
Minimum samples in a leaf node
1–10 (default: 1)
max_features
Max features to consider per split
'sqrt', 'log2', or a number
max_leaf_nodes
Maximum total leaf nodes
10–100
Post-Pruning (Cost Complexity Pruning)
First grow a full tree, then cut back branches that don't improve performance. Sklearn uses cost complexity pruning with the ccp_alpha parameter.
Cost = Impurity + α × (number of leaf nodes)
α (alpha) = complexity parameter | Higher α → more pruning → simpler tree
1
Grow a full, unpruned tree
2
Calculate the "effective alpha" for each subtree — the α at which that subtree would be pruned
3
Use cross-validation to find the best α value
4
Re-train the tree with that α to get the optimally pruned tree
Finding the Best Alpha
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
# 1. Grow full tree
full_tree = DecisionTreeClassifier(random_state=42)
full_tree.fit(X_train, y_train)
# 2. Get effective alphas
path = full_tree.cost_complexity_pruning_path(X_train, y_train)
alphas = path.ccp_alphas
# 3. Cross-validate each alpha
scores = []
for alpha in alphas:
tree = DecisionTreeClassifier(ccp_alpha=alpha, random_state=42)
cv_score = cross_val_score(tree, X_train, y_train, cv=5, scoring='accuracy')
scores.append(cv_score.mean())
# 4. Pick alpha with highest CV score
best_alpha = alphas[np.argmax(scores)]
print(f"Best alpha: {best_alpha:.4f}")
# 5. Train final pruned tree
pruned_tree = DecisionTreeClassifier(ccp_alpha=best_alpha, random_state=42)
pruned_tree.fit(X_train, y_train)
Pre-Pruning vs Post-Pruning
Aspect
Pre-Pruning
Post-Pruning
When
During tree building
After tree is fully grown
How
Set max_depth, min_samples, etc.
Set ccp_alpha, prune branches
Pros
Faster training, simpler
Often finds better tree
Cons
May stop too early (miss good splits)
Slower (grows full tree first)
In Practice
Use for quick experiments
Use for final model tuning
🎯
Golden Rule: Always compare train accuracy vs test accuracy. If the gap is large (e.g., 98% vs 72%), your tree is overfitting. Increase pruning until the gap narrows while test accuracy stays high.
Quick Check: Pruning
Q: Setting max_depth=3 is an example of which technique?
Correct! max_depth restricts tree growth DURING training — that's pre-pruning (early stopping). Post-pruning would grow the full tree first, then cut back.
💻 2.3 Hands-On: Decision Trees
Week 2
Building a Classification Tree
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt
# Load and split data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
# Build a basic tree
tree = DecisionTreeClassifier(random_state=42)
tree.fit(X_train, y_train)
# Evaluate
y_pred = tree.predict(X_test)
print(f"Train Accuracy: {tree.score(X_train, y_train):.4f}")
print(f"Test Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print("\nClassification Report:")
print(classification_report(y_test, y_pred))
Visualizing the Tree
# Plot the tree
plt.figure(figsize=(20, 10))
plot_tree(
tree,
feature_names=X.columns.tolist(),
class_names=['No', 'Yes'], # adjust for your classes
filled=True,
rounded=True,
fontsize=10,
max_depth=3 # only show top 3 levels for readability
)
plt.title('Decision Tree (showing top 3 levels)')
plt.tight_layout()
plt.show()
Workflow Summary: Build unpruned tree → Check for overfitting (train vs test gap) → Tune hyperparameters with GridSearchCV → Evaluate final model → Visualize the tree and feature importances.
🔵 3.1 K-Means Clustering
Week 3
What is K-Means?
K-Means is an unsupervised algorithm that groups data into K clusters based on similarity. Each data point belongs to the cluster with the nearest centroid (center point).
You must specify K (the number of clusters) before running the algorithm.
Imagine you're a teacher sorting a pile of 100 exam papers into 3 stacks (A, B, C grades). You start by randomly placing 3 "reference" papers. Then you compare each paper to the reference papers and put it in the closest stack. After sorting all papers, you recalculate what each stack's "average" looks like and re-sort. Repeat until the stacks stabilize. That's K-Means!
The K-Means Algorithm — Step by Step
K-Means Algorithm Flow
Step 1: Choose K (number of clusters)
▼
Step 2: Initialize K centroids (randomly pick K data points)
▼
Step 3: ASSIGN each point to nearest centroid
▼
Step 4: UPDATE centroids to mean of assigned points
▼
Step 5: Converged? (centroids didn't move)
No → Back to Step 3 | Yes ↓
DONE! Final clusters formed
Detailed Walkthrough (K=3 example)
1
Choose K=3. We want to find 3 groups in our data.
2
Initialize: Randomly select 3 data points as initial centroids: C₁=(2,3), C₂=(8,7), C₃=(5,1).
3
Assign: For every data point, calculate Euclidean distance to all 3 centroids. Assign it to the nearest one. Point (3,4) is closest to C₁ → Cluster 1.
4
Update: Recalculate each centroid as the mean of all points in its cluster. C₁ moves from (2,3) to (2.5, 3.2).
5
Check: Did centroids move significantly? Yes → go back to step 3. No → stop.
6
Typically converges in 5-20 iterations.
Euclidean Distance
The straight-line distance between two points: d = √((x₂-x₁)² + (y₂-y₁)²). This is the default distance metric in K-Means.
Distance from (1,2) to (4,6) = √((4-1)² + (6-2)²) = √(9+16) = √25 = 5
Choosing K: The Elbow Method
Since you must specify K upfront, how do you know the right number? The Elbow Method helps:
1
Run K-Means for K = 1, 2, 3, ..., 10
2
For each K, record the inertia (within-cluster sum of squared distances, also called SSE)
3
Plot K (x-axis) vs Inertia (y-axis)
4
Look for the "elbow" — the point where adding more clusters stops significantly reducing inertia
Inertia = Σ_k Σ_{x ∈ C_k} ||x - μ_k||²
Sum of squared distances from each point to its cluster centroid
Silhouette Score: A Better Evaluation
s(i) = (b(i) - a(i)) / max(a(i), b(i))
a(i) = avg distance to points in SAME cluster | b(i) = avg distance to points in NEAREST OTHER cluster
Silhouette Score
Meaning
Close to +1
Point is well-matched to its own cluster and poorly matched to others (excellent)
Close to 0
Point is on the border between two clusters
Close to -1
Point is probably assigned to the wrong cluster
Average Silhouette Score across all points tells you overall cluster quality. Higher is better.
K-Means: Strengths & Limitations
Strengths
Limitations
Fast and scalable (O(n × K × iterations))
Must specify K upfront
Easy to understand and implement
Sensitive to initialization (use K-Means++)
Works well for spherical clusters
Struggles with non-spherical or different-sized clusters
Guaranteed to converge
May converge to local minimum, not global
Scales to large datasets
Sensitive to outliers (they pull centroids)
⚡
K-Means++ (sklearn default): Instead of randomly initializing centroids, K-Means++ smartly spaces them apart. This almost always gives better results and is the default init='k-means++' in sklearn.
📊
Always scale your features! K-Means uses distance, so features on larger scales (e.g., salary in thousands vs age in decades) will dominate. Use StandardScaler before K-Means.
Quick Check: K-Means
Q1: In the elbow method, the "elbow" point represents:
Correct! The elbow is where the inertia curve bends — adding more clusters beyond this point doesn't significantly reduce inertia. It's the "good enough" K.
Q2: A data point has silhouette score = -0.3. What does this mean?
Correct! A negative silhouette score means the point is closer to a neighboring cluster than its own — it's likely misclustered.
🔗 3.2 Hierarchical Clustering & DBSCAN
Week 3
Hierarchical Clustering (Agglomerative)
Agglomerative clustering is a bottom-up approach: start with every point as its own cluster, then merge the two closest clusters repeatedly until everything is one big cluster.
Think of making a family tree starting from individuals. First, siblings are grouped into families. Then families are grouped into extended families. Then into clans, then into larger groups. At any level of the tree, you have a valid grouping — the dendrogram shows all of them.
Agglomerative Clustering Steps
Start: each point = 1 cluster (n clusters)
▼
Find the two closest clusters
▼
Merge them into one cluster (now n-1 clusters)
▼
Repeat until 1 cluster remains (or desired K is reached)
▼
Cut dendrogram at desired level
Linkage Methods — How to Measure "Closeness" Between Clusters
Linkage
Distance Between Clusters
Pros
Cons
Single
Minimum distance between any two points (one from each cluster)
Can find non-spherical shapes
Chaining effect — long, stringy clusters
Complete
Maximum distance between any two points
Compact, similar-sized clusters
Sensitive to outliers
Average
Average of all pairwise distances
Balanced approach
Slower than single/complete
Ward (most common)
Merges the pair that minimizes within-cluster variance (like K-Means)
Tends to create equal-sized, compact clusters
Biased toward spherical clusters
Dendrogram
A tree diagram that shows the hierarchical sequence of cluster merges. The y-axis shows the distance at which clusters were merged. You "cut" the dendrogram at a chosen height to get a specific number of clusters.
Cutting a dendrogram at height 5 might give you 3 clusters, while cutting at height 3 might give you 6 clusters.
💡
How to read a dendrogram: The longer a vertical line is before a merge happens, the more "different" those clusters are. Look for the tallest vertical lines — cutting there gives the most natural cluster boundaries.
DBSCAN — Density-Based Spatial Clustering
DBSCAN finds clusters as dense regions separated by sparse regions. Unlike K-Means, it:
Does NOT require you to specify K
CAN find clusters of arbitrary shape
CAN identify noise/outliers (points that belong to no cluster)
Imagine you're looking at city lights from an airplane at night. Dense clusters of lights are cities. Scattered lights in between are rural areas (noise). DBSCAN finds the "cities" by looking for areas with lots of lights close together, and labels the isolated lights as noise.
DBSCAN Parameters
eps (epsilon)
The radius of the neighborhood around each point. Two points are "neighbors" if they are within eps distance of each other.
If eps = 0.5 and two customers have spending patterns that are 0.3 units apart, they're neighbors. If they're 0.7 apart, they're not.
min_samples (minPts)
The minimum number of points within eps radius to count as a "dense" region. A point with at least min_samples neighbors (including itself) is a core point.
If min_samples = 5, a point needs at least 4 other points within its eps-neighborhood to be a core point.
Point Types in DBSCAN
Core, Border, and Noise Points
Core Point Has ≥ min_samples neighbors within eps radius Forms the "heart" of clusters
Border Point Within eps of a core point but < min_samples neighbors On the edge of a cluster
Noise Point Not within eps of any core point Outlier — no cluster
DBSCAN Algorithm Steps
1
Pick an unvisited point. Count how many neighbors are within eps distance.
2
If neighbors ≥ min_samples → it's a core point. Start a new cluster.
3
Add all its eps-neighbors to the cluster. For each new core point found, add THEIR neighbors too (cluster expands).
4
If a point is within eps of a core point but isn't a core point itself → border point, add to cluster.
5
If a point is not reachable from any core point → noise point (label = -1).
6
Repeat until all points are visited.
K-Means vs Hierarchical vs DBSCAN
Aspect
K-Means
Hierarchical
DBSCAN
Need to specify K?
Yes
Can cut dendrogram at any K
No (finds K automatically)
Cluster shapes
Spherical only
Depends on linkage
Any shape
Handles outliers?
No (assigns to nearest centroid)
No (assigns everything)
Yes (labels as noise)
Scalability
Very scalable
Slow on large data (O(n²))
Moderate
Deterministic?
No (random init)
Yes
Yes (mostly)
Best for
Large data, spherical clusters
Small data, want hierarchy
Irregular shapes, noisy data
Quick Check: Hierarchical & DBSCAN
Q1: Which linkage method tends to create the most compact, equal-sized clusters?
Correct! Ward's method minimizes within-cluster variance, similar to K-Means, producing compact, similarly-sized clusters.
Q2: A DBSCAN point has 2 neighbors within eps (min_samples = 5), but one of those neighbors is a core point. This point is:
Correct! It has fewer than min_samples neighbors (not a core point), but it's reachable from a core point, so it's a border point — on the edge of a cluster.
💻 3.3 Hands-On: Clustering
Week 3
Step 1: Preprocessing (Essential for All Clustering)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
# Load data
df = pd.read_csv('customers.csv')
# Select numeric features for clustering
features = ['Age', 'Income', 'SpendingScore']
X = df[features]
# ALWAYS scale before clustering!
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
print(f"Scaled data shape: {X_scaled.shape}")
print(f"Mean (should be ~0): {X_scaled.mean(axis=0).round(2)}")
print(f"Std (should be ~1): {X_scaled.std(axis=0).round(2)}")
K-Means: Elbow Method + Silhouette
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# Elbow Method
inertias = []
sil_scores = []
K_range = range(2, 11)
for k in K_range:
km = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42)
km.fit(X_scaled)
inertias.append(km.inertia_)
sil_scores.append(silhouette_score(X_scaled, km.labels_))
# Plot Elbow
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
ax1.plot(K_range, inertias, 'bo-')
ax1.set_xlabel('K (number of clusters)')
ax1.set_ylabel('Inertia (SSE)')
ax1.set_title('Elbow Method')
ax2.plot(K_range, sil_scores, 'ro-')
ax2.set_xlabel('K (number of clusters)')
ax2.set_ylabel('Silhouette Score')
ax2.set_title('Silhouette Score by K')
plt.tight_layout()
plt.show()
# Pick best K (e.g., K=4) and fit final model
best_k = 4
km_final = KMeans(n_clusters=best_k, init='k-means++', n_init=10, random_state=42)
df['KMeans_Cluster'] = km_final.fit_predict(X_scaled)
# Analyze clusters
print(df.groupby('KMeans_Cluster')[features].mean())
from sklearn.cluster import DBSCAN
# DBSCAN — tune eps and min_samples
db = DBSCAN(eps=0.5, min_samples=5)
df['DBSCAN_Cluster'] = db.fit_predict(X_scaled)
# Check results
n_clusters = len(set(df['DBSCAN_Cluster'])) - (1 if -1 in df['DBSCAN_Cluster'].values else 0)
n_noise = (df['DBSCAN_Cluster'] == -1).sum()
print(f"Clusters found: {n_clusters}")
print(f"Noise points: {n_noise}")
print(f"Cluster sizes:\n{df['DBSCAN_Cluster'].value_counts().sort_index()}")
# If only noise or 1 cluster, tune eps:
# Use k-distance graph to find good eps
from sklearn.neighbors import NearestNeighbors
nn = NearestNeighbors(n_neighbors=5)
nn.fit(X_scaled)
distances, _ = nn.kneighbors(X_scaled)
distances = np.sort(distances[:, -1]) # 5th nearest neighbor distance
plt.figure(figsize=(8, 4))
plt.plot(distances)
plt.xlabel('Points sorted by distance')
plt.ylabel('5th Nearest Neighbor Distance')
plt.title('K-Distance Graph (find the elbow for eps)')
plt.show()
Visualizing Clusters (2D)
# If you have 2 features, plot directly. Otherwise, use PCA to reduce to 2D.
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
X_2d = pca.fit_transform(X_scaled)
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
for ax, label_col, title in zip(axes,
['KMeans_Cluster', 'Hier_Cluster', 'DBSCAN_Cluster'],
['K-Means', 'Hierarchical', 'DBSCAN']):
scatter = ax.scatter(X_2d[:, 0], X_2d[:, 1],
c=df[label_col], cmap='viridis', alpha=0.6, s=20)
ax.set_title(title)
ax.set_xlabel('PC1')
ax.set_ylabel('PC2')
plt.colorbar(scatter, ax=ax)
plt.tight_layout()
plt.show()
🔑
Cluster Profiling: After clustering, always profile your clusters with df.groupby('Cluster')[features].mean() to understand what makes each group unique. This is the business insight!
⚠️ Common Mistakes — Organized by Week
Week 1 Linear Regression Mistakes
Ignoring Multicollinearity
What goes wrong: You include highly correlated features (e.g., "total rooms" and "rooms per floor"). The model trains fine, but coefficients are unstable — they flip signs or become huge, making interpretation impossible.
Fix: Check VIF before training. Remove or combine features with VIF > 5-10. Use df.corr() heatmap to spot pairs with |r| > 0.8.
Using R² to Compare Models with Different Feature Counts
What goes wrong: R² always increases when you add features — even random noise features. You conclude a 20-feature model is "better" than a 5-feature model just because R² is higher.
Fix: Use Adjusted R² when comparing models with different numbers of features. It penalizes unnecessary features.
Falling for the Dummy Variable Trap
What goes wrong: You one-hot encode a categorical variable with 4 categories and include all 4 dummy columns. This creates perfect multicollinearity.
Fix: Always use drop_first=True in pd.get_dummies() to create k-1 dummy variables for k categories.
Not Checking Assumptions
What goes wrong: Your model has a decent R² but the residual plot shows a clear curve — the relationship is non-linear, so linear regression is the wrong tool.
Fix: Always plot residuals vs fitted values. Look for random scatter (good) vs patterns (bad). Check residual normality with a histogram.
Week 2 Decision Tree Mistakes
Not Pruning → Massive Overfitting
What goes wrong: Default DecisionTreeClassifier() has no depth limit. It grows until every leaf is pure, creating a tree with hundreds of nodes that memorizes training data (100% train accuracy, 60% test accuracy).
Fix: Always set constraints: max_depth=5, min_samples_leaf=5, or use ccp_alpha for cost complexity pruning. Tune with GridSearchCV.
Interpreting Feature Importance as Causation
What goes wrong: "Income" has the highest feature importance, so you conclude that income CAUSES the outcome. Feature importance only shows predictive power, not causation.
Fix: Say "Income is the most predictive feature" instead of "Income causes X." Remember: correlation/prediction ≠ causation.
Confusing Gini with Entropy Results
What goes wrong: You calculate Gini impurity and get 0.48, then say "impurity is 48%." But Gini ranges from 0 to 0.5 for binary, so 0.48 is near-maximum impurity!
Fix: Know the ranges: Entropy 0-1 (binary), Gini 0-0.5 (binary). In practice, both give very similar tree structures.
Week 3 Clustering Mistakes
Forgetting to Scale Features
What goes wrong: Income ranges from 20K-200K while Age ranges from 18-70. K-Means clusters are dominated entirely by income because the distance calculation is swamped by the larger-scale feature.
Fix: ALWAYS use StandardScaler() before any distance-based algorithm. This makes all features contribute equally.
Choosing K Arbitrarily
What goes wrong: You pick K=5 because "five groups sounds right" without any evidence. The actual structure in the data might have 3 or 8 natural clusters.
Fix: Use the Elbow Method and Silhouette Score together. Also try hierarchical clustering — the dendrogram can suggest a natural K.
Using K-Means for Non-Spherical Clusters
What goes wrong: Your data has crescent-shaped or ring-shaped clusters. K-Means forces spherical boundaries and completely misassigns points.
Fix: Visualize the data first. If clusters look irregular, use DBSCAN instead — it can find clusters of any shape.
DBSCAN: Bad eps or min_samples
What goes wrong: eps is too small → everything is noise. eps is too large → everything is one cluster. min_samples is too high → most points become noise.
Fix: Use the k-distance graph to find a good eps. Start with min_samples = 2 × n_features. Experiment systematically.
🎤 Interview Prep — Key Questions & Answers
Click any question to reveal the answer.
Linear Regression
Q: What are the assumptions of linear regression?
Click to reveal
LINE: (1) Linearity — the relationship between X and Y is linear. (2) Independence — observations are independent. (3) Normality — residuals follow a normal distribution. (4) Equal variance (Homoscedasticity) — residuals have constant variance across all values of X. Also: no multicollinearity among features in multiple regression.
Q: What is multicollinearity and how do you detect it?
Click to reveal
Multicollinearity occurs when two or more independent variables are highly correlated with each other. This makes it hard to determine the individual effect of each variable. Detection: (1) Correlation matrix — look for pairs with |r| > 0.8. (2) VIF (Variance Inflation Factor) — VIF > 5-10 indicates high multicollinearity. Solutions: Remove one of the correlated features, combine them via PCA, or use regularization (Ridge/Lasso).
Q: What is the difference between R² and Adjusted R²?
Click to reveal
R² measures the proportion of variance in the target explained by the model. However, R² always increases when you add more features — even irrelevant ones. Adjusted R² adds a penalty for extra features: it only increases if a new feature genuinely improves the model. Use Adjusted R² when comparing models with different numbers of features.
Q: When would you use MAE over RMSE?
Click to reveal
Use MAE when you want robustness to outliers — MAE treats all errors equally. Use RMSE when large errors are particularly costly and you want to penalize them more (because squaring amplifies large errors). For example, in a medical setting where a large prediction error could be dangerous, RMSE is preferred. For general reporting, MAE is more intuitive ("on average, we're off by X dollars").
Q: What is the dummy variable trap?
Click to reveal
When you one-hot encode a categorical variable with k categories and include ALL k dummy variables, one dummy is a perfect linear combination of the others (if all others are 0, it must be 1). This creates perfect multicollinearity. Solution: Drop one category (use k-1 dummies). The dropped category becomes the "reference" or "baseline" category. Use pd.get_dummies(drop_first=True).
Decision Trees
Q: What is the difference between Gini impurity and Entropy?
Click to reveal
Both measure the impurity of a node. Entropy uses logarithms: H = -Σ p_i log₂(p_i), range 0 to log₂(k). Gini uses squared probabilities: G = 1 - Σ p_i², range 0 to 1-1/k. In practice, they produce very similar trees. Gini is slightly faster to compute (no log), and is sklearn's default. Use entropy if you specifically want Information Gain in the information-theoretic sense.
Q: How does a decision tree handle overfitting?
Click to reveal
Decision trees are highly prone to overfitting because they can grow until each leaf has one sample. Two approaches: (1) Pre-pruning: Set constraints during training — max_depth, min_samples_split, min_samples_leaf, max_leaf_nodes. (2) Post-pruning (cost complexity): Grow a full tree, then prune branches using ccp_alpha — higher alpha means more pruning. Use cross-validation to find the optimal alpha. Ensemble methods (Random Forest, Gradient Boosting) also help by combining many trees.
Q: What is information gain and how does a tree use it?
Click to reveal
Information Gain = Parent's impurity - Weighted average of children's impurities. The tree evaluates every possible feature and threshold, calculates the information gain for each, and picks the one with the highest gain. This is done recursively at each node. The tree is "greedy" — it optimizes locally at each step, not globally.
Q: What are the advantages and disadvantages of decision trees?
Click to reveal
Advantages: Easy to interpret and visualize, handles both numerical and categorical data, requires little data preprocessing, captures non-linear relationships, feature importance is built-in. Disadvantages: Highly prone to overfitting, unstable (small data changes → very different tree), greedy algorithm (not globally optimal), biased toward features with more levels, can create overly complex trees.
Clustering
Q: How is K-Means different from DBSCAN?
Click to reveal
K-Means: Requires K upfront, finds spherical clusters, no noise detection, sensitive to outliers, fast (O(nKi)). DBSCAN: Finds K automatically, finds arbitrary-shaped clusters, identifies noise/outliers, needs eps and min_samples, less sensitive to outliers. Use K-Means for large data with roughly spherical clusters; use DBSCAN when clusters have irregular shapes or data has outliers.
Q: How do you choose the right number of clusters?
Click to reveal
Multiple methods: (1) Elbow Method: Plot K vs inertia (SSE), look for the "elbow" where adding more K gives diminishing returns. (2) Silhouette Score: Measures how similar each point is to its own cluster vs the nearest other cluster; pick K that maximizes the average silhouette score. (3) Dendrogram: In hierarchical clustering, look for the largest vertical gap (tallest lines) to determine natural cuts. (4) Business context: Sometimes the business dictates K (e.g., "we need 3 customer tiers").
Q: Why is feature scaling important for clustering?
Click to reveal
Clustering algorithms (K-Means, DBSCAN, Hierarchical) use distance measures (typically Euclidean). If features are on different scales — say income (20K-200K) vs age (18-70) — the larger-scale feature dominates the distance calculation. Income differences would overwhelm age differences, making age irrelevant. StandardScaler (mean=0, std=1) ensures all features contribute equally to the distance computation.
Q: What is the silhouette score and how do you interpret it?
Click to reveal
For each point i: s(i) = (b(i) - a(i)) / max(a(i), b(i)), where a(i) = average distance to other points in the SAME cluster, b(i) = average distance to points in the NEAREST other cluster. Range: -1 to +1. Close to +1 means the point is well-clustered. Close to 0 means it's on a cluster boundary. Negative means it's probably in the wrong cluster. Average all s(i) values for an overall cluster quality score.
Q: Explain the different linkage methods in hierarchical clustering.
Click to reveal
Single: Distance = minimum distance between any pair of points (one from each cluster). Can find elongated shapes but suffers from "chaining." Complete: Distance = maximum distance between any pair. Creates compact clusters but sensitive to outliers. Average: Distance = average of all pairwise distances. Balanced approach. Ward: Merges clusters that minimize total within-cluster variance. Most popular; creates compact, equal-sized clusters similar to K-Means.