saas-churn-analysis
SaaS churn and retention analysis: cohort-based churn rates, retention curves, revenue churn vs logo churn, at-risk customer identification, expansion vs contraction MRR, churn recovery playbooks, and net revenue retention (NRR) benchmarking. Produces investor-ready retention charts and actionable recovery plans. Use when: analyzing why customers are churning, building cohort retention tables, calculating NRR/GRR, identifying at-risk accounts before they cancel, or presenting retention data to investors/board. NOT for: executing churn recovery outreach (use CRM/email tools), real-time subscription billing changes (use billing platform APIs), general SaaS KPI dashboards (use saas-metrics-dashboard), or revenue forecasting without churn context (use startup-financial-model).
安装 / 下载方式
totalclaw install github:LeoYeAI~openclaw-master-skills~saas-churn-analysiscurl -fsSL https://skills.taituai.com/api/skills/github%3ALeoYeAI~openclaw-master-skills~saas-churn-analysis/file -o saas-churn-analysis.md# SaaS Churn Analysis Skill
Deep-dive churn and retention analysis for SaaS businesses. Build cohort tables, calculate NRR/GRR, identify at-risk accounts, and produce investor-ready retention metrics with actionable recovery playbooks.
---
## When to Use This Skill
**Trigger phrases:**
- "Why are customers churning?"
- "What's our retention rate?"
- "Build a cohort analysis"
- "Show me net revenue retention"
- "Which accounts are at risk of canceling?"
- "Investor wants to see our logo churn"
- "What's our gross/net dollar retention?"
- "Analyze our expansion vs contraction MRR"
**NOT for:**
- Executing recovery outreach (emails, calls) — use CRM/email tools
- Billing changes, refunds, or cancellation processing — use billing platform
- General MRR tracking — use `saas-metrics-dashboard` or `subscription-revenue-tracker`
- Revenue forecasting — use `startup-financial-model`
- Customer success management — use a CS platform skill
---
## Core Churn Definitions
### Logo Churn (Customer Churn)
```
Logo Churn Rate (monthly) = Customers Lost / Customers at Start of Period
Example:
Start of month: 200 customers
Canceled: 5
Logo churn rate: 5/200 = 2.5%
```
### Revenue Churn
```
Gross Revenue Churn Rate = MRR Lost to Cancellations / MRR at Start of Period
Example:
Start MRR: $100,000
Churned MRR: $4,000 (from cancellations)
Gross churn: 4%
```
### Net Revenue Retention (NRR / NDR)
```
NRR = (Beginning MRR + Expansion MRR - Contraction MRR - Churned MRR) / Beginning MRR × 100
Components:
+ Expansion MRR: upsells, upgrades, seat additions from existing customers
- Contraction MRR: downgrades, reduced seats
- Churned MRR: cancellations
Example:
Beginning MRR: $100,000
Expansion: +$8,000
Contraction: -$2,000
Churn: -$4,000
NRR = ($100,000 + $8,000 - $2,000 - $4,000) / $100,000 = 102%
```
**NRR Benchmarks (SaaS industry):**
| NRR | Signal |
|-----|--------|
| >120% | Elite (enterprise, product-led) |
| 110–120% | Strong — expansion > churn |
| 100–110% | Healthy |
| 90–100% | Adequate — watch churn trends |
| <90% | Red flag — structural problem |
### Gross Revenue Retention (GRR)
```
GRR = (Beginning MRR - Contraction MRR - Churned MRR) / Beginning MRR × 100
(excludes expansion — pure retention, no upsell credit)
Healthy GRR benchmarks:
Enterprise SaaS: >90%
Mid-market: >85%
SMB SaaS: >75%
```
---
## Cohort Analysis
### Building a Cohort Retention Table
Track customers by their **acquisition month** and measure % remaining in each subsequent month:
```python
import pandas as pd
from datetime import datetime
def build_cohort_table(subscriptions_df: pd.DataFrame) -> pd.DataFrame:
"""
Build a cohort retention table from subscription data.
Input DataFrame columns:
- customer_id: str
- signup_date: datetime
- cancel_date: datetime | None (None = still active)
Returns:
Pivot table: rows = cohort month, columns = months_since_signup,
values = retention percentage
"""
df = subscriptions_df.copy()
df['cohort_month'] = df['signup_date'].dt.to_period('M')
df['active_through'] = df['cancel_date'].fillna(pd.Timestamp.now())
rows = []
for cohort, group in df.groupby('cohort_month'):
cohort_size = len(group)
for month_offset in range(0, 25): # 0–24 months
cutoff = cohort.to_timestamp() + pd.DateOffset(months=month_offset)
active = group[group['active_through'] >= cutoff].shape[0]
retention = active / cohort_size * 100
rows.append({
'cohort': str(cohort),
'month': month_offset,
'cohort_size': cohort_size,
'active': active,
'retention_pct': round(retention, 1)
})
result = pd.DataFrame(rows)
pivot = result.pivot(index='cohort', columns='month', values='retention_pct')
return pivot
```
**Example cohort table output:**
```
Cohort | M0 | M1 | M3 | M6 | M12
-----------|-------|-------|-------|-------|------
2025-01 | 100% | 91% | 81% | 72% | 58%
2025-02 | 100% | 93% | 84% | 76% | —
2025-03 | 100% | 89% | 79% | — | —
2025-04 | 100% | 94% | — | — | —
```
### Revenue Cohort (Dollar Retention)
Track MRR retained and expanded per cohort:
```python
def revenue_cohort_table(mrr_events_df: pd.DataFrame) -> pd.DataFrame:
"""
Revenue cohort analysis tracking MRR per acquisition cohort.
Input DataFrame columns:
- customer_id: str
- event_date: datetime
- event_type: str # 'signup', 'expansion', 'contraction', 'churn'
- mrr_change: float
Returns:
Cohort revenue retention table (% of original MRR retained+expanded)
"""
# Group by signup cohort
signups = mrr_events_df[mrr_events_df['event_type'] == 'signup'].copy()
signups['cohort_month'] = signups['event_date'].dt.to_period('M')
# For each cohort, track MRR over time
# NRR by cohort = sum(all MRR changes for cohort customers) / initial MRR
pass
```
### Churn Curve Analysis
Identify when in the customer lifecycle churn peaks:
```
Early churn (M1-M3): Onboarding failure, value not delivered
→ Diagnosis: activation rate, time-to-first-value, support tickets
Mid-term churn (M4-M12): Competitive displacement, budget cuts
→ Diagnosis: NPS trends, feature adoption, renewal engagement
Late churn (M12+): Strategic shifts, contract terms, enterprise competition
→ Diagnosis: executive sponsor changes, usage trends, renewal conversations
```
**Churn by tenure bucket:**
```python
def churn_by_tenure(subscriptions_df: pd.DataFrame) -> dict:
"""Calculate churn rate for different tenure buckets."""
buckets = {
'0-3mo': (0, 90),
'3-6mo': (90, 180),
'6-12mo': (180, 365),
'12-24mo': (365, 730),
'24mo+': (730, float('inf'))
}
results = {}
for bucket_name, (min_days, max_days) in buckets.items():
mask = (
(subscriptions_df['tenure_days'] >= min_days) &
(subscriptions_df['tenure_days'] < max_days)
)
bucket_df = subscriptions_df[mask]
if len(bucket_df) == 0:
continue
churned = bucket_df[bucket_df['cancel_date'].notna()].shape[0]
results[bucket_name] = {
'total_customers': len(bucket_df),
'churned': churned,
'churn_rate_pct': round(churned / len(bucket_df) * 100, 1)
}
return results
```
---
## At-Risk Customer Identification
### Churn Risk Scoring
Score each active customer by leading indicators:
```python
CHURN_RISK_WEIGHTS = {
'days_since_last_login': 0.25, # Usage drop
'feature_adoption_pct': -0.20, # Inverse: more features = lower risk
'support_tickets_30d': 0.15, # Escalations
'nps_score': -0.15, # Inverse: high NPS = lower risk
'days_to_renewal': -0.10, # Closer renewal = higher urgency
'billing_failures_90d': 0.15, # Payment issues
}
def churn_risk_score(customer: dict) -> float:
"""
Calculate 0-100 churn risk score for a customer.
Higher = more likely to churn.
Inputs:
customer: dict with keys matching CHURN_RISK_WEIGHTS
Returns:
Risk score 0-100 (>70 = high risk, 40-70 = medium, <40 = low)
"""
raw_score = 0
for factor, weight in CHURN_RISK_WEIGHTS.items():
if factor in customer:
# Normalize each factor to 0-100 scale first
normalized = normalize_factor(factor, customer[factor])
raw_score += normalized * weight
# Scale to 0-100
return max(0, min(100, raw_score * 100 + 50))
def get_at_risk_accounts(customers: list, threshold: float = 70.0) -> list:
"""Return customers with churn risk score above threshold, sorted by MRR."""
at_risk = [
{**c, 'risk_scor