flowchart LR
GEN["src/generate_data.py<br/>(deterministic, seed 42)"] --> DATA["data/<br/>customers.parquet<br/>subscriptions.parquet"]
DATA --> METRICS["src/metrics.py<br/>MRR waterfall · retention<br/>Kaplan-Meier · LTV"]
METRICS --> ANALYZE["src/analyze.py"]
METRICS --> BOOT["src/retention_bootstrap.py<br/>95% bootstrap CI on Logo/GRR/NRR"]
ANALYZE --> CHARTS["outputs/01-06 PNGs"]
BOOT --> CHARTS_CI["outputs/07_retention_with_ci.png<br/>outputs/retention_with_ci.md"]
ANALYZE --> EXTRACT["src/extract_key_numbers.py<br/>→ README/MEMO numbers"]
🌐 Live walkthrough: https://ucazin.github.io/saas-mrr-churn-analytics/
Subscription analytics on a realistic synthetic SaaS dataset — 5,000 customers, 36 months of monthly billing snapshots, 3 pricing tiers, deterministic seed. Built to answer the questions every B2B SaaS CFO and Head of Growth asks every Monday morning.
Data note. The dataset is procedurally generated by
src/generate_data.pywith a fixed seed (no external download). Churn hazard, expansion rate, and seat-growth distributions are tuned to match the public SaaS benchmarks (ChartMogul, OpenView, SaaS Capital), so the resulting metrics fall in realistic ranges.
| Metric | Value |
|---|---|
| Ending MRR (month 36) | $1.99 M |
| 6-month avg MoM growth | +4.8% |
| Median NRR @ 12 months | 85.2% |
| Median GRR @ 12 months | 67.5% |
| Median logo retention @ 12 months | 52.2% |
| Survival @ 12 / 24 months | 53.9% / 41.4% |
| LTV / CAC — Pro tier | 15.3× |
| LTV / CAC — Growth tier | 17.0× |
| LTV / CAC — Starter tier | 10.9× |
| Pro tier share of MRR | 55.6% (with 30.6% of logos) |
Headline insight. Churn is heavily concentrated in months 3–6 of customer life — 7–9% monthly churn versus 2–3% in months 1–2 and 3–6% from month 7 onward. This is the classic post-honeymoon drop-off, and it costs ~30% of the cohort before they reach the first renewal.
- MRR Movement Waterfall — for any month, what's the contribution of New / Expansion / Contraction / Churned MRR?
- Net & Gross Revenue Retention (NRR / GRR) — by cohort, what fraction of starting MRR is retained 12 months later?
- Logo vs Revenue Churn — are we losing many small customers or a few big ones?
- Cohort survival curve — Kaplan-Meier estimate of probability that a customer survives N months.
- LTV by tier — given current churn, what's the expected lifetime value per pricing tier, and is CAC/LTV healthy?
All charts are in outputs/:
| # | Chart | What it answers |
|---|---|---|
| 01 | 01_mrr_waterfall.png |
MRR composition month over month |
| 02 | 02_logo_retention.png |
Logo retention by cohort |
| 03a | 03a_nrr.png |
Net revenue retention by cohort |
| 03b | 03b_grr.png |
Gross revenue retention by cohort |
| 04 | 04_survival_curve.png |
Customer survival (Kaplan-Meier) |
| 05 | 05_ltv_vs_cac.png |
LTV vs CAC by tier |
| 06 | 06_churn_by_tenure.png |
Monthly churn rate × months since signup |
py -3.12 -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
# 1. Generate the dataset (~3 s)
python src/generate_data.py
# 2. Run all analyses and render the 7 charts
python src/analyze.py
# Optional: print the headline numbers used in this README and MEMO.md
python src/extract_key_numbers.py- Python 3.10+ — pandas, numpy, matplotlib, seaborn, pyarrow
- DuckDB (optional) — for an SQL view of the same data
- Synthetic data — deterministic, reproducible, generated by
src/generate_data.py. No external download needed.
02-saas-mrr-churn-cohort/
├── src/
│ ├── generate_data.py # Synthetic SaaS dataset (run once)
│ ├── metrics.py # Reusable: mrr_movements, retention, survival, LTV
│ ├── analyze.py # All 7 charts + headline stats
│ └── extract_key_numbers.py # Pull numbers used in README/MEMO
├── data/ # Generated parquet (gitignored)
├── outputs/ # Charts (committed)
├── notebooks/
│ └── walkthrough.md # Written narrative of findings
├── MEMO.md # One-page business memo with recommendations
├── README.md
├── LICENSE # MIT
├── requirements.txt
└── .gitignore
For each month m, ending MRR decomposes into:
EndingMRR(m) = StartingMRR(m)
+ NewMRR (new logos this month)
+ ExpansionMRR (existing logos upgraded / added seats)
- ContractionMRR (existing logos downgraded)
- ChurnedMRR (logos that left this month)
This is the model used by ChartMogul, Maxio, Stripe Sigma, and every Series B finance team.
Indexed by [acquisition_cohort_month][months_since_acquisition]:
- Logo retention = customers still active / customers in cohort
- GRR = retained MRR / starting MRR (excludes expansion)
- NRR = retained MRR + expansion / starting MRR
Kaplan-Meier estimator on customer-month observations, right-censored for customers still active at the end of the observation window. Implemented from scratch in metrics.survival_curve — no lifelines dependency.
- SaaS metric definitions that match industry standards (ChartMogul / Bessemer / OpenView)
- Multi-step pandas pipelines that produce auditable intermediate tables
- Cohort matrix construction and heatmap visualisation
- Survival analysis basics (Kaplan-Meier) without external libraries
- Reproducibility — deterministic seed, idempotent generator, no external dependencies
- Industry × tier churn segmentation
- Bayesian MRR forecast with uncertainty intervals
- Streamlit front-end for the same dashboard (project 7)