Привет, Хабр! Меня зовут Кореньков Александр, и я работаю в команде «Выгода и вовлечение» в направлении продуктовой аналитики онлайн-доставки в компании X5 DigiПривет, Хабр! Меня зовут Кореньков Александр, и я работаю в команде «Выгода и вовлечение» в направлении продуктовой аналитики онлайн-доставки в компании X5 Digi

Как мы научились честно считать эффект промокодов: Causal Inference в онлайн-доставке X5 Digital

2026/03/05 13:54
11м. чтение
Для обратной связи или замечаний по поводу данного контента, свяжитесь с нами по адресу [email protected]

Привет, Хабр! Меня зовут Кореньков Александр, и я работаю в команде «Выгода и вовлечение» в направлении продуктовой аналитики онлайн-доставки в компании X5 Digital. Занимаюсь машинным обучением на стыке с продуктовой аналитикой: оцениваю эффективность маркетинговых механик и рекламных каналов, а после стараюсь помочь бизнесу принимать верные решения, основанные не на ощущениях, а на данных и результатах их анализа.

Сегодня расскажу о модели, которую мы построили для оценки реального эффекта промокодов. Главные вопросы: кому, какой, и зачем мы выдаем промокод. Спойлер: ответ нас удивил. И именно этот ответ стал главной причиной, по которой эту модель вообще стоило строить.

Почему стандартный подход не работает

Представьте стандартный отчёт по промокампании: «Пользователи, применившие промокод, потратили на 800 рублей больше среднего». Бизнес доволен, маркетинг рапортует об успехе. Но подождите, а сколько из них потратили бы эти деньги и без промокода?

Это не риторический вопрос. Это принципиальная проблема, которая называется selection bias — систематическая ошибка отбора.

Промокоды в нашем случае получают все пользователи, попавшие в промоволну, но активируют их, как правило, наиболее вовлечённые покупатели, те, кто и так регулярно делает заказы, тратит больше среднего, имеет платные подписки и карты лояльности. Когда мы сравниваем тех, кто применил промокод, с теми, кто не применил, мы сравниваем принципиально разные группы людей. Это примерно как заметить, что в дождливую погоду на улицах больше людей с зонтами, и сделать вывод, что именно зонты вызывают дождь.

Добавьте к этому сезонность: запустили промо в декабре — продажи выросли на 30%. «Промо дало +30%!» Но продажи выросли бы и без промо — это просто праздники. Истинный вклад промо мог составить лишь малую часть от наблюдаемого роста.

Получается, что наивное сравнение «до и после» или «с промо и без промо» неизбежно искажает картину. Нам нужен был принципиально другой подход.

Почему нельзя просто провести A/B-тест

Первая мысль, которая приходит в голову: давайте разделим пользователей на две группы — одна получает промокод, другая нет — и сравним результаты. Классический рандомизированный эксперимент, золотой стандарт причинно-следственного анализа и всё такое.

5324e6b756df69ad395fd929e5448c5c.png

На практике это не работает по нескольким причинам (Рисунок 1). Во-первых, некоторые промокоды выпускаются сразу на всех пользователей волны, такова природа маркетинговых кампаний. Сознательно лишать часть аудитории выгодного предложения ради чистоты эксперимента, не правильно этически и противоречит самой логике промо. Во-вторых, нам часто нужно оценить эффект уже запущенных кампаний по историческим данным, а не ждать месяцами результатов нового теста.

Именно здесь на сцену выходит Causal Inference — комплекс методов, которые позволяют оценить причинно-следственный эффект без рандомизированного эксперимента, используя ретроспективные наблюдательные данные.

Causal Inference: считаем причины, а не корреляции

Ключевая идея каузального анализа — это вопрос «что было бы, если бы...». Для каждого пользователя, применившего промокод, мы хотим знать: сколько бы он потратил, если бы промокода не было? Это называется контрфактуалом — ненаблюдаемым альтернативным исходом.

