Hands-on classification projects covering spam detection, disease prediction, image classification, and customer churn with complete Python implementations.
The best way to truly understand classification algorithms is to build complete projects from scratch. Reading about logistic regression and decision trees gives you theoretical knowledge, but wrestling with real data gives you practical wisdom. In this lesson, we will walk through several real-world classification projects that progressively increase in complexity. Each project teaches you different aspects of the classification pipeline — from data exploration and feature engineering to model selection and evaluation.
Project 1: Email Spam Detection
Spam detection is the classic binary classification problem that has been studied since the late 1990s. Every email provider uses some version of this, and it is a fantastic first project because the feature engineering is intuitive and the datasets are freely available.
Understanding the Problem
Given an email's text content, predict whether it is spam or legitimate (ham). This is binary classification with direct business impact — false positives (marking legitimate email as spam) are extremely costly because users miss important messages, potentially losing business deals or personal connections.
The interesting challenge here is that spammers constantly evolve their tactics. They use misspellings ("v1agra"), embed text in images, or craft messages that look like legitimate notifications. A good spam classifier must generalize to new spam patterns.
Complete Implementation
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.pipeline import Pipeline
# Load the SMS Spam Collection dataset
df = pd.read_csv('spam.csv', encoding='latin-1')
df = df[['v1', 'v2']].rename(columns={'v1': 'label', 'v2': 'text'})
df['label'] = df['label'].map({'spam': 1, 'ham': 0})
print(f"Dataset shape: {df.shape}")
print(f"Spam ratio: {df['label'].mean():.2%}")
print(f"Average spam length: {df[df['label']==1]['text'].str.len().mean():.0f} chars")
print(f"Average ham length: {df[df['label']==0]['text'].str.len().mean():.0f} chars")
# Split data with stratification to maintain spam ratio
X_train, X_test, y_train, y_test = train_test_split(
df['text'], df['label'], test_size=0.2, random_state=42, stratify=df['label']
)
# Build a pipeline: TF-IDF vectorization + classifier
pipeline = Pipeline([
('tfidf', TfidfVectorizer(max_features=5000, stop_words='english',
ngram_range=(1, 2), min_df=2)),
('classifier', MultinomialNB(alpha=0.1))
])
# Train and evaluate
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
print("\n" + classification_report(y_test, y_pred, target_names=['Ham', 'Spam']))
# Test with custom messages
test_messages = [
"Congratulations! You won a free iPhone. Click here now!",
"Hey, are we still meeting for coffee tomorrow at 3pm?",
"URGENT: Your account will be suspended. Verify immediately."
]
predictions = pipeline.predict(test_messages)
for msg, pred in zip(test_messages, predictions):
print(f"{'SPAM' if pred else 'HAM'}: {msg[:50]}...")
What You Learn From This Project
The spam detection project teaches you about text feature extraction using TF-IDF (turning words into numbers), handling class imbalance (spam is typically only 13 percent of emails), choosing appropriate metrics (precision matters more than recall here), and building end-to-end pipelines that combine preprocessing and classification.
Project 2: Heart Disease Prediction
Medical diagnosis classification has life-or-death consequences, making this project excellent for understanding the precision-recall tradeoff in high-stakes scenarios.
The Clinical Context
A hospital wants to screen patients for heart disease risk using readily available measurements like blood pressure, cholesterol, and ECG results. Missing a positive case (false negative) means a patient does not receive treatment. A false positive means unnecessary further testing — expensive but not dangerous.
Here is the key insight: in medical screening, recall is more important than precision. We would rather flag healthy patients for additional tests than miss someone who actually has heart disease.
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score, roc_curve
from sklearn.pipeline import Pipeline
# Load Cleveland Heart Disease dataset (303 patients, 13 features)
df = pd.read_csv('heart.csv')
print(f"Patients: {len(df)}, Features: {df.shape[1]-1}")
print(f"Disease prevalence: {df['target'].mean():.2%}")
X = df.drop('target', axis=1)
y = df['target']
# Compare multiple classifiers with cross-validation
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
models = {
'Logistic Regression': Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(max_iter=1000, C=0.1))
]),
'Random Forest': RandomForestClassifier(n_estimators=200, max_depth=10, random_state=42),
'Gradient Boosting': GradientBoostingClassifier(n_estimators=150, learning_rate=0.1,
max_depth=4, random_state=42)
}
print("\nModel Comparison (5-fold CV):")
for name, model in models.items():
cv_scores = cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc')
model.fit(X_train, y_train)
test_auc = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])
print(f" {name}: CV AUC = {cv_scores.mean():.4f} ± {cv_scores.std():.4f}, "
f"Test AUC = {test_auc:.4f}")
# Feature importance for clinical interpretability
rf = models['Random Forest']
importances = pd.Series(rf.feature_importances_, index=X.columns)
print("\nTop 5 Risk Factors:")
for feat, imp in importances.sort_values(ascending=False).head().items():
print(f" {feat}: {imp:.4f}")
Project 3: Customer Churn Prediction
Predicting which customers will cancel their subscription is worth millions to companies. This project specifically teaches handling imbalanced datasets using SMOTE and translating model probabilities into business actions.
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import classification_report, roc_auc_score
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
# Load and preprocess telecom churn data
df = pd.read_csv('telco_churn.csv')
# Encode categorical features
for col in df.select_dtypes(include='object').columns:
if col != 'Churn':
df[col] = LabelEncoder().fit_transform(df[col])
df['Churn'] = df['Churn'].map({'Yes': 1, 'No': 0})
X = df.drop('Churn', axis=1)
y = df['Churn']
print(f"Churn rate: {y.mean():.2%}") # ~26% - moderately imbalanced
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Pipeline with SMOTE to handle imbalance
pipeline = ImbPipeline([
('scaler', StandardScaler()),
('smote', SMOTE(random_state=42)),
('clf', GradientBoostingClassifier(n_estimators=200, learning_rate=0.1, random_state=42))
])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
y_prob = pipeline.predict_proba(X_test)[:, 1]
print(f"ROC-AUC: {roc_auc_score(y_test, y_prob):.4f}")
print(classification_report(y_test, y_pred, target_names=['Stayed', 'Churned']))
Converting Predictions to Business Decisions
The real value comes from using prediction probabilities to prioritize retention efforts. Customers with 80 percent churn probability get expensive retention offers. Those with 40-60 percent get medium-cost interventions. This risk stratification maximizes the return on retention spending.
Project 4: Multi-Class Digit Recognition
This project transitions from binary to multi-class classification using the scikit-learn digits dataset — a simpler version of MNIST that runs without GPU.
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.svm import SVC
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.preprocessing import StandardScaler
digits = load_digits()
X, y = digits.data, digits.target
print(f"Samples: {X.shape[0]}, Features: {X.shape[1]}, Classes: {len(set(y))}")
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# SVM with RBF kernel excels at digit recognition
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
svm = SVC(kernel='rbf', C=10, gamma='scale')
svm.fit(X_train_s, y_train)
y_pred = svm.predict(X_test_s)
print(f"Accuracy: {(y_pred == y_test).mean():.4f}") # ~98.6%
Scaling Up: From Notebook to Production
After building these projects in Jupyter notebooks, the next step is thinking about deployment. A spam classifier that runs in a notebook is interesting; one deployed as an API serving real-time predictions is valuable. Consider wrapping your best model in a Flask or FastAPI endpoint, containerizing it with Docker, and deploying to a cloud service. Each project above can become a portfolio piece that demonstrates not just ML knowledge but engineering maturity.
The transition from notebook to production also forces you to think about data pipelines (how does new data reach your model?), model monitoring (is accuracy degrading over time?), and retraining schedules (how often should you update the model with new data?). These operational concerns are what separate hobby projects from professional ML work.
Universal Project Principles
After building these projects, internalize these patterns: always start simple with logistic regression as a baseline, stratify your train-test splits for imbalanced data, use cross-validation for robust estimates, tune hyperparameters systematically with grid search, and report metrics appropriate to the business context. Build all four projects for a strong portfolio demonstrating practical classification skills across different domains and challenges.