Data Science project 13
Customer Churn Analysis Data Science Project
Build a complete churn analysis workflow with reproducible Python code, traceable calculations, validation checks, and honest limitations.
Explore Data Science training in VizagView all project ideas
Analysis question
Where does descriptive churn rate vary across contract types and tenure bands?
Dataset
Two thousand synthetic subscriptions with contract type, tenure, support-call count, monthly fee, and a generated churn outcome.
Requirements
- Python 3.10 or later
- A terminal or command prompt
python -m pip install numpy pandas- About 45-60 minutes to build and review
Method and data checks
Calculate the overall rate, aggregate customer count, rate, and average fee by contract, create explicit tenure bands, and aggregate rates by tenure.
- Rates use customer-level binary outcomes
- Every customer appears in exactly one contract group
- Tenure bins have explicit boundaries
- Associations are not presented as causal explanations
Complete Python code
Save the program as ds_churn_analysis.py. The dataset generator or loader, analysis functions, output, and reproducibility controls are included.
"""Descriptive customer churn analysis on synthetic subscriptions."""
from __future__ import annotations
import numpy as np
import pandas as pd
def make_demo_subscriptions(n: int = 2_000, seed: int = 42) -> pd.DataFrame:
rng = np.random.default_rng(seed)
contract = rng.choice(["monthly", "annual", "two_year"], n, p=[0.58, 0.3, 0.12])
tenure = rng.integers(1, 73, n)
support_calls = rng.poisson(1.7, n)
monthly_fee = rng.uniform(399, 2499, n)
logit = -2.4 + 1.15 * (contract == "monthly") - 0.018 * tenure + 0.28 * support_calls + 0.00022 * (monthly_fee - 1000)
churned = rng.binomial(1, 1 / (1 + np.exp(-logit)))
return pd.DataFrame({"customer_id": range(1, n + 1), "contract": contract, "tenure_months": tenure, "support_calls": support_calls, "monthly_fee": monthly_fee.round(2), "churned": churned})
def analyse_churn(data: pd.DataFrame) -> dict[str, object]:
churn = data.copy()
churn["tenure_band"] = pd.cut(churn["tenure_months"], [0, 6, 12, 24, 48, 100], labels=["0-6", "7-12", "13-24", "25-48", "49+"])
by_contract = churn.groupby("contract", as_index=False).agg(customers=("customer_id", "size"), churn_rate=("churned", "mean"), average_fee=("monthly_fee", "mean"))
by_tenure = churn.groupby("tenure_band", observed=True, as_index=False).agg(customers=("customer_id", "size"), churn_rate=("churned", "mean"))
return {"overall_rate": float(churn["churned"].mean()), "by_contract": by_contract, "by_tenure": by_tenure}
def main() -> None:
result = analyse_churn(make_demo_subscriptions())
print(f"Overall churn: {result['overall_rate']:.1%}")
print(result["by_contract"].round(3).to_string(index=False))
print("\nBy tenure:\n", result["by_tenure"].round(3).to_string(index=False))
print("Observed segments suggest questions; they do not prove why customers left.")
if __name__ == "__main__":
main()
Run the project
- Create and activate a virtual environment.
- Install dependencies with
python -m pip install numpy pandas. - Run
python ds_churn_analysis.py. - Reconcile row counts and totals before interpreting patterns.
- Read the limitations before substituting any real dataset.
Expected analytical output
Overall churn rate plus contract and tenure comparison tables.
Interpretation and responsible-use limits
A segment-level rate does not show why an individual left or prove that a retention action will work. Real use requires label definitions, time-aware cohorts, fairness review, and experiments.
Ways to extend the project
Add acquisition cohorts, cancellation reasons, survival curves, revenue retention, time-based comparison, intervention tests, and uncertainty intervals.
Continue learning Data Science
Try the next project, return to the Softenant project library, or explore the Data Science course in Vizag for guided data cleaning, analysis, visualisation, and portfolio feedback.