Проблема в том, что мы никогда не можем наблюдать одного и того же пользователя одновременно в двух состояниях: с промо и без. Поэтому нам нужно найти для каждого «treated» пользователя (того, кто применил промо) его «двойника» — максимально похожего пользователя, который промокод не применил. Именно этот двойник и будет нашим контрфактуалом.

Для решения этой задачи мы выбрали комбинацию двух классических методов: PSM (Propensity Score Matching) и DiD (Difference-in-Differences). Каждый метод по отдельности имеет ограничения, но вместе они закрывают слабые места друг друга.

Шаг первый: PSM — ищем близнецов

Propensity Score Matching решает задачу подбора похожих пар. Идея простая: вместо того чтобы искать пользователей, совпадающих по десяткам характеристик одновременно (что практически невозможно), мы сжимаем все признаки в одно число — propensity score. Это вероятность того, что пользователь применит промокод, вычисленная на основе его характеристик до начала промоволны. Мы обучаем логистическую регрессию, которая по ключевым ковариатам с недельной грануляцией: суммарному RTO (товарооборот с учетом НДС) до промо, количеству заказов, количеству активных дней, среднему чеку и глубине истории предсказывает вероятность применения промо.

Зачем именно эти признаки и что они нам дают? Они описывают поведение пользователя до воздействия и не содержат информации о том, что произошло после (иначе бы мы допустили утечку данных из будущего). После обучения для каждого пользователя, применившего промо (treated), мы ищем ближайшего по propensity score пользователя из тех, кто промо не применил (control). Этот поиск происходит с ограничением на максимально допустимое расстояние между парами — caliper. Представьте облако точек, в котором наиболее близкие друг к другу будут считаться парой.

Качество подбора пар мы оцениваем через метрику SMD (Standardized Mean Difference) — стандартизованную разницу средних точек (Рисунок 2). Если SMD по всем ковариатам меньше 0.25, группы считаются достаточно сбалансированными. Если меньше 0.10 — баланс отличный. Если PSM не справляется с первого раза (нашли слишком мало пар), модель автоматически смягчает параметры и пробует снова. Это адаптивный retry-механизм.

e930a40073be20fa7174c422ba6972e5.pngРисунок 2 – качество балансировки PSM и эффективность мэтчинга
Рисунок 2 – качество балансировки PSM и эффективность мэтчинга

Важно понимать ограничение: в среднем для каждой промоволны пары находятся лишь для относительно небольшой доли treated-пользователей. Это не баг, а особенность метода — мы жертвуем охватом ради качества сравнения. Лучше честно оценить эффект на сбалансированной выборке, чем получить смещённую оценку на всех данных.

Шаг второй: DiD — считаем разность разностей

После того как PSM создал сбалансированные пары, в игру вступает Difference-in-Differences. Этот метод отвечает на вопрос: насколько изменилось поведение treated-группы относительно control-группы после промо?

Логика DiD предельно наглядна. Допустим:

  • Treated-группа: RTO до промо — 500 руб./неделю, после — 570 руб./неделю. Изменение: +70 руб.

  • Control-группа: RTO до промо — 495 руб./неделю, после — 515 руб./неделю. Изменение: +20 руб.

Разность разностей: 70 − 20 = +50 руб. Это и есть причинный эффект промокода — то, что не объясняется общим трендом, сезонностью или другими внешними факторами, которые одинаково влияют на обе группы.

Ключевое условие для корректности DiD называется parallel trends (Рисунок 3). До промо обе группы должны были развиваться параллельно. Если treated-пользователи росли быстрее контрольных ещё до начала промо, значит, различие в трендах существовало изначально, и DiD даст смещённую оценку.

Мы проверяем это условие автоматически: строим регрессию на данных за четыре недели до старта промо и тестируем, есть ли статистически значимое взаимодействие между группой и временем. Если тест показывает нарушение параллельности — волна помечается как ненадёжная, и её результаты интерпретируются с осторожностью.

