§ P18 — DAX-кейс · Сегментация

RFM-сегментация клиентов на DAX

ETL · Скоринг · 10 сегментов · Визуализация

Классический инструмент управления клиентской базой — разделить клиентов на «Чемпионов», «Новичков», «Почти ушедших» и ещё семь групп по трём параметрам: Recency, Frequency, Monetary. Разбираем production-реализацию на DAX с правильной ETL-подготовкой и визуализацией в Power BI.

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 можно реализовать двумя путями:

  1. Полностью в ETL. В SQL считаем R/F/M, квинтили через NTILE(5), складываем в dim_клиент как материализованные колонки. В Power BI просто отображаем. Быстро, стабильно, но сегмент не пересчитывается под фильтры в отчёте.
  2. В 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_channel
  • dim_date: календарь, помечен как Date Table
  • dim_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 сегментов:

Champions
R 4-5 · F 4-5 · M 4-5
~8%
Loyal
R 3-5 · F 3-5 · M 3-5
~14%
Potential Loyalists
R 4-5 · F 1-3 · M 1-3
~12%
New Customers
R 4-5 · F 1 · M 1-2
~6%
Promising
R 3-4 · F 1 · M 1
~5%
Need Attention
R 2-3 · F 2-3 · M 2-3
~9%
About To Sleep
R 2-3 · F 1-2 · M 1-2
~7%
At Risk
R 1-2 · F 2-5 · M 2-5
~13%
Can't Lose
R 1 · F 4-5 · M 4-5
~4%
Hibernating
R 1-2 · F 1-2 · M 1-2
~15%
Lost
R 1 · F 1 · M 1
~7%

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-поддержка. Нельзя терять.
LoyalUpsell: предложить премиум-продукты, увеличить средний чек.
Potential LoyalistsNurturing: cross-sell, рассылки по интересам, программы лояльности.
New CustomersOnboarding: welcome-серия, первый offer со скидкой, помощь с первым заказом.
PromisingEngagement: активные offers, ремаркетинг, рекомендации товаров.
Need AttentionReactivation: скидки, бонусы, личный контакт (email/звонок).
About To SleepAgressive offers: sharp-discount (-20%, -30%), «мы скучаем».
At RiskWin-back: большая скидка, бесплатная доставка, персональное предложение. Высокая ценность — нельзя терять тихо.
Can't LoseРучной контакт: менеджер звонит, разбирается в причине молчания.
HibernatingReactivation-кампания или suppression (не тратить на них маркетинг).
LostНизкоприоритетная реактивация или исключение из рассылок.
Где экономия. Исключение сегмента «Lost» из платных маркетинговых кампаний (push, CPA-реклама) экономит 10-25% маркетингового бюджета без потери выручки. На базе в 100 тыс. клиентов, где «Lost» — 7%, это 7 тыс. человек, на которых не тратятся деньги ни на что, кроме органических касаний.

Гибкая RFM: параметризация периода

Бизнес-вопрос может быть: «А какая у нас была сегментация год назад?» или «Покажи RFM за последние 6 месяцев, а не 12». Решается параметрами:

  1. Создаём What-If параметр в модели: «RFM Window» = 3, 6, 12, 24 (месяца)
  2. В мерах Frequency и Monetary заменяем константу -12 на -[RFM Window Value]
  3. Пользователь двигает срез — 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. Считать М по всей истории клиента, а не за период. Клиент, который купил на 1 млн ₽ в 2019, но с тех пор ничего — получит топ-M-скор и попадёт в Champions/Loyal. Нужен fixed window (12 месяцев).
  2. Включать отменённые заказы в F и M. Фильтр по status = completed обязателен.
  3. Использовать количество позиций, а не заказов в F. Клиент с 1 заказом из 50 позиций и клиент с 50 заказами по 1 позиции — разные сегменты. DISTINCTCOUNT по order_id.
  4. Равные квинтили на малой базе. При 100 клиентах каждый квинтиль — 20 человек, скоры выдают мало смысла. Минимальная база для RFM — 500-1000 активных клиентов.
  5. B2B vs B2C. RFM хорошо работает в B2C (много транзакций, низкий средний чек). В B2B (редкие крупные сделки) часто нужен другой подход — например, tenure-based сегментация или account health scoring.
  6. Игнорировать outliers в M. Один клиент, купивший на 100 млн ₽ (оптовая сделка), искажает распределение. Либо уберите outliers, либо логарифмируйте M перед скорингом.
  7. Сегменты через прямое сравнение строк вместо скоров. 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 одной категории в смежные.

Шаги внедрения

  1. Согласуйте окно с бизнесом: 6 / 12 / 24 месяца. Зависит от длительности покупательского цикла.
  2. Определите «покупку»: completed orders, с исключением отменённых, возвратов, служебных транзакций.
  3. Посчитайте R/F/M для всей базы — сначала в SQL, убедитесь что результаты осмысленны (средний чек похож на реальность, распределение не имеет странных пиков).
  4. Постройте квинтили и мапинг сегментов. Проверьте доли: если один сегмент <1% или >50% — правило написано плохо.
  5. Визуализируйте: treemap по количеству и выручке, scatter R × F, heatmap R × F с цветом M.
  6. Прикрутите срез по сегменту к другим отчётам: «покажи выручку Champions по категориям», «удержание At Risk по каналам».
  7. Интегрируйте с CRM: экспорт списка customer_id каждого сегмента в систему email/push-маркетинга для автоматических кампаний.

Что делать дальше

§ Проект · 2-3 недели

Спроектируем
под вашу базу

RFM для ритейла, e-com, HoReCa, SaaS. Настроим скоринг под вашу специфику, интегрируем с CRM для автоматических сегментных кампаний.

Телефон+7 918 042 34 43