RFM — простая, но проверенная методика сегментации клиентов. Возникла в direct-marketing 1990-х, когда рассылку по каталогу нужно было таргетировать без ML и нейросетей. Сейчас используется везде: e-commerce, ритейл, доставка, банки, SaaS.
Три измерения:
- R (Recency) — как давно клиент покупал в последний раз. Меньше — лучше.
- F (Frequency) — сколько покупок было за период. Больше — лучше.
- M (Monetary) — сколько денег принёс за период. Больше — лучше.
Каждый клиент получает оценку от 1 до 5 по каждому измерению (квинтили). Получается код вида «5-5-5» (идеальный клиент) или «1-1-1» (потерянный). На основе этих кодов клиенты группируются в 10-12 сегментов с бизнес-именами: «Champions», «At Risk», «Hibernating» и т.д.
Главный плюс: в отличие от сложной сегментации (k-means, DBSCAN), этот метод бизнес понимает без обучения. Маркетолог и CEO разбираются в сегментах сразу.
Архитектурный выбор: ETL или DAX
RFM можно реализовать двумя путями:
- Полностью в ETL. В SQL считаем R/F/M, квинтили через NTILE(5), складываем в dim_клиент как материализованные колонки. В Power BI просто отображаем. Быстро, стабильно, но сегмент не пересчитывается под фильтры в отчёте.
- В DAX динамически. R/F/M считаются мерами от текущего контекста (например, «за последние 12 месяцев» из срез). Пересчёт на лету под любой выбранный период. Дороже по производительности, но гибче.
На практике обычно работает гибрид: базовую сегментацию делаем в ETL (fixed window — 12 месяцев от текущей даты, пересчёт раз в сутки), а динамическая версия висит отдельной вкладкой для аналитиков. Разберём DAX-путь — он сложнее и полезнее как референс.
Модель данных
Минимально нужно:
fact_orders: order_id, customer_id, order_date, выручка, status (completed / cancelled)dim_customer: customer_id, first_order_date, registration_date, acquisition_channeldim_date: календарь, помечен как Date Tabledim_rfm_segment: вспомогательная таблица с определениями сегментов (см. ниже)
Для скоринга нужен фиксированный «конец окна». Обычно это TODAY(), но при анализе исторических периодов можно параметризовать. В примерах используем TODAY().
Базовые R, F, M меры на уровне клиента
Отдельные меры для R, F и M — считаются для каждого customer_id в контексте визуала.
// R: дней с последней покупки
Recency Days =
VAR _last_order =
CALCULATE(
MAX(fact_orders[order_date]),
fact_orders[status] = "completed"
)
RETURN
DATEDIFF(_last_order, TODAY(), DAY)
// F: количество покупок за последние 12 месяцев
Frequency =
CALCULATE(
DISTINCTCOUNT(fact_orders[order_id]),
fact_orders[status] = "completed",
DATESINPERIOD(
dim_date[date],
TODAY(),
-12,
MONTH
)
)
// M: выручка за последние 12 месяцев
Monetary =
CALCULATE(
SUM(fact_orders[revenue]),
fact_orders[status] = "completed",
DATESINPERIOD(
dim_date[date],
TODAY(),
-12,
MONTH
)
)
Эти меры показывают корректные значения, когда в визуале в строках — customer_id. На уровне «всех клиентов» (total) Recency Days даст DATEDIFF от глобального максимума — это не то, что нужно, но для сегментации неважно.
Скоринг: разбиваем на квинтили
Дальше интереснее. Для каждого клиента нужно определить, к какому квинтилю (1-5) он относится по каждому измерению. Классическая формула NTILE в DAX не сработает напрямую, но можно построить эквивалент через RANKX + деление на количество клиентов.
// R-скор: 5 = самый свежий, 1 = давно не покупал
R Score =
VAR _total_customers =
CALCULATE(
DISTINCTCOUNT(dim_customer[customer_id]),
ALL(dim_customer)
)
VAR _rank =
RANKX(
ALL(dim_customer),
[Recency Days],
,
ASC, // чем меньше дней, тем лучше (свежее)
DENSE
)
RETURN
SWITCH(
TRUE(),
_rank <= _total_customers * 0.2, 5,
_rank <= _total_customers * 0.4, 4,
_rank <= _total_customers * 0.6, 3,
_rank <= _total_customers * 0.8, 2,
1
)
// F-скор: 5 = много покупок
F Score =
VAR _total = CALCULATE(DISTINCTCOUNT(dim_customer[customer_id]), ALL(dim_customer))
VAR _rank = RANKX(ALL(dim_customer), [Frequency], , DESC, DENSE)
RETURN
SWITCH(TRUE(),
_rank <= _total * 0.2, 5,
_rank <= _total * 0.4, 4,
_rank <= _total * 0.6, 3,
_rank <= _total * 0.8, 2,
1)
// M-скор: аналогично
M Score =
VAR _total = CALCULATE(DISTINCTCOUNT(dim_customer[customer_id]), ALL(dim_customer))
VAR _rank = RANKX(ALL(dim_customer), [Monetary], , DESC, DENSE)
RETURN
SWITCH(TRUE(),
_rank <= _total * 0.2, 5,
_rank <= _total * 0.4, 4,
_rank <= _total * 0.6, 3,
_rank <= _total * 0.8, 2,
1)
Важно: RANKX по всем клиентам — тяжёлая операция. На базе в 10-20 тыс. клиентов работает приемлемо, на 100 тыс.+ начинает тормозить. Для больших моделей лучше материализовать скоры в ETL (пересчёт раз в сутки).
Определение сегментов
Скоры склеиваются в код «R-F-M» и мапятся на именованные сегменты. Самая распространённая схема — 10 сегментов:
DAX-мапинг через SWITCH + TRUE():
RFM Segment =
VAR _r = [R Score]
VAR _f = [F Score]
VAR _m = [M Score]
RETURN
SWITCH(
TRUE(),
_r >= 4 && _f >= 4 && _m >= 4, "Champions",
_r >= 3 && _f >= 3 && _m >= 3, "Loyal",
_r >= 4 && _f <= 3 && _m <= 3, "Potential Loyalists",
_r >= 4 && _f = 1, "New Customers",
_r >= 3 && _f = 1 && _m = 1, "Promising",
_r >= 2 && _f >= 2 && _m >= 2 && _r <= 3, "Need Attention",
_r = 1 && _f >= 4 && _m >= 4, "Can't Lose",
_r <= 2 && _f >= 2 && _m >= 2, "At Risk",
_r <= 2 && _f <= 2 && _m <= 2, "Hibernating",
_r = 1 && _f = 1 && _m = 1, "Lost",
"About To Sleep"
)
Порядок условий важен: более специфичные сегменты идут раньше общих. SWITCH-TRUE возвращает первое совпадение, так что «Can't Lose» (R=1 + высокие F/M) должен быть раньше общего «At Risk».
Визуализация: три ключевых визуала
1. Распределение сегментов (treemap или bar)
Смотрим, как распределена клиентская база. Treemap показывает долю каждого сегмента по количеству клиентов. Дополнительно в tooltip — выручка этого сегмента. Часто шокирует: Champions — 8% клиентов, но 50-60% выручки.
Customer Count by Segment =
CALCULATE(
DISTINCTCOUNT(dim_customer[customer_id])
)
Revenue by Segment =
CALCULATE(
[Monetary]
)
// Визуал: Treemap, Category = RFM Segment,
// Values = Customer Count, Tooltip = Revenue
2. Scatter plot: Recency × Frequency × Monetary (bubble)
Один точка — один клиент. X = Recency Days (инвертирован — чем ближе к 0, тем правее), Y = Frequency, размер пузыря = Monetary, цвет = RFM Segment. Визуально видно кластеры, outliers, «дыры» в базе.
3. Heatmap: R × F (в разрезе M или количества клиентов)
Матрица 5 × 5 (R-скор по строкам, F-скор по колонкам), цвет ячейки — количество клиентов или средний Monetary. Быстро видно, где концентрация базы и куда двигаться.
Бизнес-действия по сегментам
Главный вопрос: что делать с каждым сегментом. Стандартные рекомендации:
| Сегмент | Действие |
|---|---|
| Champions | удержание: лояльная программа, early access, VIP-поддержка. Нельзя терять. |
| Loyal | Upsell: предложить премиум-продукты, увеличить средний чек. |
| Potential Loyalists | Nurturing: cross-sell, рассылки по интересам, программы лояльности. |
| New Customers | Onboarding: welcome-серия, первый offer со скидкой, помощь с первым заказом. |
| Promising | Engagement: активные offers, ремаркетинг, рекомендации товаров. |
| Need Attention | Reactivation: скидки, бонусы, личный контакт (email/звонок). |
| About To Sleep | Agressive offers: sharp-discount (-20%, -30%), «мы скучаем». |
| At Risk | Win-back: большая скидка, бесплатная доставка, персональное предложение. Высокая ценность — нельзя терять тихо. |
| Can't Lose | Ручной контакт: менеджер звонит, разбирается в причине молчания. |
| Hibernating | Reactivation-кампания или suppression (не тратить на них маркетинг). |
| Lost | Низкоприоритетная реактивация или исключение из рассылок. |
Гибкая RFM: параметризация периода
Бизнес-вопрос может быть: «А какая у нас была сегментация год назад?» или «Покажи RFM за последние 6 месяцев, а не 12». Решается параметрами:
- Создаём What-If параметр в модели: «RFM Window» = 3, 6, 12, 24 (месяца)
- В мерах Frequency и Monetary заменяем константу
-12на-[RFM Window Value] - Пользователь двигает срез — RFM пересчитывается
Frequency (Dynamic Window) =
VAR _window = SELECTEDVALUE('RFM Window'[Months], 12)
RETURN
CALCULATE(
DISTINCTCOUNT(fact_orders[order_id]),
fact_orders[status] = "completed",
DATESINPERIOD(dim_date[date], TODAY(), -_window, MONTH)
)
Для анализа «как было раньше» добавляется второй параметр «As Of Date» — и TODAY() заменяется на значение из срез. Получается машина времени: двигаем ползунок даты и смотрим историческую сегментацию.
Типичные ошибки при реализации RFM
- Считать М по всей истории клиента, а не за период. Клиент, который купил на 1 млн ₽ в 2019, но с тех пор ничего — получит топ-M-скор и попадёт в Champions/Loyal. Нужен fixed window (12 месяцев).
- Включать отменённые заказы в F и M. Фильтр по status = completed обязателен.
- Использовать количество позиций, а не заказов в F. Клиент с 1 заказом из 50 позиций и клиент с 50 заказами по 1 позиции — разные сегменты. DISTINCTCOUNT по order_id.
- Равные квинтили на малой базе. При 100 клиентах каждый квинтиль — 20 человек, скоры выдают мало смысла. Минимальная база для RFM — 500-1000 активных клиентов.
- B2B vs B2C. RFM хорошо работает в B2C (много транзакций, низкий средний чек). В B2B (редкие крупные сделки) часто нужен другой подход — например, tenure-based сегментация или account health scoring.
- Игнорировать outliers в M. Один клиент, купивший на 100 млн ₽ (оптовая сделка), искажает распределение. Либо уберите outliers, либо логарифмируйте M перед скорингом.
- Сегменты через прямое сравнение строк вместо скоров. SWITCH(RFM_Code, "555", "Champions", "554", "Loyal", ...) работает для 125 комбинаций, но нечитаем. Всегда через логические условия на R/F/M отдельно.
Продвинутые расширения
RFM + CLV
Добавляем к стандартным R/F/M четвёртую метрику — ожидаемую пожизненную ценность (клиент Lifetime Value). Считается из истории покупок, удержание-кривой и average order value. Позволяет приоритизировать: два клиента в Champions могут иметь CLV 500k и 5k — работать с ними нужно по-разному.
RFMT: добавляем Tenure
T = дней с первой покупки. Помогает различать «новых Champions» (купили много за короткое время — возможно, это всплеск, не устойчивый паттерн) и «устоявшихся Champions» (давно с компанией, высокая лояльность).
Категорийный RFM
Отдельный скоринг по каждой категории товаров. Клиент может быть Champion в «Электронике» и Lost в «Одежде». Полезно для cross-sell: продвигать Champions одной категории в смежные.
Шаги внедрения
- Согласуйте окно с бизнесом: 6 / 12 / 24 месяца. Зависит от длительности покупательского цикла.
- Определите «покупку»: completed orders, с исключением отменённых, возвратов, служебных транзакций.
- Посчитайте R/F/M для всей базы — сначала в SQL, убедитесь что результаты осмысленны (средний чек похож на реальность, распределение не имеет странных пиков).
- Постройте квинтили и мапинг сегментов. Проверьте доли: если один сегмент <1% или >50% — правило написано плохо.
- Визуализируйте: treemap по количеству и выручке, scatter R × F, heatmap R × F с цветом M.
- Прикрутите срез по сегменту к другим отчётам: «покажи выручку Champions по категориям», «удержание At Risk по каналам».
- Интегрируйте с CRM: экспорт списка customer_id каждого сегмента в систему email/push-маркетинга для автоматических кампаний.
Что делать дальше
- Нужна полная RFM-сегментация под ключ — 30-минутный звонок. Типовой проект — 2-3 недели от согласования до production.
- Уже есть RFM, но хочется гибче или быстрее — BI-аудит поможет оптимизировать текущую реализацию.
- Связанный контент — P17 про когортный удержание, DAX-справочник.