Итоговая метрика — ATT (Average Treatment Effect on the Treated) — показывает средний инкрементальный RTO на пользователя в неделю благодаря промокоду (Рисунок 4). Вместе с ним модель выдаёт p-value, 95% доверительный интервал и флаг валидности parallel trends.

Рисунок 4 – оценка инкрементального эффекта
Рисунок 4 – оценка инкрементального эффекта

Как это устроено на уровне пайплайна

Вся логика PSM+DiD упакована в 13 ячеек Jupyter-ноутбука, организованных в единый воспроизводимый пайплайн.

Конфигурация и загрузка данных. Все параметры модели (caliper, пороги SMD, целевая переменная) собраны в одном месте. Это позволяет легко воспроизводить эксперименты с разными настройками. Данные загружаются из четырёх таблиц и кэшируются в parquet, что ускоряет процесс подготовки данных для повторных запусков с нескольких часов до нескольких секунд.

Валидация и предобработка. Перед анализом мы проверяем, что ковариаты действительно измерены до начала промо (иначе — data leakage), что в каждой волне достаточно treated и control-пользователей и что один пользователь не попал в обе группы одновременно. Выбросы обрабатываются через winsorization. Вместо удаления экстремальных значений мы заменяем их на граничные перцентили, сохраняя размер выборки (иначе бы у нас были пользователи, сделавшие заказ на миллион рублей за раз).

PSM и DiD классы. Два основных класса реализуют всю методологическую логику, описанную выше, плюс адаптивный retry для волн, которые не проходят PSM с первой попытки.

Параллельная обработка всех волн. Каждая промо-волна обрабатывается независимо, а весь процесс распараллелен через ThreadPoolExecutor. На выходе единая таблица с результатами PSM+DiD для каждой волны.

Агрегация и визуализация. Последняя ячейка собирает статистику по всем волнам: сколько успешно обработано, сколько провалили PSM, каково распределение uplift, сколько волн статистически значимы.

Шаг третий: CatBoost — от оценки к пониманию

PSM+DiD даёт нам ответ «что произошло» для каждой конкретной промо-волны. Но для бизнеса будет полезно понять, почему одни механики работают лучше других и предсказать эффективность новой кампании ещё до её запуска.

Для этого мы построили вторую часть модели — CatBoost-регрессор, который обучается на результатах PSM+DiD и учится предсказывать uplift для новых промо-волн. Пока что к результатам стоит относиться с осторожностью, потому что обучающая выборка не так велика и точность прогноза оставляет желать лучшего. Но это вовсе не окончательный результат, потому что промо как выходили, так и будут выходить, а значит объёмы данных для обучения будут увеличиваться.

Входные признаки — это описание промо-механики (размер скидки, тип, минимальный заказ), характеристики аудитории (базовый RTO, количество заказов, активность до промо), сезонность и качество оценки самого PSM+DiD (SMD, match rate, валидность parallel trends). Последний блок признаков особенно важен: если PSM сработал плохо, оценка uplift ненадёжна, и мы снижаем вес этой волны при обучении через sample weighting.

Почему CatBoost? Он хорошо работает с категориальными признаками без дополнительного кодирования, устойчив к выбросам и имеет встроенную регуляризацию. Всё это важно при работе с относительно небольшой выборкой (количество промо-волн конечное).

Процесс обучения включает кросс-валидацию на пяти фолдах, ранняя остановка по валидационной метрике и финальный refit на всех данных. Качество модели оценивается через MAE (средняя абсолютная ошибка в рублях) и R² (доля объяснённой вариации uplift между волнами). Типичный R² в диапазоне 0.35–0.60 для задач causal ML — это хороший результат. Промо-эффекты сильно варьируются, и значительная часть вариации объясняется случайностью или факторами, которые сложно измерить заранее.

