Net Promoter Score (simulated survey)¶
Would our customers recommend us?
Current value (customers case data): +12 - ! Watch. Open the case.
| Field | Value |
|---|---|
| Definition | Percentage of promoters (score 9-10) minus percentage of detractors (score 0-6) among respondents. Passives (7-8) count in the denominator only. |
| Formula | 100 * (count(score >= 9) - count(score <= 6)) / count(responses) |
| Unit | score (-100 to +100) |
| Granularity | Survey response; reported monthly with its sample size. |
| Direction | Higher is better ↑ |
| Target and thresholds | ≥ +30 (amber ≥ +10) |
| Parameters | – |
| Owner | Customer Experience Lead |
| Source | Simulated survey (synthetic generator) |
| Input | table survey, columns score |
| Recommended chart | bar - Stacked 100% bar with the three groups (only three parts) plus the score as a label. |
| Related KPIs | Revenue at risk (RFM "At risk"), Customer retention (quarter over quarter) |
How to read it¶
A range from -100 to +100, not a percentage and not an average. With small samples the confidence interval is wide; show n next to it.
Common misreadings¶
- Averaging the 0-10 scores (e.g. 7.8/10) is not NPS and hides polarisation.
- Averaging segment NPS values without weighting by respondents gives the wrong total.
- Treating a 3-point monthly change as real when the margin of error is +/- 8 points.
Worked example (from the test suite)¶
| score |
|---|
| 10 |
| 9 |
| 9 |
| 8 |
| 7 |
| 6 |
| 3 |
| 10 |
| 0 |
| 9 |
Expected result: +20. Promoters (9-10): 10, 9, 9, 10, 9 = 5. Detractors (0-6): 6, 3, 0 = 3. Passives: 2. NPS = 100 * (5 - 3) / 10 = +20. The mean score 7.1 is not NPS.
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("nps")
def nps(df: pd.DataFrame) -> float:
scores = df["score"].dropna()
if scores.empty:
return float("nan")
return 100 * ((scores >= 9).sum() - (scores <= 6).sum()) / len(scores)
Reference SQL (tested on SQLite):
SELECT 100.0 * (SUM(CASE WHEN score >= 9 THEN 1 ELSE 0 END) - SUM(CASE WHEN score <= 6 THEN 1 ELSE 0 END)) / COUNT(*)
FROM survey