Skip to content

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