Feature Importance раскрывает ключевые драйверы эффективности. По нашим данным, наибольший вклад вносят размер скидки, характеристики аудитории (особенно базовый RTO до промо) и минимальная сумма заказа для применения. Интересно, что один и тот же промокод даёт принципиально разный эффект для разных когорт: для пользователей с низким базовым чеком относительный прирост от промо оказывается существенно выше, чем покупателей с большой суммой заказа. Это ключевой инсайт для будущей персонализации.

Результаты CatBoost также показывают, что не все механики одинаково эффективны в разные сезоны. Например, механика с фиксированной скидкой и высоким минимальным заказом может давать сильный положительный эффект весной и отрицательный осенью. Это значит, что оценивать промо «в целом» без учёта сезонного контекста — ещё одна ловушка, в которую легко попасть.

Неудобная правда

Когда мы получили первые результаты по историческим данным, стало понятно, что картина далека от оптимистичной и не такая уж однозначная. Часть промо отработали в минус, какие-то вышли в ноль, и мы просто нагрузили систему рассылкой промо-предложения. А некоторые промо дали огромный положительный эффект. Конечно, таких хотелось бы побольше, но как этого добиться? А главное, на данный момент, как это интерпретировать?

Есть несколько механизмов.

Каннибализация. Пользователь купил бы товар и без промокода, но теперь мы ещё и дали ему скидку. Инкремент RTO = 0, а маржа снизилась на размер скидки.

Перенос покупки. Промо ускорило покупку, которая всё равно произошла бы чуть позже. Краткосрочный эффект есть, долгосрочный — нулевой или отрицательный.

Охотники за скидками. Часть аудитории покупает только со скидкой и не формирует лояльность. LTV таких пользователей низкий.

Важно понимать: это не означает, что промокоды бесполезны как инструмент. Это означает, что нужно задавать правильные вопросы. Не спрашивать «работает ли промо?», а спрашивать «для кого оно работает?». Средний эффект по всей аудитории может быть отрицательным, при этом для отдельных сегментов, например, для пользователей с определённым уровнем активности и историей покупок — эффект будет положительным.

Именно это открытие задало направление следующего шага.

Что дальше: от оценки к таргетированию

Модель PSM+DiD позволяет нам оценить, для каких пользователей промокод реально сработал, и выделить «золотой сегмент» с максимальным инкрементом. Следующая задача — построить look-alike модель, которая по характеристикам пользователя будет предсказывать его принадлежность к этому сегменту (Таблица 1).

Таблица 1. Четыре квадранта пользователей
Таблица 1. Четыре квадранта пользователей

Логика такая: описываем профиль «золотого сегмента» через RFM-метрики, канал привлечения, платформу, историю и другие признаки. Строим классификатор, который умеет находить похожих пользователей во всей базе. В результате промокоды будут направляться не всей аудитории подряд, а тем, для кого они реально создают дополнительную ценность.

Это смещает парадигму с «промо всем» на «промо тем, кому они помогают». По аналогии с мировыми кейсами — Booking.com, Wayfair, Uber — такой подход позволяет существенно улучшить ROI промо-активности при сокращении бюджета на нецелевой аудитории.

Вместо заключения

Работа над этой моделью научила нас нескольким важным вещам.

Первое: измерять нужно правильно. Конверсия промокода и инкремент RTO — это принципиально разные метрики, и первая ничего не говорит о реальной эффективности.

Второе: среднее скрывает гетерогенность. За нулевым или отрицательным ATT может скрываться сильный положительный эффект для конкретного сегмента.

Третье: каузальный анализ — это не академическое упражнение, а инструмент для принятия бизнес-решений. Но только при условии, что его результаты реально влияют на дизайн кампаний.

Модель уже даёт команде ответы на вопросы, которые раньше решались интуицией, долгим и неоднозначным ручным расчётом или наивными метриками. А впереди — персонализация, которая превратит инсайты в конкретные действия.

Если интересны детали методологии — готов рассказать подробнее про конкретные аспекты PSM, DiD или CatBoost в комментариях. Ну и в целом буду рад обратной связи от тех, кто работает с похожими задачами в e-com или ритейле.

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу [email protected] для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.