Модель атрибуции — это правило, по которому вы распределяете «заслугу» за конверсию между каналами и касаниями, через которые прошёл пользователь по пути к покупке. Если нужно решение прямо сейчас: для операционных отчётов начните с last touch, добавьте position-based, когда поймёте, что верх воронки недооценён, и переходите к data-driven только когда накопились тысячи конверсий и есть кому поддерживать модель. Ниже я разберу каждую модель на пальцах, покажу, как посчитать их в SQL, и расскажу, где обычно спотыкаются кандидаты на собеседовании.
Что такое атрибуция простыми словами?
Представьте, что человек купил у вас подписку. За две недели до этого он пришёл из контекстной рекламы, потом увидел ваш пост в соцсети, кликнул на письмо из рассылки и наконец зашёл напрямую и оплатил. Кому засчитать эту продажу? Рекламе, которая привела впервые? Рассылке, которая напомнила? Или прямому заходу, на котором произошла оплата?
Атрибуция как раз отвечает на этот вопрос. Она не создаёт новых данных — она перераспределяет уже случившуюся конверсию (или её деньги) между касаниями. От выбранной модели напрямую зависит, какой канал получит бюджет в следующем квартале, а какой урежут. Поэтому спор про атрибуцию — это почти всегда спор про деньги, а не про математику.
Ключевых понятий три. Касание (touchpoint) — это любое взаимодействие пользователя с каналом: клик по рекламе, переход из письма, визит из органики. Путь (journey) — упорядоченная по времени цепочка касаний до конверсии. Окно атрибуции (lookback window) — период, в течение которого касание вообще имеет право претендовать на заслугу: обычно 7, 14 или 30 дней. Всё, что было раньше окна, мы просто игнорируем.
Чем last touch отличается от first touch?
Это две простейшие однокасательные модели, и разница между ними принципиальная. Last touch отдаёт всю заслугу последнему касанию перед конверсией. First touch — первому касанию в цепочке. Всё, что между ними, обнуляется.
Last touch хвалит каналы «дна воронки»: прямые заходы, брендовый поиск, ретаргетинг. Именно они обычно оказываются последними перед оплатой, поэтому в отчёте last touch они выглядят героями. Проблема в том, что прямой заход не «привёл» клиента — клиент уже был прогрет, просто пришёл дозреть. Модель систематически недооценивает всё, что работает на узнаваемость.
First touch — зеркальная история. Она хвалит верх воронки: медийку, охватные кампании, первый контакт из органики. И так же систематически недооценивает то, что реально закрывает сделку. Обе модели удобны тем, что их легко посчитать и легко объяснить менеджеру. Обе врут, но врут предсказуемо и в разные стороны, поэтому их полезно смотреть в паре: если канал силён в first touch, но слаб в last touch, он про привлечение; если наоборот — про закрытие.
На практике 80% дашбордов в компаниях крутятся именно на last touch просто потому, что рекламные кабинеты отдают её по умолчанию. Знать её реализацию наизусть — обязательный минимум, и её любят спрашивать на собеседованиях.
Как работают линейная, time decay и position-based модели?
Когда однокасательных моделей становится мало, в ход идут многокасательные (multi-touch). Они делят заслугу между всеми касаниями пути, но по разным правилам.
Линейная (linear) — самая честная и самая наивная одновременно. Каждому из N касаний достаётся ровно 1/N конверсии. Если путь состоял из четырёх касаний, каждое получит по 0.25. Плюс — никто не забыт. Минус — она считает, что случайный показ баннера и финальный клик по промокоду одинаково важны, а это редко правда.
Time decay (затухание по времени) исходит из идеи, что чем ближе касание к конверсии, тем оно ценнее. Вес считается по формуле экспоненциального затухания: weight = 2^(-t / H), где t — сколько дней прошло от касания до конверсии, а H — период полураспада (half-life), часто 7 дней. Касание за день до покупки весит почти единицу, касание двухнедельной давности — копейки. Модель хороша для длинных циклов сделки, где недавние контакты действительно важнее.
Position-based (позиционная, она же U-образная) — компромисс, который любят продуктовые команды. Классическая раскладка: 40% заслуги первому касанию, 40% последнему, оставшиеся 20% поровну делятся между всеми средними. Логика простая: важнее всего то, что нас познакомило, и то, что закрыло сделку, а середина — поддержка. Это чаще всего разумный дефолт, если вы уже переросли last touch, но ещё не дозрели до data-driven.
Что такое data-driven атрибуция и стоит ли её внедрять?
Data-driven атрибуция (DDA) отличается тем, что веса не заданы вами вручную, а выводятся из самих данных. Модель смотрит на реальные пути и оценивает, насколько присутствие канала в цепочке повышает вероятность конверсии по сравнению с путями без него. Классический подход — значения Шепли (Shapley values) из теории игр: канал получает столько заслуги, сколько он в среднем добавляет к результату во всех возможных комбинациях каналов. Другой распространённый вариант — марковские цепи, где считают, насколько упадёт конверсия, если из графа переходов «удалить» канал (removal effect).
Звучит красиво, но у DDA высокая цена входа. Ей нужны объёмы: на редких конверсиях статистика шумит и веса скачут от месяца к месяцу. Ей нужна чистая склейка касаний в единый путь — а это самая больная часть из-за разных устройств и потери идентификаторов. И её тяжело объяснить бизнесу: «модель так решила» — плохой аргумент в разговоре про урезание бюджета.
Мой практический критерий: внедряйте DDA, когда у вас стабильно тысячи конверсий в месяц, налажена склейка пользователей и есть человек, который будет модель поддерживать и защищать её выводы. Если хоть один пункт не выполнен — position-based даст 80% пользы за 10% усилий. Разбор Shapley и марковских цепей удобно тренировать на Python, где есть готовые структуры для перебора комбинаций.
Как посчитать last-touch атрибуцию в SQL?
Перейдём к коду. Договоримся о двух таблицах. touchpoints(user_id, channel, touched_at) — по строке на каждое касание. conversions(user_id, converted_at, revenue) — по строке на каждую конверсию. Задача — приклеить к каждой конверсии её касания внутри окна атрибуции и распределить заслугу.
Last touch делается через оконную функцию: нумеруем касания в обратном порядке по времени и берём первое.
WITH journey AS (
SELECT
c.user_id,
c.converted_at,
c.revenue,
t.channel,
ROW_NUMBER() OVER (
PARTITION BY c.user_id, c.converted_at
ORDER BY t.touched_at DESC
) AS rn_last
FROM conversions c
JOIN touchpoints t
ON t.user_id = c.user_id
AND t.touched_at <= c.converted_at
AND t.touched_at >= c.converted_at - INTERVAL '30 days' -- окно атрибуции
)
SELECT
channel,
COUNT(*) AS conversions,
SUM(revenue) AS revenue
FROM journey
WHERE rn_last = 1
GROUP BY channel
ORDER BY revenue DESC;
First touch — тот же запрос, только ORDER BY t.touched_at ASC в оконной функции. Обратите внимание на два условия в JOIN: touched_at <= converted_at отсекает касания, случившиеся уже после покупки (иначе вы засчитаете постпродажные письма как «приведшие» к сделке), а нижняя граница задаёт то самое окно атрибуции. Синтаксис интервала я пишу в стиле PostgreSQL; в других движках это будет dateadd или вычитание дней. Разобраться с оконными функциями и вычитанием дат можно в SQL-справочнике.
Как реализовать linear и position-based в SQL?
Линейная модель проще, чем кажется: считаем число касаний в пути оконным COUNT, а дальше каждому касанию отдаём долю 1/N.
WITH journey AS (
SELECT
c.user_id, c.converted_at, c.revenue, t.channel,
COUNT(*) OVER (PARTITION BY c.user_id, c.converted_at) AS touches
FROM conversions c
JOIN touchpoints t
ON t.user_id = c.user_id
AND t.touched_at <= c.converted_at
AND t.touched_at >= c.converted_at - INTERVAL '30 days'
)
SELECT
channel,
ROUND(SUM(1.0 / touches), 2) AS attributed_conversions,
ROUND(SUM(revenue / touches), 2) AS attributed_revenue
FROM journey
GROUP BY channel
ORDER BY attributed_revenue DESC;
Position-based требует знать и позицию касания, и общее их число, чтобы применить правило 40/20/40. Нумеруем касания в прямом порядке, отдельно считаем длину пути и раскидываем веса через CASE.
WITH ranked AS (
SELECT
c.user_id, c.converted_at, c.revenue, t.channel,
ROW_NUMBER() OVER (PARTITION BY c.user_id, c.converted_at
ORDER BY t.touched_at) AS pos,
COUNT(*) OVER (PARTITION BY c.user_id, c.converted_at) AS n
FROM conversions c
JOIN touchpoints t
ON t.user_id = c.user_id
AND t.touched_at <= c.converted_at
AND t.touched_at >= c.converted_at - INTERVAL '30 days'
),
weighted AS (
SELECT channel, revenue,
CASE
WHEN n = 1 THEN 1.0 -- одно касание получает всё
WHEN n = 2 THEN 0.5 -- два делят пополам
WHEN pos = 1 OR pos = n THEN 0.4 -- первое и последнее
ELSE 0.2 / (n - 2) -- середина делит 20%
END AS weight
FROM ranked
)
SELECT
channel,
ROUND(SUM(weight), 2) AS attributed_conversions,
ROUND(SUM(weight * revenue), 2) AS attributed_revenue
FROM weighted
GROUP BY channel
ORDER BY attributed_revenue DESC;
Проверка на вменяемость: сумма attributed_conversions по всем каналам должна совпасть с общим числом конверсий во всех моделях. Если не сходится — где-то потеряли касания или в пути затесались строки после конверсии. Эти запросы удобно прогнать вживую в SQL-тренажёре на своих данных, а похожие задачи с окнами и агрегацией собраны в разделе тестовых заданий.
Какую модель атрибуции выбрать?
Универсального ответа нет, и это правильный ответ на собеседовании. Выбор зависит от длины цикла сделки, объёма конверсий и того, какое решение вы собираетесь принять по итогам отчёта.
Мой рабочий алгоритм такой. Если продукт с коротким циклом и импульсной покупкой — last touch адекватен, тут почти нет пути, чтобы его делить. Если цикл длинный и вы хотите справедливее оценить верх воронки — берите position-based как разумный дефолт. Если недавние касания объективно важнее (дорогая покупка, много касаний перед сделкой) — time decay. И только на больших стабильных объёмах с чистой склейкой — data-driven.
Важнее самой модели — не менять её молча. Как только вы переключаете атрибуцию, отчёты всех каналов меняются, и любой канал можно «выключить» или «включить» просто сменив правило. Поэтому фиксируйте модель, документируйте окно атрибуции и всегда показывайте пару моделей рядом, чтобы было видно, где именно результат чувствителен к выбору. Отдельно держите в голове, что атрибуция отвечает на вопрос «кому засчитать», а не «что было бы, если убрать канал» — на последний честно отвечает только эксперимент.
Какие ошибки в атрибуции встречаются чаще всего?
Первая и самая частая — путать атрибуцию с причинностью. Модель показывает корреляцию касаний с конверсиями, а не причинный вклад канала. Если хотите узнать реальный эффект — нужен инкрементальный тест (geo-lift, holdout-группа), а не перекладывание весов. На собеседовании фраза «атрибуция не равна инкременту» сразу выделяет кандидата.
Вторая — забыть про окно атрибуции и границу touched_at <= converted_at. Без верхней границы вы засчитаете постпродажные касания, без нижней — притянете касания годичной давности. И то и другое искажает картину.
Третья — двойной счёт при нескольких конверсиях одного пользователя. Если человек купил дважды, партиционировать надо по паре (user_id, converted_at), а не только по user_id, иначе касания приклеятся ко всем его покупкам разом. Четвёртая — игнорировать пользователей без касаний: прямые заходы и органика часто вообще не пишут touchpoint, и такие конверсии молча выпадают из знаменателя, завышая долю платных каналов. Пятая — сравнивать выручку между отчётами на разных моделях, будто это одна метрика; это разные разрезы одного и того же пирога. Полезно держать рядом стабильные продуктовые метрики вроде DAU и retention, чтобы видеть, реальный ли это рост или просто перекладка заслуги внутри той же аудитории.
Атрибуция — тема, которую любят на интервью в продуктовых командах: она проверяет и SQL с оконными функциями, и понимание бизнес-логики, и умение отличить корреляцию от эффекта. Если хотите отработать её вместе с остальным арсеналом аналитика — SQL, Python, метрики, A/B — на тренажёре есть задачи с реальных собеседований, а по Python пригодится справочник для расчёта Shapley и марковских весов. Полный доступ к банку задач и AI-разбору собеседований открывает Pro — рекомендую попробовать бесплатную часть и посмотреть, зайдёт ли формат.