Skip to content

Repository files navigation

SaaS MRR, Churn & Cohort Analytics

Architecture

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"]
Loading

🌐 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.py with 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.

Headline findings

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.

Business questions answered

  1. MRR Movement Waterfall — for any month, what's the contribution of New / Expansion / Contraction / Churned MRR?
  2. Net & Gross Revenue Retention (NRR / GRR) — by cohort, what fraction of starting MRR is retained 12 months later?
  3. Logo vs Revenue Churn — are we losing many small customers or a few big ones?
  4. Cohort survival curve — Kaplan-Meier estimate of probability that a customer survives N months.
  5. LTV by tier — given current churn, what's the expected lifetime value per pricing tier, and is CAC/LTV healthy?

Charts

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

How to run

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

Tech Stack

  • 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.

Project Structure

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

Methodology highlights

MRR Movement (the SaaS GAAP)

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.

Retention matrices (three of them)

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

Survival curve

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.

Skills demonstrated

  • 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

What I'd add next

  • Industry × tier churn segmentation
  • Bayesian MRR forecast with uncertainty intervals
  • Streamlit front-end for the same dashboard (project 7)

About

Subscription analytics on a synthetic SaaS dataset — 5,000 customers, 36 monthly billing snapshots, MRR waterfall, NRR/GRR cohorts, Kaplan-Meier survival, LTV/CAC by tier.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages