Skip to content

Project Brief — Customer Churn Prediction

A telecom company loses roughly 15–25% of its subscriber base every year to churn. Acquiring a new customer costs five to seven times more than retaining an existing one. The business case for a churn model is simple: if you can flag the right customers two weeks before they leave, the retention team can intervene.

Your job is to build that flag.


Business Objective

Identify customers who are likely to churn in the next billing cycle so the retention team can offer them a targeted incentive before they cancel.

Success metric: F1 score on the churned class (label = 1). We care about both precision and recall — false negatives cost retention spend on the wrong customers, false positives mean real churners slip through.

Minimum bar: F1 (churn class) > 0.60 on the held-out test set.

Info

F1 is the harmonic mean of precision and recall. It is the right metric here because the churn class is a minority class and we care about both types of error. Accuracy would be misleading — a model that predicts "no churn" for every customer would be ~75% accurate but useless.


Project Phases

Phase What you produce
EDA and Cleaning Clean dataset, written findings, no surprises
Feature Engineering Encoded, scaled, leak-free feature matrix
Modeling Baseline + three models, CV comparison
Evaluation Test set metrics, feature importance, threshold analysis
Report Written results section, business recommendation

Dataset

Run the cell below once at the top of your notebook. Every subsequent file in this project references the resulting df object.

import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 1000

# --- Raw features ---
tenure_months    = rng.integers(1, 72, size=n)
monthly_charges  = rng.uniform(20, 120, size=n).round(2)
num_products     = rng.integers(1, 5, size=n)
support_calls    = rng.integers(0, 10, size=n)
has_tech_support = rng.choice([0, 1], size=n, p=[0.4, 0.6])

contract_type = rng.choice(
    ["Month-to-Month", "One Year", "Two Year"],
    size=n,
    p=[0.55, 0.25, 0.20],
)
payment_method = rng.choice(
    ["Electronic Check", "Mailed Check", "Bank Transfer", "Credit Card"],
    size=n,
    p=[0.35, 0.25, 0.20, 0.20],
)

# --- Churn label (correlated with support calls, short tenure, M2M contract) ---
churn_score = (
    0.03 * support_calls
    - 0.015 * tenure_months
    + 0.004 * monthly_charges
    + 0.30 * (contract_type == "Month-to-Month").astype(float)
    - 0.20 * has_tech_support
    + rng.normal(0, 0.15, size=n)
)
churn_prob = 1 / (1 + np.exp(-churn_score))   # sigmoid → probability
churn      = (churn_prob > 0.50).astype(int)

# --- Introduce realistic messiness ---
# ~4% missing values in monthly_charges
missing_idx = rng.choice(n, size=int(0.04 * n), replace=False)
monthly_charges_with_na = monthly_charges.astype(float).copy()
monthly_charges_with_na[missing_idx] = np.nan

# ~2% duplicate rows
dup_idx = rng.choice(n, size=int(0.02 * n), replace=False)

df = pd.DataFrame({
    "customer_id":     [f"CUST_{i:04d}" for i in range(n)],
    "tenure_months":   tenure_months,
    "monthly_charges": monthly_charges_with_na,
    "num_products":    num_products,
    "support_calls":   support_calls,
    "has_tech_support":has_tech_support,
    "contract_type":   contract_type,
    "payment_method":  payment_method,
    "churn":           churn,
})

# Inject duplicates
df = pd.concat([df, df.iloc[dup_idx]], ignore_index=True)
df.shape
# Output: (1020, 9)

Success

After running this cell you should have a DataFrame with 1,020 rows and 9 columns. The churn rate should be approximately 27–32%. Verify with df["churn"].mean().round(3).


Data Dictionary

Column Type Description
customer_id string Unique customer identifier — drop before modeling
tenure_months int Months the customer has been with the company
monthly_charges float Monthly bill amount in USD (4% missing)
num_products int Number of active product subscriptions (1–4)
support_calls int Support calls made in the last 3 months (0–9)
has_tech_support int 1 if customer has tech support add-on
contract_type string Billing contract: Month-to-Month / One Year / Two Year
payment_method string How the customer pays
churn int Target — 1 if churned, 0 if retained

Warning

customer_id is an identifier, not a feature. If you accidentally include it in your feature matrix, tree models will overfit to it and your test score will look artificially high. Drop it before building any feature pipeline.


Project Folder Structure

Organise your work like this before you write a single line of model code:

churn-prediction/
├── notebook.ipynb          ← single notebook, cells in order
├── README.md               ← project summary (filled in at the end)
└── requirements.txt        ← pandas, scikit-learn, matplotlib, seaborn

For a team project, separate the notebook into scripts (src/). For this solo exercise, one well-organised notebook is fine.

Tip

Name your notebook sections with markdown headers that match the project phases. Reviewers and interviewers will skim your notebook top-to-bottom — make it easy for them to jump to any phase without reading everything.


00-agenda | 02-eda-and-cleaning