05. Linear Regression - Expert Topics
Linear Regression - Expert Topics π
Welcome to Expert Level! π
Youβve learned the basics and intermediate topics. Now letβs tackle the expert-level concepts that separate good data scientists from great ones!
This tutorial assumes zero prior knowledge and builds you up to expert level. Weβll cover everything you need to handle real-world linear regression problems.
Table of Contents
- Categorical Variable Encoding
- Multicollinearity Deep Dive
- Influential Points and Outliers
- Polynomial Regression
- Log Transformations
- Cross-Validation
- Feature Selection Methods
- Advanced Evaluation Metrics
- Confidence Intervals & Predictions
- Real-World Workflow
1. Categorical Variable Encoding
π― The Problem
Linear Regression needs numbers, but real data often has categories (text)!
Example:
House Data:
| Size | Bedrooms | Location | Price |
|------|----------|-----------|-------|
| 2000 | 3 | Urban | $400k |
| 1500 | 2 | Suburban | $300k |
| 1800 | 3 | Rural | $250k |
Question: How does the model use βUrbanβ, βSuburbanβ, βRuralβ?
Answer: We need to convert them to numbers! This is called encoding.
π Method 1: One-Hot Encoding (Most Common!)
What it does: Creates a separate column for each category (0 or 1).
Simple Example:
Before encoding:
| Location |
|-----------|
| Urban |
| Suburban |
| Rural |
| Urban |
After one-hot encoding:
| Is_Urban | Is_Suburban | Is_Rural |
|----------|-------------|----------|
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 0 | 0 | 1 |
| 1 | 0 | 0 |
How to read it:
- βUrbanβ β [1, 0, 0]
- βSuburbanβ β [0, 1, 0]
- βRuralβ β [0, 0, 1]
When to use:
- β Categories have NO order (Red, Blue, Green)
- β Few categories (less than 10-15)
- β Each category is independent
Python Code:
import pandas as pd
# Sample data
df = pd.DataFrame({
'size': [2000, 1500, 1800, 2200],
'location': ['Urban', 'Suburban', 'Rural', 'Urban'],
'price': [400, 300, 250, 450]
})
# Method 1: pandas get_dummies (easiest!)
df_encoded = pd.get_dummies(df, columns=['location'])
print(df_encoded)
# Output:
# size price location_Rural location_Suburban location_Urban
# 0 2000 400 0 0 1
# 1 1500 300 0 1 0
# 2 1800 250 1 0 0
# 3 2200 450 0 0 1
Method 2: scikit-learn
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse_output=False)
location_encoded = encoder.fit_transform(df[['location']])
print("Categories:", encoder.categories_)
print("Encoded:", location_encoded)
β οΈ The Dummy Variable Trap
The Problem:
If you have 3 categories, you only need 2 columns (not 3)!
Why? Because the third can be inferred from the others.
Example:
If Is_Urban = 0 AND Is_Suburban = 0
Then it MUST be Rural!
So Is_Rural is redundant!
This causes multicollinearity (weβll learn more soon).
The Fix: Drop one category (called βreference categoryβ)
Python Code:
# Drop first category to avoid trap
df_encoded = pd.get_dummies(df, columns=['location'], drop_first=True)
print(df_encoded)
# Output:
# size price location_Suburban location_Urban
# 0 2000 400 0 1
# 1 1500 300 1 0
# 2 1800 250 0 0 β This is Rural!
# 3 2200 450 0 1
Interpretation after dropping:
Model: Price = Ξ²β + Ξ²βΓSize + Ξ²βΓIs_Suburban + Ξ²βΓIs_Urban
Ξ²β = Price for Rural houses (reference category)
Ξ²β = Extra price for Suburban vs Rural
Ξ²β = Extra price for Urban vs Rural
π Method 2: Label Encoding
What it does: Assigns each category a number (0, 1, 2, β¦)
Example:
Before:
| Color |
|--------|
| Red |
| Blue |
| Green |
| Red |
After:
| Color |
|-------|
| 0 | β Red
| 1 | β Blue
| 2 | β Green
| 0 | β Red
When to use:
- β Categories have ORDER (Small, Medium, Large)
- β Tree-based models (Random Forest, XGBoost)
β DONβT use for Linear Regression with unordered categories!
Why? The model thinks Green (2) is βtwiceβ Blue (1), which is meaningless!
Python Code:
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
df['color_encoded'] = encoder.fit_transform(df['color'])
print("Original:", df['color'].unique())
print("Encoded:", encoder.classes_)
π Method 3: Ordinal Encoding
What it does: Like Label Encoding, but you control the order!
Example: T-shirt sizes
Categories: Small, Medium, Large, X-Large
Ordinal Encoding:
Small β 1
Medium β 2
Large β 3
X-Large β 4
Now the order makes sense!
Large (3) > Medium (2) > Small (1) β
When to use:
- β Categories have CLEAR order
- β Distance between categories matters
- β Examples: education levels, ratings, sizes
Python Code:
from sklearn.preprocessing import OrdinalEncoder
# Define the order
categories = [['Small', 'Medium', 'Large', 'X-Large']]
encoder = OrdinalEncoder(categories=categories)
df['size_encoded'] = encoder.fit_transform(df[['size']])
print(df)
π Method 4: Target Encoding (Mean Encoding)
What it does: Replaces category with the average target value for that category.
Example: Predicting house prices by neighborhood
Before encoding:
| Neighborhood | Price |
|--------------|-------|
| A | $300k |
| B | $500k |
| A | $320k |
| C | $200k |
| B | $480k |
Calculate mean price per neighborhood:
A: ($300k + $320k) / 2 = $310k
B: ($500k + $480k) / 2 = $490k
C: $200k
After target encoding:
| Neighborhood | Price |
|--------------|-------|
| 310 | $300k |
| 490 | $500k |
| 310 | $320k |
| 200 | $200k |
| 490 | $480k |
When to use:
- β Many categories (high cardinality)
- β Strong relationship between category and target
- β Examples: zip codes, neighborhoods, products
β οΈ Warning: Can cause data leakage! Always use cross-validation.
Python Code:
# Calculate target encoding
target_means = df.groupby('neighborhood')['price'].mean()
df['neighborhood_encoded'] = df['neighborhood'].map(target_means)
print(df)
π Method 5: Frequency Encoding
What it does: Replaces category with how often it appears.
Example:
Before:
| City |
|-----------|
| New York |
| Boston |
| New York |
| Chicago |
| New York |
| Boston |
Frequencies:
New York: 3 times
Boston: 2 times
Chicago: 1 time
After frequency encoding:
| City |
|------|
| 3 |
| 2 |
| 3 |
| 1 |
| 3 |
| 2 |
When to use:
- β Many categories
- β Frequency itself is informative
- β Examples: product categories, customer segments
Python Code:
freq_map = df['city'].value_counts().to_dict()
df['city_encoded'] = df['city'].map(freq_map)
print(df)
π Method 6: Binary Encoding
What it does: Converts categories to binary numbers, then splits into columns.
Example:
Categories: Red, Blue, Green, Yellow, Purple (5 categories)
Step 1: Assign numbers
Red β 1 β Binary: 001
Blue β 2 β Binary: 010
Green β 3 β Binary: 011
Yellow β 4 β Binary: 100
Purple β 5 β Binary: 101
Step 2: Split into columns
| Color | Bit_1 | Bit_2 | Bit_3 |
|--------|-------|-------|-------|
| Red | 0 | 0 | 1 |
| Blue | 0 | 1 | 0 |
| Green | 0 | 1 | 1 |
| Yellow | 1 | 0 | 0 |
| Purple | 1 | 0 | 1 |
When to use:
- β Many categories (50+)
- β Want fewer columns than one-hot
- β Memory-efficient
Python Code:
# Install: pip install category_encoders
import category_encoders as ce
encoder = ce.BinaryEncoder()
df_encoded = encoder.fit_transform(df['color'])
print(df_encoded)
π― Encoding Decision Tree
Is the category ordered? (Small < Medium < Large)
βββ YES β Use Ordinal Encoding
βββ NO
βββ Few categories (<15)?
β βββ YES β Use One-Hot Encoding (drop_first=True)
β βββ NO
β βββ Strong target relationship?
β β βββ YES β Use Target Encoding
β β βββ NO β Use Frequency or Binary Encoding
π Comparison Table
| Method | Use Case | Pros | Cons |
|---|---|---|---|
| One-Hot | Unordered, few categories | Simple, no false order | Many columns |
| Label | Ordered (tree models only) | One column | Implies false order for linear models |
| Ordinal | Ordered categories | Preserves order | Need to define order |
| Target | Many categories, strong target relation | Few columns, captures relationships | Data leakage risk |
| Frequency | Many categories | Simple, captures frequency | Loses category info |
| Binary | Very many categories (50+) | Memory efficient | Less interpretable |
2. Multicollinearity Deep Dive
π― What is Multicollinearity?
Simple definition: When two or more features are highly correlated (basically measure the same thing).
Real-world analogy:
Imagine asking three witnesses about a crime:
- Witness 1: Says it happened at 3 PM
- Witness 2: Says it happened at 1500 hours
- Witness 3: Says it happened in the afternoon
All three witnesses are saying the same thing! Adding all three doesnβt give you more information.
This is multicollinearity in regression!
π Two Types of Multicollinearity
Type 1: Structural Multicollinearity
What: You CREATED it through feature engineering.
Example:
Original feature: x
Created features: x, xΒ², xΒ³
These are mathematically related!
xΒ² is just x Γ x
Common causes:
- Polynomial features (x, xΒ², xΒ³)
- Interaction terms (x Γ y when x, y already in model)
- Same data in different units (height in inches AND height in cm)
- Dummy variable trap
Type 2: Data-Based Multicollinearity
What: Naturally exists in the data.
Example:
Predicting salary:
- Years of experience
- Age
- Years since graduation
These are naturally correlated!
Older people usually have more experience.
Common causes:
- Naturally related features (age, experience)
- Highly similar measurements
- Features derived from same source
π¨ Why is Multicollinearity a Problem?
Problem 1: Unstable Coefficients
Small changes in data β BIG changes in coefficients!
Example:
Dataset 1:
Price = 100 + 0.5ΓSize + 0.3ΓBedrooms
Add 1 more house, retrain:
Dataset 2:
Price = 100 + 1.2ΓSize - 0.4ΓBedrooms
Coefficients changed dramatically! β
Problem 2: Unreliable Standard Errors
Standard errors get inflated, making coefficients seem less significant.
What this means:
- Real important features may seem unimportant
- p-values become unreliable
- Confidence intervals become huge
Problem 3: Hard to Interpret
You canβt tell which feature is actually important!
Example:
Both Age and Years_of_Experience predict salary.
With multicollinearity:
- Age coefficient: $5k/year
- Experience coefficient: $3k/year
But this might mean:
- Age really matters β $8k/year alone
- Experience really matters β $7k/year alone
- Or any combination!
You can't tell! β
β When Multicollinearity is NOT a Problem
Surprise! Sometimes you can ignore multicollinearity:
Case 1: You only care about predictions
If your goal is just predicting (not understanding), multicollinearity doesnβt hurt prediction accuracy much!
Example:
Goal: Predict house prices
Don't care: Why prices are what they are
β Multicollinearity might be okay!
Case 2: Multicollinearity is in control variables
If correlated features are just controls (not what youβre studying), itβs fine.
Case 3: Sample size is huge
With millions of data points, even highly correlated features can be estimated reliably.
π How to Detect Multicollinearity
Method 1: Correlation Matrix
Simple visual check.
Python Code:
import seaborn as sns
import matplotlib.pyplot as plt
# Calculate correlations
corr_matrix = X.corr()
# Visualize
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0,
square=True, linewidths=1, fmt='.2f')
plt.title('Feature Correlation Matrix')
plt.show()
What to look for:
-
correlation > 0.8: Strong correlation β -
correlation > 0.6: Moderate β οΈ -
correlation < 0.4: Weak β
Limitation: Only catches PAIRWISE correlations. Misses complex relationships!
Method 2: Variance Inflation Factor (VIF) - The Gold Standard!
What it measures: How much the variance of a coefficient is βinflatedβ due to correlation with other features.
Formula:
VIF = 1 / (1 - RΒ²)
Where RΒ² is from regressing one feature against all others.
Interpretation:
| VIF | Meaning | Action |
|---|---|---|
| 1 | No correlation | β Perfect |
| 1-5 | Moderate correlation | β Acceptable |
| 5-10 | High correlation | β οΈ Investigate |
| > 10 | Severe correlation | β Fix it! |
Python Code:
from statsmodels.stats.outliers_influence import variance_inflation_factor
import pandas as pd
# Calculate VIF for each feature
def calculate_vif(X):
vif = pd.DataFrame()
vif["Feature"] = X.columns
vif["VIF"] = [variance_inflation_factor(X.values, i)
for i in range(X.shape[1])]
return vif.sort_values('VIF', ascending=False)
vif_results = calculate_vif(X)
print(vif_results)
Real Example:
Feature VIF
age 12.5 β Drop or combine!
experience 10.2 β Drop or combine!
years_since_grad 8.7 β οΈ Investigate
education 2.1 β
Fine
salary_history 1.5 β
Fine
Method 3: Condition Number
What it measures: Overall multicollinearity in the dataset.
Interpretation:
- < 30: No problem β
- 30-100: Moderate β οΈ
-
100: Severe β
Python Code:
import numpy as np
# Calculate condition number
cond_number = np.linalg.cond(X.values)
print(f"Condition Number: {cond_number:.2f}")
π οΈ How to Fix Multicollinearity
Solution 1: Drop One of the Correlated Features
Easiest fix!
Example:
# Find highly correlated features
corr_matrix = X.corr().abs()
# Get upper triangle
upper = corr_matrix.where(
np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)
)
# Find features with correlation > 0.8
to_drop = [column for column in upper.columns
if any(upper[column] > 0.8)]
print(f"Drop these: {to_drop}")
X_clean = X.drop(columns=to_drop)
Decision rule: Keep the feature thatβs:
- More interpretable
- Easier to measure
- More relevant to your problem
Solution 2: Combine Correlated Features
Create a new feature that combines them.
Example:
# Instead of using both height_inches and height_cm
# Just keep one!
# Or combine related features:
df['total_size'] = df['living_room_size'] + df['kitchen_size']
df.drop(columns=['living_room_size', 'kitchen_size'], inplace=True)
Solution 3: Principal Component Analysis (PCA)
Transforms correlated features into uncorrelated ones.
Python Code:
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
# Scale first!
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# Apply PCA
pca = PCA(n_components=0.95) # Keep 95% of variance
X_pca = pca.fit_transform(X_scaled)
print(f"Original features: {X.shape[1]}")
print(f"PCA components: {X_pca.shape[1]}")
print(f"Variance explained: {pca.explained_variance_ratio_}")
Trade-off: Lose interpretability, but fix multicollinearity.
Solution 4: Use Regularization
Ridge Regression handles multicollinearity excellently!
from sklearn.linear_model import Ridge
# Ridge handles correlated features well
ridge = Ridge(alpha=1.0)
ridge.fit(X, y)
Solution 5: Center Variables (for Polynomial Features)
For structural multicollinearity from polynomial features:
# Bad: x and xΒ² are highly correlated
X['x_squared'] = X['x'] ** 2
# Better: Center first
X['x_centered'] = X['x'] - X['x'].mean()
X['x_centered_squared'] = X['x_centered'] ** 2
# Now they're less correlated!
3. Influential Points and Outliers
π― The Three Types of Problematic Points
Type 1: Outliers
What: Points with unusual y-values
Example:
House sizes: 1000-3000 sq ft (normal)
House prices: $200k-$600k (normal)
But one 2000 sq ft house: $2M β Outlier!
Type 2: High Leverage Points
What: Points with unusual x-values
Example:
House sizes: mostly 1000-3000 sq ft
But one house: 10,000 sq ft β High leverage!
Type 3: Influential Points
What: Points that significantly change the regression line.
Combination: Often outlier + high leverage = influential!
π Visual Comparison
Normal Outlier: High Leverage: Influential Point:
y β β y β β y β
β β β β β
β β β β β
β ββOutlier! β β β β
β β β β
β β β β βHL β β βInfluential!
ββββββββ x ββββββββ x ββββββββ x
Pulls line slightly Doesn't pull much REALLY changes line!
π How to Detect
Method 1: Cookβs Distance
What it measures: How much the model would change without this point.
Rule of thumb: Cookβs Distance > 1 = Influential
Python Code:
import statsmodels.api as sm
import matplotlib.pyplot as plt
# Fit model
X_with_const = sm.add_constant(X)
model = sm.OLS(y, X_with_const).fit()
# Calculate influence
influence = model.get_influence()
cooks_d = influence.cooks_distance[0]
# Plot
plt.figure(figsize=(10, 6))
plt.stem(range(len(cooks_d)), cooks_d)
plt.axhline(y=4/len(y), color='r', linestyle='--',
label='Threshold (4/n)')
plt.xlabel('Observation Index')
plt.ylabel("Cook's Distance")
plt.title("Cook's Distance Plot")
plt.legend()
plt.show()
# Find influential points
influential = np.where(cooks_d > 4/len(y))[0]
print(f"Influential points at indices: {influential}")
Method 2: Leverage
What it measures: How unusual the X values are.
Rule of thumb: Leverage > 2(p+1)/n = High leverage (where p = features, n = samples)
leverage = influence.hat_matrix_diag
# Find high leverage points
n, p = X.shape
threshold = 2 * (p + 1) / n
high_leverage = np.where(leverage > threshold)[0]
print(f"High leverage points: {high_leverage}")
Method 3: Standardized Residuals
What: Residuals scaled to standard deviations.
| Rule of thumb: | standardized residual | > 3 = Outlier |
residuals = influence.resid_studentized_internal
outliers = np.where(np.abs(residuals) > 3)[0]
print(f"Outliers at indices: {outliers}")
π οΈ What to Do With These Points
Decision tree:
Is the point an error?
βββ YES (data entry error, measurement error)
β βββ REMOVE it
βββ NO (it's a real, unusual observation)
βββ Is it influential?
β βββ YES β Investigate why
β β βββ Special case? β Consider keeping but report
β β βββ Mistake? β Remove
β βββ NO β Keep it
βββ Document your decision!
Important: ALWAYS investigate before removing!
4. Polynomial Regression
π― When Linear Isnβt Enough
Sometimes data isnβt linear:
Linear data: Curved data:
y β y β β
β β β β
β β β β
β β β β
β β β β
ββββββββ x ββββββββ x
Linear regression works β
Linear regression FAILS β
Solution: Add polynomial features!
π The Idea
Instead of just x, use x, xΒ², xΒ³, etc.
Before (Linear):
y = Ξ²β + Ξ²βΓx
After (Polynomial degree 2):
y = Ξ²β + Ξ²βΓx + Ξ²βΓxΒ²
After (Polynomial degree 3):
y = Ξ²β + Ξ²βΓx + Ξ²βΓxΒ² + Ξ²βΓxΒ³
π‘οΈ Real Example: Temperature vs Ice Cream Sales
Temperature Sales
10Β°C 50
15Β°C 80
20Β°C 150
25Β°C 300
30Β°C 500
35Β°C 750
Linear model: BAD fit!
Polynomial (degree 2): GREAT fit!
Python Code:
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
# Create polynomial features
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly_features.fit_transform(X)
print(f"Original features: {X.shape}")
print(f"Polynomial features: {X_poly.shape}")
# Train model
model = LinearRegression()
model.fit(X_poly, y)
# Better: Use a pipeline
pipeline = Pipeline([
('poly', PolynomialFeatures(degree=2)),
('linear', LinearRegression())
])
pipeline.fit(X, y)
β οΈ Choosing the Right Degree
Too low: Underfitting (misses patterns) Too high: Overfitting (memorizes noise)
Degree 1 (Linear): Degree 3 (Good): Degree 15 (Overfit!):
y β β y β β y β β±β² β
β β β β β β± β² β
β β β β ββ β β β²β±β
β β β β ββ β β β±β²β
ββββββββ x ββββββββ x ββββββββ x
Underfit β Just right β
Overfit β
Best practice: Use cross-validation to choose degree!
5. Log Transformations
π― When to Use Log
Use log when data is skewed or has exponential growth.
Common scenarios:
- Income (right-skewed)
- House prices
- Population growth
- Stock prices
π What Log Does
Before log:
Income: $30k, $40k, $50k, $1M, $5M
(mostly small, few HUGE)
Distribution: Heavily right-skewed β
After log:
Log(Income): 10.3, 10.6, 10.8, 13.8, 15.4
(more even distribution)
Distribution: Approximately normal β
π οΈ Three Types of Log Transformations
Type 1: Log on Y (target)
y_log = np.log(y)
model.fit(X, y_log)
Interpretation: Coefficient Γ 100 = % change in y per unit change in x
Example:
log(Price) = 5 + 0.05ΓBedrooms
Each bedroom adds 5% to price (approximately)!
Type 2: Log on X (feature)
X['log_income'] = np.log(X['income'])
model.fit(X, y)
Interpretation: Coefficient Γ 0.01 = change in y per 1% change in x
Example:
Price = 100 + 50Γlog(Income)
A 1% increase in income adds $0.50 to price.
A 100% increase in income adds $50 to price.
Type 3: Log-Log (both)
X['log_income'] = np.log(X['income'])
y_log = np.log(y)
model.fit(X[['log_income']], y_log)
Interpretation: Coefficient = Elasticity (% change in y per % change in x)
Example:
log(Price) = 2 + 0.8Γlog(Income)
A 1% increase in income leads to 0.8% increase in price.
This is called "elasticity"!
β οΈ Important Notes
Canβt log zero or negative values!
# Bad:
y_log = np.log(y) # Fails if y has 0 or negative
# Good: Use log(1+y) or shift data
y_log = np.log1p(y) # log(1+y), handles 0
y_log = np.log(y + 1) # Manual shift
6. Cross-Validation
π― The Problem with Single Train/Test Split
One split can be misleading!
Example:
Random split 1: Train RΒ² = 0.85, Test RΒ² = 0.80 β
Random split 2: Train RΒ² = 0.85, Test RΒ² = 0.65 β
Same model, different conclusions!
π K-Fold Cross-Validation
The Idea: Split data into K parts. Use each part as test once.
Visual (5-fold):
Iteration 1: [Test ][Train][Train][Train][Train]
Iteration 2: [Train][Test ][Train][Train][Train]
Iteration 3: [Train][Train][Test ][Train][Train]
Iteration 4: [Train][Train][Train][Test ][Train]
Iteration 5: [Train][Train][Train][Train][Test ]
Average performance across all 5!
π οΈ Implementation
from sklearn.model_selection import cross_val_score, KFold
from sklearn.linear_model import LinearRegression
# Create model
model = LinearRegression()
# Set up 5-fold CV
kfold = KFold(n_splits=5, shuffle=True, random_state=42)
# Run cross-validation
scores = cross_val_score(model, X, y, cv=kfold, scoring='r2')
print(f"RΒ² scores: {scores}")
print(f"Mean RΒ²: {scores.mean():.3f}")
print(f"Std RΒ²: {scores.std():.3f}")
print(f"95% CI: [{scores.mean() - 2*scores.std():.3f}, "
f"{scores.mean() + 2*scores.std():.3f}]")
π― Choosing K
| K | Pros | Cons | Use When |
|---|---|---|---|
| 5 | Fast | Less reliable | Quick exploration |
| 10 | Good balance | Standard | Most cases |
| n (LOOCV) | Most thorough | Very slow | Small datasets |
7. Feature Selection Methods
π― Why Select Features?
More features β Better model!
Reasons to select:
- β Reduce overfitting
- β Faster training
- β Better interpretation
- β Less data needed
π Method 1: Univariate Selection
Idea: Select features with strongest individual relationship to target.
from sklearn.feature_selection import SelectKBest, f_regression
# Select top 5 features
selector = SelectKBest(score_func=f_regression, k=5)
X_selected = selector.fit_transform(X, y)
# See which were selected
selected_features = X.columns[selector.get_support()]
print(f"Selected: {selected_features.tolist()}")
π Method 2: Recursive Feature Elimination (RFE)
Idea: Start with all features, remove worst one at a time.
from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
# Use RFE to select 5 features
rfe = RFE(estimator=LinearRegression(), n_features_to_select=5)
rfe.fit(X, y)
# See ranking
ranking = pd.DataFrame({
'Feature': X.columns,
'Selected': rfe.support_,
'Rank': rfe.ranking_
}).sort_values('Rank')
print(ranking)
π Method 3: Lasso Regularization
Lasso automatically selects features! (Sets unimportant ones to 0)
from sklearn.linear_model import Lasso
lasso = Lasso(alpha=0.1)
lasso.fit(X, y)
# Selected features
selected = X.columns[lasso.coef_ != 0]
print(f"Selected: {selected.tolist()}")
π Method 4: Forward/Backward Selection
Forward: Start with 0 features, add one at a time. Backward: Start with all features, remove one at a time.
from mlxtend.feature_selection import SequentialFeatureSelector
# Forward selection
sfs = SequentialFeatureSelector(
LinearRegression(),
k_features=5,
forward=True,
scoring='r2',
cv=5
)
sfs.fit(X, y)
print(f"Selected: {sfs.k_feature_names_}")
8. Advanced Evaluation Metrics
π Beyond RΒ²
Adjusted RΒ²
Why: RΒ² always increases with more features (even useless ones!)
Formula:
Adjusted RΒ² = 1 - (1 - RΒ²) Γ (n - 1) / (n - p - 1)
Where:
n = number of samples
p = number of features
Interpretation: Penalizes for adding features that donβt help.
def adjusted_r2(r2, n, p):
return 1 - (1 - r2) * (n - 1) / (n - p - 1)
r2 = model.score(X, y)
adj_r2 = adjusted_r2(r2, X.shape[0], X.shape[1])
print(f"RΒ²: {r2:.3f}")
print(f"Adjusted RΒ²: {adj_r2:.3f}")
AIC (Akaike Information Criterion)
What: Balances model fit and complexity.
Lower is better!
Use: Compare different models on same data.
BIC (Bayesian Information Criterion)
Similar to AIC but penalizes complexity more.
import statsmodels.api as sm
X_with_const = sm.add_constant(X)
model = sm.OLS(y, X_with_const).fit()
print(f"AIC: {model.aic:.2f}")
print(f"BIC: {model.bic:.2f}")
MAPE (Mean Absolute Percentage Error)
What: Average percentage error.
Why useful: Easy to understand, scale-independent.
def mape(y_true, y_pred):
return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
mape_score = mape(y_test, y_pred)
print(f"MAPE: {mape_score:.2f}%")
# Interpretation: "Predictions are off by X% on average"
9. Confidence Intervals & Predictions
π Two Types of Intervals
Confidence Interval for Mean Prediction
What: Range for the AVERAGE outcome.
Example: βFor 2000 sq ft houses, average price is $400k Β± $20kβ
Prediction Interval for Individual
What: Range for a SINGLE outcome.
Example: βThis specific 2000 sq ft house will cost $400k Β± $50kβ
Always wider! Individual predictions are more uncertain.
π οΈ Implementation
import statsmodels.api as sm
# Fit model
X_const = sm.add_constant(X_train)
model = sm.OLS(y_train, X_const).fit()
# Make predictions with intervals
X_test_const = sm.add_constant(X_test)
predictions = model.get_prediction(X_test_const)
# Get summary
results = predictions.summary_frame(alpha=0.05)
print(results.head())
# Columns:
# mean - Prediction
# mean_ci_lower, mean_ci_upper - Confidence interval (mean)
# obs_ci_lower, obs_ci_upper - Prediction interval (individual)
10. Real-World Workflow
π― Complete Step-by-Step Process
1. UNDERSTAND THE PROBLEM
ββ What are you predicting? Why?
2. EXPLORE THE DATA
ββ Summary statistics
ββ Distribution plots
ββ Correlation analysis
ββ Missing values check
3. PREPROCESS DATA
ββ Handle missing values
ββ Handle outliers
ββ Encode categorical variables
ββ Feature engineering
ββ Split train/test
4. CHECK ASSUMPTIONS
ββ Linearity
ββ Independence
ββ Homoscedasticity
ββ Normality
ββ Multicollinearity (VIF)
5. BUILD MODEL
ββ Try simple linear first
ββ Add complexity if needed
ββ Use regularization
ββ Cross-validate
6. EVALUATE
ββ Multiple metrics (RΒ², MAE, RMSE)
ββ Adjusted RΒ²
ββ Residual analysis
ββ Cook's distance
7. INTERPRET
ββ Coefficient meaning
ββ Confidence intervals
ββ Feature importance
ββ Limitations
8. DEPLOY & MONITOR
ββ Save model
ββ Track performance
ββ Update as needed
π Pro Tips for Real Projects
Tip 1: Always Start Simple
Begin with basic linear regression before complex methods.
Tip 2: Document Everything
- Why you chose features
- Why you removed outliers
- Decision rationale
Tip 3: Visualize Often
- Distributions
- Relationships
- Residuals
- Predictions vs actual
Tip 4: Cross-Validate Always
Single train/test splits can mislead.
Tip 5: Check Assumptions
A bad model with high RΒ² is still bad!
Tip 6: Domain Knowledge Wins
The best feature engineering comes from understanding the problem.
π Summary - Your Expert Toolkit
Encoding Categorical Variables:
- One-Hot for unordered, few categories
- Ordinal for ordered categories
- Target encoding for many categories
Multicollinearity:
- VIF > 10 = problem
- Solutions: Drop, combine, PCA, regularization
Outliers/Influential Points:
- Cookβs Distance for influence
- Leverage for unusual X values
- Standardized residuals for unusual y values
Non-Linear Patterns:
- Polynomial regression for curves
- Log transformation for skewed data
Validation:
- Use cross-validation always
- Multiple metrics, not just RΒ²
- Adjusted RΒ² for fair comparison
Feature Selection:
- RFE for systematic selection
- Lasso for automatic selection
- Always validate choices
π Youβre Now an Expert!
Congratulations! You now know:
- β How to handle real-world messy data
- β How to detect and fix all assumption violations
- β How to handle categorical variables properly
- β How to deal with multicollinearity
- β How to handle outliers and influential points
- β How to apply transformations
- β How to validate models properly
- β How to select features
- β How to interpret results properly
Next Steps:
- Practice with real datasets (Kaggle)
- Learn about other ML algorithms
- Build end-to-end projects
- Read research papers
- Teach others!
Remember: The best way to become an expert is practice! π
Happy Modeling! πβ¨