01Loading the dataset

We'll use the Telco Customer Churn dataset from IBM, a widely-used open dataset of 7,043 telecom customers with 21 features describing their account, services and demographics — and whether they churned.

data_loading.py
import pandas as pd
import numpy as np

# Telco Customer Churn dataset (IBM, openly available)
url = "https://raw.githubusercontent.com/IBM/telco-customer-churn-on-icp4d/master/data/Telco-Customer-Churn.csv"
df = pd.read_csv(url)

print(df.shape)
print(df.head())
print(df['Churn'].value_counts())
Output

(7043, 21) — 7,043 customers. Churn distribution: 5,174 stayed (No), 1,869 churned (Yes) — about 26.5% churn rate, which tells us immediately this is an imbalanced classification problem.

02Exploratory data analysis

We examine which factors correlate most with churn — contract type, tenure, and monthly charges are usually the strongest signals in telecom churn data.

eda.py
import matplotlib.pyplot as plt
import seaborn as sns

# Convert TotalCharges to numeric (it's stored as string with some blanks)
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'], errors='coerce')
df = df.dropna(subset=['TotalCharges'])

# Churn rate by contract type
churn_by_contract = df.groupby('Contract')['Churn'].apply(
    lambda x: (x == 'Yes').mean()
)
print(churn_by_contract)

# Tenure distribution by churn status
plt.figure(figsize=(8, 5))
sns.boxplot(data=df, x='Churn', y='tenure', palette=['#1a4a3a', '#e24b4a'])
plt.title('Customer Tenure by Churn Status')
plt.savefig('tenure_by_churn.png')
Key finding

Month-to-month contracts churn at ~42% vs just ~3% for two-year contracts. Customers who churn also have much lower median tenure — they tend to leave within the first few months. This single insight (contract type) is often the strongest lever telecom companies have to reduce churn.

03Preprocessing & encoding

Logistic regression needs numeric input, so we encode categorical columns (Contract, PaymentMethod, etc.) using one-hot encoding, then split into train/test sets.

preprocessing.py
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# Drop customer ID, encode target
df = df.drop('customerID', axis=1)
df['Churn'] = df['Churn'].map({'Yes': 1, 'No': 0})

# One-hot encode all categorical columns
categorical_cols = df.select_dtypes(include='object').columns
df_encoded = pd.get_dummies(df, columns=categorical_cols, drop_first=True)

# Separate features and target
X = df_encoded.drop('Churn', axis=1)
y = df_encoded['Churn']

# Train/test split, stratified to preserve churn ratio in both sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# Scale numeric features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

print(f"Training samples: {X_train.shape[0]}, Features: {X_train.shape[1]}")

04Training the model

We train a Logistic Regression classifier with class_weight='balanced' — this tells the model to pay more attention to the minority class (churned customers), which is critical since they're under-represented.

train_model.py
from sklearn.linear_model import LogisticRegression

# class_weight='balanced' compensates for the 73/27 class imbalance
model = LogisticRegression(class_weight='balanced', max_iter=1000, random_state=42)
model.fit(X_train_scaled, y_train)

# Examine which features drive churn prediction most
coefficients = pd.DataFrame({
    'Feature': X.columns,
    'Coefficient': model.coef_[0]
}).sort_values('Coefficient', ascending=False)

print("Top 5 features increasing churn risk:")
print(coefficients.head())
print("\nTop 5 features decreasing churn risk:")
print(coefficients.tail())

05Evaluation

Accuracy alone is misleading on imbalanced data — a model that always predicts "no churn" would be 73% accurate while being useless. Instead we evaluate with precision, recall, and ROC-AUC, which properly account for the imbalance.

evaluate.py
from sklearn.metrics import classification_report, roc_auc_score, confusion_matrix
import seaborn as sns

y_pred = model.predict(X_test_scaled)
y_proba = model.predict_proba(X_test_scaled)[:, 1]

print(classification_report(y_test, y_pred, target_names=['Stayed', 'Churned']))
print(f"ROC-AUC Score: {roc_auc_score(y_test, y_proba):.4f}")

# Confusion matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6, 5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Greens',
            xticklabels=['Stayed', 'Churned'],
            yticklabels=['Stayed', 'Churned'])
plt.title('Confusion Matrix')
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.savefig('confusion_matrix.png')
MetricChurned ClassWhat it means
Precision0.5151% of customers flagged as "will churn" actually do
Recall0.79Model catches 79% of customers who actually churn
ROC-AUC0.84Strong ability to rank churners above non-churners

Notice the deliberate tradeoff: recall (0.79) is prioritised over precision (0.51) using class_weight='balanced'. In churn prediction, missing an actual churner (false negative) usually costs more than wrongly flagging a loyal customer (false positive) — so this tradeoff is intentional, not a flaw.

06Real-world use case

Where this exact technique is used in industry

Telecom Retention Teams
Airtel, Jio and Vodafone run churn models like this weekly, triggering retention calls or discount offers to customers flagged as high-risk before they leave.
SaaS Subscription Products
Subscription software companies use the same approach to predict which accounts are at risk of not renewing, prioritising customer success outreach.
Banking & Insurance
Banks use churn models to predict which customers might close accounts or switch providers, informing proactive relationship management.
OTT & Streaming Platforms
Streaming services predict subscription cancellation risk to time personalised content recommendations or pricing offers.

This project shows why classification problems need more thought than just calling .fit() — handling class imbalance correctly and choosing the right evaluation metric for the business problem is what separates a working churn model from a misleading one.

Want to build this with guided support?

Classification, imbalanced data and evaluation metrics are all covered hands-on in our Machine Learning training programme.

View Training Programs WhatsApp Us