Revenue at risk (RFM "At risk")¶
How much of our revenue comes from good customers who have stopped buying?
Current value (customers case data): 9.4% - ✓ On target. Open the case.
| Field | Value |
|---|---|
| Definition | Revenue in the look-back window from customers in the RFM "At risk" segment (high frequency/monetary score, low recency score) divided by total revenue. |
| Formula | sum(monetary where segment = 'At risk') / sum(monetary) |
| Unit | % |
| Granularity | Customer; recomputed monthly on a 12-month window. |
| Direction | Lower is better ↓ |
| Target and thresholds | ≤ 10.0% (amber ≤ 20.0%) |
| Parameters | – |
| Owner | CRM Lead |
| Source | Transactions (UCI Online Retail II), RFM scoring in this repo |
| Input | table customer_rfm, columns segment, monetary |
| Recommended chart | bar - Revenue by segment, sorted, with the At-risk bar highlighted. |
| Related KPIs | Customer retention (quarter over quarter), Repeat purchase rate, Net Promoter Score (simulated survey) |
How to read it¶
The size of the win-back opportunity. The segment list is the action list: call the top customers by monetary value first.
Common misreadings¶
- RFM scores are quintiles of this dataset - an "At risk" label is relative, not absolute.
- Recency depends on the snapshot date; recompute on a fixed calendar to keep it comparable.
Worked example (from the test suite)¶
| segment | monetary |
|---|---|
| Champions | 500.0 |
| At risk | 200.0 |
| At risk | 100.0 |
| Hibernating | 200.0 |
Expected result: 30.00%. At risk revenue 200 + 100 = 300 of 1000 = 0.3.
This example is asserted in tests/test_formulas.py against the Python function and in tests/test_sql.py against the SQL below.
Implementations¶
Python function (the single implementation used by the site):
@kpi("revenue_at_risk_share")
def revenue_at_risk_share(df: pd.DataFrame) -> float:
at_risk = df.loc[df["segment"].eq("At risk"), "monetary"].sum()
return safe_ratio(at_risk, df["monetary"].sum())
Reference SQL (tested on SQLite):
SELECT SUM(CASE WHEN segment = 'At risk' THEN monetary ELSE 0 END) / SUM(monetary) FROM customer_rfm