We have done EDA, A/B testing, customer segmentation, fraud detection (you can find them in the archive → link).
Today we are doing churn prediction.
Most churn projects end with a list of customers who might leave. That is useful, but it is only half of what a business actually needs.
The version we are building today answers two questions: who is likely to churn, and how much revenue is at risk if they do? That second number is what turns a data project into a business decision.
Before we proceed - a small ad. Your clicks on the ads help to cover newsletter hosting fees and make me $1. Thank you!
1,000+ Proven ChatGPT Prompts That Help You Work 10X Faster
ChatGPT is insanely powerful.
But most people waste 90% of its potential by using it like Google.
These 1,000+ proven ChatGPT prompts fix that and help you work 10X faster.
Sign up for Superhuman AI and get:
1,000+ ready-to-use prompts to solve problems in minutes instead of hours—tested & used by 1M+ professionals
Superhuman AI newsletter (3 min daily) so you keep learning new AI tools & tutorials to stay ahead in your career—the prompts are just the beginning
Why churn matters
Acquiring a new customer costs five to seven times more than retaining an existing one. Any marketer will tell you this.
Every subscription business — SaaS, streaming, fitness apps — lives or dies by its churn rate.
But "here are 500 customers who might leave" is not actionable (this word is so overused, but it is indeed important). "We stand to lose $84,000 in monthly recurring revenue if we do nothing, and these are the 20 customers worth calling first" — that is actionable.
That is what we are building.
The dataset
We are using the Predictive Analytics for Customer Churn dataset from Kaggle — a subscription streaming service with 21 columns and 243,000+ customers.
Download it here: https://www.kaggle.com/datasets/safrin03/predictive-analytics-for-customer-churn-dataset
It comes as two files — train.csv and test.csv. We will use train.csv for this project.
Key columns:
CustomerID — unique identifier
AccountAge — how many months they have been a subscriber
SubscriptionType — Basic, Standard or Premium
MonthlyCharges — what they pay per month (range $5–$20)
ViewingHoursPerWeek — how much content they watch
AverageViewingDuration — average length of each session
SupportTicketsPerMonth — how often they contact support
GenrePreference — Sci-Fi, Action, Fantasy, Drama, Comedy
WatchlistSize — number of items saved to their watchlist
Churn — our target variable (1 = churned, 0 = stayed)
Step 1 — Load and explore
# pip install pandas scikit-learn matplotlib seaborn
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.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
from sklearn.preprocessing import LabelEncoder
sns.set_theme(style='whitegrid')
df = pd.read_csv('train.csv')
print(df.shape) # 243,787 rows, 21 columns
print(df.head())
print(df.isnull().sum())
# Check churn rate
print(df['Churn'].value_counts(normalize=True).round(3))You will find an 18.1% churn rate — more realistic than the perfectly balanced datasets often used in beginner tutorials, and still imbalanced enough to matter for model evaluation.
Step 2 — Explore what drives churn
Before building anything, understand who is churning and why.
# Churn rate by subscription type
churn_by_sub = df.groupby('SubscriptionType')['Churn'].mean().sort_values(ascending=False)
churn_by_sub.plot(kind='bar', color='coral', figsize=(8, 4))
plt.title('Churn Rate by Subscription Type')
plt.ylabel('Churn Rate')
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()Basic subscribers churn at 19.7% vs 16.3% for Premium — customers on the cheapest plan are most at risk. This makes business sense: they have the least invested in the service.
# Do churners watch less content?
df.boxplot(column='ViewingHoursPerWeek', by='Churn', figsize=(8, 5))
plt.title('Viewing Hours by Churn Status')
plt.suptitle('')
plt.show()
print(df.groupby('Churn')['ViewingHoursPerWeek'].mean().round(2))Churners watch an average of 17.4 hours per week vs 21.2 hours for retained customers. Low engagement is almost always a leading indicator of churn — if a customer stops watching, they are thinking about cancelling. That gives you a window to intervene before they do.
# Account age comparison
print(df.groupby('Churn')['AccountAge'].mean().round(2))Churned customers have an average account age of 45.7 months vs 63.3 months for retained customers. Newer subscribers are significantly more at risk — the first few months are the critical retention window.
# Support tickets
print(df.groupby('Churn')['SupportTicketsPerMonth'].mean().round(2))Churners raise more support tickets on average (5.0 vs 4.4). A spike in support contacts is often a warning sign worth flagging to the customer success team.
Step 3 — Prepare the data
# Drop CustomerID — it is just an identifier
df = df.drop('CustomerID', axis=1)
# Encode categorical columns
cat_cols = ['SubscriptionType', 'PaymentMethod', 'ContentType',
'GenrePreference', 'DeviceRegistered', 'Gender',
'PaperlessBilling', 'MultiDeviceAccess',
'ParentalControl', 'SubtitlesEnabled']
le = LabelEncoder()
for col in cat_cols:
df[col] = le.fit_transform(df[col].astype(str))
# This dataset contains no missing values so no additional cleaning is required
# but it is good practice to always check
if df.isnull().sum().sum() > 0:
df = df.dropna()
print(df.shape)Step 4 — Build the model
X = df.drop('Churn', axis=1)
y = df['Churn']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
model = RandomForestClassifier(
n_estimators=100,
class_weight='balanced',
random_state=42,
n_jobs=-1 # use all CPU cores
)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1] # churn probability per customerWe are saving y_prob — the churn probability for every customer. We need this for the revenue layer in the next step.
Step 5 — Evaluate properly
print(classification_report(y_test, y_pred, target_names=['Stayed', 'Churned']))
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Stayed', 'Churned'])
disp.plot(cmap='Blues')
plt.title('Confusion Matrix')
plt.show()Focus on recall for the Churned class — what percentage of actual churners did the model catch?
In many churn projects, recall is more important than overall accuracy because every missed churner represents potential lost revenue.
That said, businesses also need to balance recall with precision so they do not spend retention budgets on customers who were unlikely to leave.
Step 6 — The business layer: revenue at risk
This is the step most tutorials skip. And it is the most important one.
# Build a results dataframe for the test set
results = X_test.copy()
results['Churn_Actual'] = y_test.values
results['Churn_Probability'] = y_prob
results['MonthlyRevenue'] = results['MonthlyCharges']
# Expected monthly revenue at risk = probability of churn × monthly revenue
results['RevenueAtRisk'] = results['Churn_Probability'] * results['MonthlyRevenue']
# Sort by revenue at risk — highest priority first
results = results.sort_values('RevenueAtRisk', ascending=False)
# Total revenue at risk
total_at_risk = results['RevenueAtRisk'].sum()
print(f"Total monthly revenue at risk: ${total_at_risk:,.0f}")
# Top 20 customers to contact first
top_20 = results[['Churn_Probability', 'MonthlyRevenue', 'RevenueAtRisk']].head(20)
print(top_20)This is the output that changes a model into a retention strategy. The retention team does not call 48,000 customers.
They call the top 20 — the customers with the highest expected revenue at risk.
Step 7 — Feature importance
importances = pd.Series(model.feature_importances_, index=X.columns)
top_features = importances.sort_values(ascending=False).head(10)
top_features.plot(kind='barh', color='steelblue', figsize=(8, 5))
plt.title('Top Features Driving Churn')
plt.xlabel('Importance Score')
plt.gca().invert_yaxis()
plt.tight_layout()
plt.show()What you tell stakeholders: "Account age, viewing hours, and monthly charges are the strongest predictors of churn. Customers with newer accounts and lower engagement consistently appear to be at higher risk, making them strong candidates for proactive retention campaigns."
That is completely supported by your model — and it is a sentence a CMO can act on.
How to turn this into a portfolio project
Overview — 243,000+ subscription streaming customers, goal is to predict churn and quantify revenue at risk to prioritise retention outreach.
Approach — exploratory analysis by subscription type, viewing hours and account age, Random Forest with balanced class weights, evaluated primarily using recall rather than accuracy because churn prediction is an imbalanced classification problem, revenue at risk layer added to prioritise which customers to contact first.
A machine learning model predicts behaviour. Businesses make decisions using money. Translating churn probabilities into expected revenue is what connects the model to a real business decision.
Findings — 18.1% overall churn rate. Basic subscribers churn at the highest rate. Low viewing hours and short account age are the strongest early warning signals. Fill in your actual recall score and total revenue at risk from your run.
Limitations — no cost data for retention campaigns so no ROI calculation included, monthly charges range is narrow ($5–$20) which limits revenue differentiation, model would need retraining as subscriber behaviour evolves.
Keep pushing 💪,
Karina
New video this week
My latest YouTube video covers 10 data concepts that pro analysts use every day — and that beginner courses rarely teach. Things like granularity, NULL vs zero, window functions, cohort analysis, and why correlation is not causation.
If you are in your first data role or preparing for interviews, this one is worth watching.
Watch it here → link
Just starting with Python? Wondering if programming is for you?
Master key data analysis tasks like cleaning, filtering, pivot and grouping data using Pandas, and learn how to present your insights visually with Matplotlib with ‘Data Analysis with Python’ masterclass.
Already know the basics and want something more hands-on?
Take the Python Challenge.
You'll work through a real business problem, complete a portfolio-ready project, and practise the kind of analysis employers expect from junior analysts.
👉 Start with the Masterclass if you're a complete beginner.
👉 Choose the Python Challenge if you're comfortable with the fundamentals and want to apply them to a real project.
Data Analyst & Data Scientist


