В мире измерений после cookies атрибуция теряет сигналы с каждым днём. Даже с SKAdNetwork в iOS 17.4 компании с трудом видят настоящий ROAS, а руководители маркетинга всё чаще обращаются к эконометрическим моделям для измерения реального вклада каналов. Marketing Mix Modeling (MMM) — статистический метод, разработанный в 1960-х для анализа телевизионной рекламы — в 2026 году вновь центре внимания, наряду с server-side измерениями и озёрами first-party данных. Robyn, выпущенный Meta в 2021 году как open-source, добавил к этой регрессионной методологии современное машинное обучение и байесовскую оптимизацию гиперпараметров, ускорив внедрение.
Почему MMM критичен именно сейчас
Модель last-click attribution рушится вместе с потерей cookies, а multi-touch attribution (MTA) из-за необходимости event-level данных становится невозможна в условиях GDPR и ATT. Google Analytics 4 использует data-driven attribution на основе машинного обучения, но работает только внутри экосистемы Google. Однако 60% маркетингового бюджета остаётся вне Google: Meta, TikTok, программатический дисплей, офлайн-ТВ, спонсорство.
MMM опирается не на отслеживание на уровне пользователей, а на агрегированные еженедельные или ежедневные данные. Регрессионная модель извлекает взаимосвязь между расходами каждого канала и продажами (или конверсиями). Модель построена на двух ключевых предположениях: насыщение (растущие расходы приносят убывающий предельный доход) и адсток (реклама сегодня влияет на будущие недели). Эти предположения статистичны, но отражают деловую реальность. Robyn нацелена на автоматическое нахождение этих двух параметров посредством байесовской оптимизации гиперпараметров. В версиях после 2024 года (v3.11+) добавлены ridge regression и prophet time-series decomposition, повышающие точность модели при работе с сезонностью.
Ещё одна критическая особенность Robyn — holdout validation: модель обучается на прошлых 12 неделях данных и прогнозирует следующие 4 недели, измеряя ошибку на невидимых данных. Это предотвращает переобучение и подтверждает, что модель действительно научилась распознавать каналы. Google's Meridian и старые MMM-решения Facebook используют похожие подходы, но это closed-source и дорого. Robyn даёт бесплатный доступ к той же методологии.
Структура данных и подготовка
Для запуска Robyn требуется вот такой формат данных: каждая строка — один временной период (день или неделя), каждый столбец — расходы канала или метрика конверсии. Рекомендуется минимум 104 недели (2 года), так как статистическая значимость коэффициентов регрессии зависит от размера выборки. С меньше чем 52 неделями модель часто не сходится.
# Пример структуры данных — недельная агрегация из BigQuery
df <- data.frame(
DATE = seq.Date(from = as.Date("2024-01-01"), by = "week", length.out = 104),
revenue = runif(104, 80000, 150000),
google_search_spend = runif(104, 5000, 15000),
meta_spend = runif(104, 8000, 20000),
tiktok_spend = runif(104, 2000, 8000),
tv_grp = runif(104, 50, 200),
organic_sessions = runif(104, 10000, 30000),
competitor_index = runif(104, 0.8, 1.2)
)
Важные детали:
- Столбец
DATEдолжен быть класса Date, не string - Revenue или конверсия — зависимая переменная модели (dependent variable)
- Каналы (google_search_spend, meta_spend) — столбцы paid медиа, к ним применяются адсток и насыщение
- Переменные вроде
organic_sessionsиcompetitor_index— это органические / контрольные переменные, к ним не применяется насыщение, они используются для вычленения baseline - Если есть данные офлайн-канала вроде ТВ, нормализуй их как GRP, reach или минуты просмотра
Robyn не работает с автоматическими метками вроде facebook_spend; названия столбцов задаёшь сам, но в функции InputCollect() явно указываешь, какие столбцы paid, а какие organic.
Архитектура first-party данных — это сложная задача, если её ещё не построили. Server-side GTM, raw-экспорт GA4, API Meta / Google Ads, данные о продажах из CRM — всё это нужно объединить в BigQuery и сделать недельный rollup. Когда мы строим этот ETL-pipeline с dbt, на выходе получаем готовую таблицу fact_marketing_weekly для MMM.
Конфигурация насыщения и адстока
Сила Robyn в том, что она может оптимизировать кривую насыщения и параметры адстока отдельно для каждого канала. Насыщение моделируется функцией Hill:
effect = spend^alpha / (spend^alpha + half_saturation^alpha)
Параметр alpha определяет вогнутость кривой, half_saturation — уровень расходов, при котором эффект достигает половины максимума. Intent-based каналы типа Google Search насыщаются рано (низкий alpha, низкий half_saturation). Каналы awareness типа ТВ или YouTube насыщаются поздно.
Адсток моделирует влияние прошлых расходов на сегодня. Геометрический адсток — самый распространённый:
adstocked_spend[t] = spend[t] + theta * adstocked_spend[t-1]
theta (между 0 и 1) — скорость decay. Для ТВ theta высокий (0.7-0.9 — эффект длится неделями), для Search низкий (0.1-0.3 — эффект быстро исчезает). Robyn находит эти параметры оптимизацией Nevergrad, но ты должен задать диапазон prior:
hyperparameters <- list(
google_search_spend_alphas = c(0.5, 1.5),
google_search_spend_gammas = c(0.1, 0.4), # адсток decay
google_search_spend_thetas = c(0, 0.3), # адсток theta
meta_spend_alphas = c(0.5, 2.0),
meta_spend_gammas = c(0.3, 0.8),
meta_spend_thetas = c(0.2, 0.6),
tv_grp_alphas = c(1.0, 3.0),
tv_grp_gammas = c(0.5, 0.9),
tv_grp_thetas = c(0.6, 0.9)
)
Эти range'ы нужно устанавливать на основе domain knowledge. Если выбрать полностью случайные значения, модель расходится или находит абсурдные коэффициенты (например, отрицательное влияние ТВ). Документация Robyn предлагает default range'ы, но протестируй их на своих данных перед использованием.
Обучение модели и holdout-валидация
Для запуска Robyn используешь функцию robyn_run(). Внутри неё работает Nevergrad — библиотека для байесовской оптимизации, которая ищет лучшую комбинацию гиперпараметров. Типичный run — это 2000 итераций × 10 trials = 20,000 обучений модели. На MacBook M1 с 8 ядрами это занимает примерно 15 минут.
library(Robyn)
InputCollect <- robyn_inputs(
dt_input = df,
date_var = "DATE",
dep_var = "revenue",
dep_var_type = "revenue",
paid_media_vars = c("google_search_spend", "meta_spend", "tiktok_spend"),
paid_media_spends = c("google_search_spend", "meta_spend", "tiktok_spend"),
organic_vars = c("organic_sessions"),
prophet_vars = c("trend", "season", "holiday"),
window_start = "2024-01-01",
window_end = "2025-12-31",
adstock = "geometric",
hyperparameters = hyperparameters
)
OutputModels <- robyn_run(
InputCollect = InputCollect,
iterations = 2000,
trials = 10,
outputs = FALSE
)
После обучения модель выводит Pareto-оптимальные решения. Robyn оптимизирует два метрики: NRMSE (нормализованная среднеквадратичная ошибка) и decomposition RSSD (остаточная сумма квадратов разложения). На Pareto frontier каждая модель — это компромисс: одна хороша по fit'у, но плоха по decomposition, другая — наоборот. Ты вручную выбираешь наиболее разумную модель.
Для holdout-валидации откладываешь последние 4-8 недель. Robyn делает это автоматически:
robyn_refresh(
robyn_object = OutputModels,
dt_input = df_new, # Refresh с новыми данными
refresh_steps = 4,
refresh_mode = "manual"
)
Если holdout MAPE (mean absolute percentage error) ниже 10%, модель считается надёжной. Выше 20% — опасный сигнал переобучения или пропущенной переменной.
Интерпретация результатов и оптимизация бюджета
Наиболее критичный результат Robyn — таблица channel contribution. Она показывает вклад каждого канала в выручку в процентах и ROAS (return on ad spend). Но внимание: это исторический ROAS, а не marginal ROAS. Marginal ROAS показывает, сколько дополнительной выручки принесёт следующая 1000 рублей, и вычисляется как производная кривой насыщения.
Функция budget_allocator() в Robyn перераспределяет существующий бюджет согласно кривым насыщения. Если Google Search насыщен, лишний бюджет переходит на Meta или TikTok. Эта оптимизация находит точку на response curve, где маржинальные доходы уравнены (микроэкономика 101: MR₁ = MR₂).
AllocatorCollect <- robyn_allocator(
robyn_object = OutputModels,
select_model = "1_100_2", # ID модели, выбранной из Pareto
scenario = "max_response_expected_spend",
channel_constr_low = c(0.7, 0.7, 0.5), # Минимум 70% Google, 70% Meta, 50% TikTok
channel_constr_up = c(1.5, 2.0, 3.0), # Максимальный лимит увеличения
expected_spend = 100000
)
Результат показывает, как распределить бюджет 100,000 рублей для максимизации выручки. Но это статичная рекомендация — в реальности меняется creative, активность конкурентов, сезонность. Поэтому MMM нужно освежать ежемесячно.
Компромиссы и ограничения
MMM, в отличие от attribution, работает на агрегированном уровне. Это значит, её нельзя использовать для персонализации. MMM не покажет, какой keyword лучше работает в Google Search — только общий вклад Search в выручку. Кроме того, модель подвержена проблеме correlation ≠ causation: если продажи растут летом и ты также увеличиваешь ТВ-расходы летом, модель может переоценить вклад ТВ.
Для решения этой проблемы нужно валидировать MMM с помощью incrementality test. Geo-lift или holdout-тест измеряют реальный каузальный эффект, который потом сравниваются с результатами MMM. Robyn может включить результаты incrementality как параметр calibration — он работает байесовским prior'ом и приближает модель к реальности.
Ещё одна сложность — добавление новых каналов в модель. Если открыл новый канал (например, Snapchat) и есть только 8 недель данных, Robyn не сможет научиться кривой насыщения этого канала. В этом случае нужно либо задать manual prior, либо исключить канал из модели на первые 12 недель.
Наконец, MMM самая мощная, когда объединяет офлайн и онлайн. Если не включиш