Skip to content

Repeat purchase rate

What share of customers come back for a second order?

Current value (customers case data): 72.4% - ✓ On target. Open the case.

Field Value
Definition Customers with two or more distinct invoices in the window divided by customers with at least one.
Formula count(customers with invoices >= 2) / count(customers)
Unit %
Granularity Customer; window of 12 months.
Direction Higher is better ↑
Target and thresholds ≥ 60.0% (amber ≥ 50.0%)
Parameters –
Owner CRM Lead
Source Transactions (UCI Online Retail II)
Input table sales, columns customer_id, invoice_id
Recommended chart bar - By acquisition cohort, against the target line.
Related KPIs Customer retention (quarter over quarter), Revenue at risk (RFM "At risk")

How to read it

Is the first purchase turning into a relationship? Customers acquired at the end of the window have had less time to repeat - compare like with like.

Common misreadings

  • Counting invoice lines instead of distinct invoices makes every multi-item basket a "repeat".

Worked example (from the test suite)

customer_id invoice_id
a i1
a i1
a i2
b i3
b i3
c i4

Expected result: 33.33%. Distinct invoices: a = 2 (i1, i2), b = 1 (two lines of i3), c = 1. 1 of 3 customers repeats = 0.3333. Counting lines would wrongly give 2/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("repeat_purchase_rate")
def repeat_purchase_rate(df: pd.DataFrame) -> float:
    orders = df.groupby("customer_id")["invoice_id"].nunique()
    return safe_ratio((orders >= 2).sum(), len(orders))

Reference SQL (tested on SQLite):

SELECT CAST(SUM(CASE WHEN n >= 2 THEN 1 ELSE 0 END) AS REAL) / COUNT(*)
FROM (SELECT customer_id, COUNT(DISTINCT invoice_id) AS n FROM sales GROUP BY customer_id)