Почему метрика обманывает: состав, календарь, база и декомпозиция
Как честно посчитанная метрика приводит к неверному выводу: парадокс Симпсона, регрессия к среднему, сезонность, аномалии и декомпозиция роста по вкладам сегментов.
Содержание статьи
В недельном отчёте конверсия выросла с 13% до 32%. Запрос написан правильно, знаменатель тот же, дубли проверены, данные свежие. А внутри обоих каналов, из которых собран этот показатель, конверсия упала: 10% → 9% и 40% → 35%. Обе цифры верны одновременно, и решение «увеличить бюджет, раз работает» закрепит ухудшение. Это не курьёз и не ошибка в SQL: агрегат смешивает размер сегмента с его качеством, и вес пересилил ставку. Дальше — пять механизмов, из-за которых корректно посчитанное число ведёт к неверному выводу, и порядок проверок, который ловит их до того, как изменение объяснят поведением пользователей.
Коротко
Почти любое изменение метрики допускает четыре объяснения. Три из них не имеют отношения к продукту, и проверяются они быстрее, чем четвёртое.
- Состав: изменились веса сегментов, а не поведение внутри них — парадокс Симпсона.
- База: предыдущая точка была экстремальной, и возврат к норме произошёл бы сам — регрессия к среднему.
- Календарь: сравниваются разные дни недели, месяцы разной длины или несезонные периоды.
- Данные: алерт сработал на опоздавшей загрузке, а не на продукте.
- И только после этих четырёх — гипотеза о том, что пользователи стали вести себя иначе.
- Декомпозиция на множители превращает «метрика выросла» в набор проверяемых вкладов и закрывает все предыдущие ловушки сразу.
У одного роста есть четыре объяснения, и продукт — последнее в очереди
Агрегированное число — это результат арифметики над множеством строк. Оно меняется, когда меняется хотя бы одно из трёх: какие строки попали в расчёт, с чем именно ты сравниваешь текущую точку, и что происходило внутри строк. Первые два пункта не про продукт вообще, но выглядят они точно так же, как третий: цифра в отчёте просто становится другой.
Порядок проверок стоит выбирать по их стоимости. Пересчёт на фиксированных весах — это один запрос. Сравнение с тем же днём недели — фильтр в дашборде. Проверка свежести источника — минута. А доказательство того, что изменилось поведение, требует контрольной группы и недель ожидания. Если начать с самого дорогого объяснения, дешёвые остаются непроверенными, и в отчёт уезжает кейс успеха, который держится на изменении микса трафика.
Неприятная деталь: эти механизмы складываются. Маркетинг увеличил долю сильного канала (состав), это совпало с началом учебного сезона (календарь), а на прошлой неделе был провальный день после инцидента (база). Каждый из трёх эффектов даёт по несколько процентных пунктов, и вместе они рисуют убедительный рост, в котором от релиза нет ничего.
| Что видно в отчёте | Первая проверка | Механизм |
|---|---|---|
| общий рост, сегменты падают | пересчёт на фиксированных весах | парадокс Симпсона |
| улучшение после провала | holdout с тем же правилом отбора | регрессия к среднему |
| рост к прошлой неделе | сравнение с тем же днём недели | сезонность |
| резкий скачок вниз | freshness и полнота текущего дня | проблема данных |
| рост без объяснения | разложение на множители | нужна декомпозиция |
Конверсия выросла с 13% до 32%, хотя в каждом канале она упала
Разберём тот случай из вступления по строкам. В июне канал A привёл 900 пользователей с конверсией 10%, канал B — 100 пользователей с конверсией 40%. Всего 90 + 40 = 130 конверсий на 1 000 пользователей, то есть 13%. В июле закупка перевернулась: A дал 100 пользователей, B — 900. Конверсия внутри каждого канала просела: A до 9%, B до 35%. Считаем: 9 + 315 = 324 конверсии на 1 000, то есть 32,4%.
Общая конверсия выросла в два с половиной раза, пока оба её источника ухудшились. Причина в том, что отношение сумм не равно среднему отношений: вес канала задаётся его знаменателем. Доля дорогого сильного канала B выросла с 10% до 90% трафика, и этот сдвиг веса пересилил падение ставок внутри обоих каналов.
Тот же механизм переворачивает вывод в A/B-тесте, и там он опаснее, потому что эксперимент принято считать защитой от подобных искажений. Вариант выигрывает и на mobile, и на desktop: 2% → 6% и 12% → 18%. Но в контроль попало 3 000 мобильных и 7 000 десктопных пользователей, а в тест — 9 000 мобильных и 1 000 десктопных. Конверсия контроля: (60 + 840) / 10 000 = 9,0%. Конверсия теста: (540 + 180) / 10 000 = 7,2%. Итоговый uplift минус 1,8 п.п. при плюс 4 и плюс 6 п.п. в сегментах.
В эксперименте такой перекос — это не парадокс, а сломанная рандомизация: сильный и слабый сегменты распределены по вариантам неравномерно. Поэтому SRM-проверку делают не только на общем размере групп, но и в разрезе ключевых сегментов. Совпадение суммарных 10 000 против 10 000 ничего не гарантирует.
rate_total = Σ conversions / Σ users ≠ average(rate_segment)Вес сегмента в общей метрике равен его знаменателю. Среднее по сегментам даёт другое число и отвечает на другой вопрос.
| Группа | Июнь | Июль | Что изменилось |
|---|---|---|---|
| Канал A | 90 / 900 = 10% | 9 / 100 = 9% | ставка вниз, вес вниз |
| Канал B | 40 / 100 = 40% | 315 / 900 = 35% | ставка вниз, вес вверх |
| Всего | 130 / 1000 = 13% | 324 / 1000 = 32,4% | микс маскирует падение |
| На весах июня | 13% | 11,6% | сопоставимое сравнение |
Факт: 13% → 32,4%. На весах июня (90% A, 10% B) тот же июль даёт 0,09 × 0,9 + 0,35 × 0,1 = 11,6%. Сегментные ставки упали обе, поэтому сопоставимое сравнение показывает падение, а не рост.
Положи рядом две колонки: метрику по сегментам и размер сегмента. Если знак изменения метрики и знак изменения доли расходятся хотя бы у одного крупного сегмента, общий агрегат уже нельзя читать как характеристику качества.
Фиксированный микс: один запрос, который отделяет состав от поведения
Пересчёт на фиксированных весах работает так: ставки берём из текущего периода, а доли сегментов — из опорного. Получается ответ на вопрос «какой была бы метрика, если бы состав трафика не менялся». Разница между фактом и этим числом и есть вклад микса.
Опорный период приходится выбирать, и выбор меняет число. На весах июня июль даёт 11,6% против июньских 13%. На весах июля июнь дал бы 0,10 × 0,1 + 0,40 × 0,9 = 37% против фактических 32,4% в июле. Оба сценария показывают падение, и это как раз тот случай, когда вывод устойчив: знак не зависит от выбора весов. Если бы знаки разошлись, в отчёт пошли бы оба числа с пояснением, какой вопрос закрывает каждое.
Поэтому перед расчётом полезно назвать вслух, про какую популяцию спрашиваешь. «Что мы получаем сейчас» — это факт с текущим миксом, им считают выручку и планируют мощности. «Стали ли каналы хуже» — это фиксированный микс. «Что даст изменение продукта» — это уже эффект внутри сегмента, и на него агрегат не отвечает вовсе.
with seg as (
select
period,
channel,
count(*) as users,
count(*) filter (where converted) as conversions
from visits
where period in ('2026-06', '2026-07')
group by period, channel
),
base_weights as (
-- веса опорного периода: фиксируем состав трафика
select channel, users::numeric / sum(users) over () as weight
from seg
where period = '2026-06'
)
select
s.period,
sum(s.conversions)::numeric / sum(s.users) as rate_fact,
sum(w.weight * s.conversions::numeric / s.users) as rate_fixed_mix
from seg s
join base_weights w using (channel)
group by s.period
order by s.period;- Ключевые разрезы выбирай до того, как увидел расхождение: канал, платформа, страна, тариф, новизна пользователя.
- Сегменты с маленьким знаменателем объединяй в «прочее», иначе шум в них будет выглядеть как эффект.
- В эксперименте проверяй баланс сегментов, а не только суммарный размер групп.
- Если в отчёте остаётся одно число, рядом должна стоять строка «вклад микса» с её знаком.
Плохой день сменяется хорошим сам, и баннер тут не при чём
Второй механизм срабатывает, когда решение принимают после экстремального значения. Во вторник конверсия упала с обычных 7,8% до 4,1%. В среду включили новый баннер, и дальше пошло 6,0%, 7,1%, 7,6%. Кажется, баннер вернул метрику. На самом деле вернулась сама метрика: экстремальная точка содержала много случайного шума, а следующее измерение того же процесса естественно оказывается ближе к его среднему.
Регрессия к среднему — это не сила, которая тянет показатель к норме. Это следствие двух решений: наблюдение выбрано по экстремальному значению, а оценивают его на новом измерении. Чем больше доля шума относительно устойчивого сигнала, тем сильнее иллюзия. На метрике с размахом ±0,3 п.п. эффект почти не заметен, на конверсии маленького сегмента он даёт те самые три с половиной пункта «роста».
Тот же сценарий встречается вне дашбордов. Поддержка обзвонила клиентов с самым долгим временем ответа — через неделю время сократилось. Ретаргетинг догнал пользователей, которые только что положили товар в корзину, — конверсия оказалась высокой. Магазины с упавшими продажами получили программу поддержки — продажи подросли. В каждом случае часть улучшения случилась бы без вмешательства, потому что отбор шёл по значению, которое само по себе было необычным.
Обе группы отобраны по одному правилу — провал во вторник. Вмешательство получила только первая. К четвёртому дню разрыв между ними 0,1 п.п., а «рост» внутри каждой группы — около 3,5 п.п.
Правило отбора и окно оценки должны быть разными вещами
Защита здесь не статистическая, а процедурная: отбор и оценка разводятся во времени и в данных. Зафиксируй baseline до отбора — каким был показатель у этих объектов раньше, а не в тот день, когда они попали в список. Выбери окно оценки, которое не пересекается с окном отбора. И оставь holdout: часть объектов, прошедших тот же фильтр, но не получивших вмешательства.
Holdout решает задачу, с которой не справляется сравнение «до и после»: он движется к среднему точно так же, как обработанная группа. Поэтому ответом становится не изменение внутри группы, а разница между группами. В примере выше это 0,1 п.п. вместо 3,5 п.п. — и дальше вопрос уже не «помог ли баннер», а «хватает ли нам мощности, чтобы отличить 0,1 п.п. от нуля».
Отдельно: реагировать на инцидент нужно. Никто не просит ждать контрольную группу, когда сломан checkout. Разделяются не действия, а выводы: срочное исправление остаётся срочным исправлением, а доказательством влияния оно становится только при наличии baseline, контроля и независимого окна. Если команда регулярно вмешивается после плохих дней, стоит один раз посчитать, как ведут себя такие дни сами по себе — и дальше сравнивать с этим числом, а не с нулём.
| Вопрос | Что смотреть | Сигнал, что это регрессия |
|---|---|---|
| Как выбрали объекты | правило отбора | отобраны по самому outcome |
| Что в holdout | параллельная динамика | там такое же улучшение |
| Сколько держится эффект | несколько окон подряд | возврат к baseline за день-два |
| Что изменилось в продукте | релизы и логи | объяснение только ручное |
38 против 27: половина «падения» — это просто воскресенье
Третий механизм — календарь. Типичная недельная форма DAU в B2C-продукте: 42, 39, 38, 40, 41 тысячи в рабочие дни и 30, 27 в выходные. Сравнение среды с воскресеньем даёт минус 29%, сравнение воскресенья с понедельником — плюс 56%. Оба числа посчитаны верно и не значат ничего: они описывают день недели, а не продукт.
Тренд, сезонность и шум — три разные части ряда. Тренд — устойчивое движение уровня. Сезонность — повторяемый паттерн с известным периодом: день недели, месяц, квартал, учебный год. Шум — остаток, который выбранная модель не объясняет. Разовое событие вроде релиза или аварии сезонностью не является, даже если совпало дважды: для сезонности нужен механизм, который объясняет, почему паттерн повторяется.
Календарь ломает и годовые сравнения. Недельная активность выросла на 18% после email-кампании, но кампания попала на начало учебного сезона, когда аудитория возвращается сама. Более аккуратный разбор выглядел так: плюс 9% к той же неделе прошлого года, из которых 6 п.п. объясняются возвратом учебной аудитории, а устойчивый тренд — около 3%. Локальный рост на iOS после релиза остался гипотезой до следующей когорты.
Месяцы разной длины — отдельная ловушка, потому что её не видно. В феврале 28 дней, в марте 31: это плюс 10,7% объёма при полностью неизменном поведении. Любое месячное сравнение абсолютных величин требует нормализации на число дней или на число рабочих дней, если метрика про B2B.
Среднее за семь дней — 36,7 тыс. Сглаживание показывает уровень, но стирает форму: по линии rolling-7 не видно, что воскресенье ниже среды на 29%.
Четыре сравнения одного ряда отвечают на четыре разных вопроса
Вместо спора о «правильном» способе сравнения проще держать рядом все четыре и помнить, что каждый ловит. День к предыдущему дню реагирует мгновенно и путается в дне недели. Неделя к неделе снимает дневную сезонность, но ломается на празднике внутри окна. Скользящее среднее за семь дней показывает уровень и с задержкой примерно в три дня прячет инцидент. Год к году ловит сезонно похожий период, но за год меняется состав базы — и тогда к календарной ловушке добавляется ловушка состава из третьего раздела.
Сглаживание полезно и опасно одновременно. Оно убирает дневную пилу, но размазывает провал: падение на 40% в один день на rolling-7 выглядит как аккуратная ступенька минус 6%. Поэтому в дашборде держат обе линии — raw и сглаженную, — а аннотации релизов и акций рисуют прямо на графике. Зритель должен видеть, откуда взялась точка и с каким периодом её сравнивают.
Последний день почти всегда неполный. Если его не отрезать или не подписать, каждое утро метрика будет «падать» — и команда постепенно научится не верить графику. Для рядов с задержкой загрузки работает простое правило: показывать только зрелые окна, а незрелый хвост рисовать другим цветом.
| Сравнение | Отвечает на вопрос | Где ошибается |
|---|---|---|
| День к дню | что случилось вчера | день недели и неполный день |
| WoW по тому же дню | меняется ли недельный уровень | праздник внутри окна |
| Rolling 7 | где находится уровень | прячет и запаздывает |
| YoY (сдвиг 364 дня) | сезонно похожий период | за год сменился состав базы |
daily = events.set_index('date').resample('D')['user_id'].nunique().to_frame('dau')
daily['dod'] = daily['dau'].pct_change() # день к предыдущему
daily['wow'] = daily['dau'] / daily['dau'].shift(7) - 1 # тот же день недели
daily['rolling_7'] = daily['dau'].rolling(7, min_periods=7).mean()
daily['yoy'] = daily['dau'] / daily['dau'].shift(364) - 1 # 364, чтобы совпал день недели
# незрелый хвост не сравниваем
daily = daily.loc[: daily.index.max() - pd.Timedelta(days=1)]Падение на 28% оказалось опозданием загрузки на два часа
Четвёртый механизм живёт на стыке аналитики и данных. В 10:00 дашборд показал минус 28% к обычному уровню DAU. Проверка заняла пять минут: ingestion отставал на два часа, сырые события продолжали поступать, продукт работал. Продуктового инцидента не было, но тревожный красный прямоугольник в отчёте был.
Аномалия — это наблюдение, заметно отличающееся от ожидаемого по выбранной модели baseline. Значит, первое решение — не порог, а baseline. Скользящая медиана устойчивее среднего к выбросам. Медиана по тем же дням недели сразу снимает сезонность из предыдущего раздела. Прогноз с явной сезонной составляющей точнее, но требует ухода. z-score разумен на более-менее симметричном распределении, IQR спокойнее переносит тяжёлые хвосты; на счётчиках с правой асимметрией оба работают лучше после логарифмирования.
Второе решение — минимальный объём. Без него правило будет срабатывать на сегментах из сорока пользователей, где разница в шесть конверсий даёт минус 30% и ничего не означает. Порог по объёму обрезает эти сегменты до того, как они попадут в алерт, и заодно снимает добрую половину ложных срабатываний.
Третье — признание того, что после сильного релиза уровень может честно измениться. z-score, считающий baseline по последним 28 дням, будет неделю объявлять аномалией новую норму. Либо окно пересчитывают после подтверждённого сдвига уровня, либо правило знает про даты релизов.
Обычный разброс ±4%. Точка минус 24% выходит за него, но это ещё не вывод: она одинаково совместима с инцидентом продукта, опоздавшей загрузкой и праздником.
s = orders.set_index('date')['orders'].asfreq('D').to_frame('value')
s['dow'] = s.index.dayofweek
# медиана четырёх предыдущих таких же дней недели
s['baseline'] = (
s.groupby('dow')['value']
.transform(lambda x: x.shift(1).rolling(4, min_periods=3).median())
)
s['delta_pct'] = s['value'] / s['baseline'] - 1
# порог по объёму отсекает шум маленьких дней
alerts = s[(s['delta_pct'].abs() > 0.15) & (s['baseline'] >= 200)]Алерт без диагностики превращается в шум через две недели
Детектирование и диагностика — разные задачи, и смешивать их в одном сигнале дорого. Детектирование отмечает отклонение. Диагностика собирает контекст: свежесть источника, полнота текущего дня, доля NULL в ключевых полях, дубли, деплои за сутки, поведение соседних метрик и разбивка по платформам и версиям. Результатом проверки должен быть статус, а не обсуждение: подтверждённый инцидент, проблема данных, ожидаемая сезонность или «пока непонятно» с назначенным владельцем и сроком.
Мониторинг полезно разделить на слои, потому что у них разные владельцы и разная скорость реакции. Freshness отвечает на вопрос «данные пришли вовремя». Completeness — «события и поля на месте». Бизнес-слой — «метрика отклонилась от ожидаемой». Когда эти сигналы слиты в один, продуктовая команда получает алерт о конверсии, причина которого лежит в пайплайне, и тратит час на поиск того, чего не случилось.
Чувствительность настраивается не на глаз, а бэктестом. Прогони правило по истории за полгода, посчитай число срабатываний и долю тех, которые оказались ничем. Двадцать алертов в неделю означают, что их перестанут читать к концу месяца. Отдельно проверь, ловит ли мониторинг искусственную задержку данных: подвинь загрузку на три часа и посмотри, сработает ли freshness-сигнал раньше бизнес-метрики.
| Слой | Сигнал | Владелец |
|---|---|---|
| Freshness | данные пришли вовремя | data platform |
| Completeness | полнота событий и полей | analytics engineering |
| Business | метрика вне ожидаемого диапазона | product / operations |
| Diagnosis | локализация и статус | дежурная команда |
Ссылка на дашборд, владелец реакции, источник данных, baseline и порог, минимальный объём, список диагностических разрезов и критерий закрытия. Без критерия закрытия алерт живёт в чате неделями и тихо обесценивает все остальные.
Разложи метрику на множители, и спорить станет не о чем
Три предыдущие ловушки объединяет одно: итоговое число не показывает, из чего оно собрано. Декомпозиция возвращает эту информацию. Многие продуктовые метрики — произведение или отношение компонентов: revenue = payers × orders_per_payer × AOV, GMV = orders × average order value, DAU = новые + вернувшиеся + удержанные. Разложение не доказывает причину, зато показывает арифметический путь от прошлой точки к текущей.
Выручка выросла на 9,0%. Смотрим множители: плательщиков стало на 7% больше, заказов на плательщика — на 5% больше, средний чек упал на 3%. Проверяем: 1,07 × 1,05 × 0,97 = 1,0898. Сходится. Теперь у разговора есть структура: рост дали база плательщиков и частота покупок, а чек работает против. Предложение «поднять цены» перестаёт быть первой гипотезой, потому что видно, что чек и так уже снизился — вероятно, из-за скидочного микса или сдвига в сторону дешёвых категорий.
Сверка суммы компонентов с исходной метрикой — не формальность, а основной контроль. Если произведение множителей не даёт фактическое изменение, значит компоненты посчитаны на разных grain, в разных окнах или с разными фильтрами. Ошибка в зерне расчёта важнее любого красивого графика: waterfall, который не сходится, объясняет не метрику, а собственную сборку.
Порядок множителей влияет на величину вкладов, и это нормальное свойство метода, а не баг. Перестановка шагов перераспределяет взаимодействие между компонентами. Поэтому порядок фиксируют один раз и пишут рядом с отчётом; для аккуратного разделения вкладов в долгоживущих отчётах используют логарифмические веса, где сумма вкладов не зависит от порядка.
revenue = payers × orders_per_payer × AOVПроизведение множителей обязано равняться фактическому изменению выручки. Не равняется — проблема в grain или в окне, а не в интерпретации.
Индекс: 100 → 107 (плательщики +7%) → 112,35 (частота +5%) → 108,98 (чек −3%). Последний столбец — итог, а не отдельный шаг.
with m as (
select
date_trunc('month', paid_at) as month,
count(distinct user_id) as payers,
count(*)::numeric / count(distinct user_id) as orders_per_payer,
sum(amount)::numeric / count(*) as aov,
sum(amount) as revenue
from orders
where paid_at >= date '2026-05-01' and paid_at < date '2026-07-01'
group by 1
)
select
month,
revenue / lag(revenue) over (order by month) as k_revenue,
payers::numeric / lag(payers) over (order by month) as k_payers,
orders_per_payer / lag(orders_per_payer) over (order by month) as k_freq,
aov / lag(aov) over (order by month) as k_aov,
-- обязано быть равно k_revenue
payers::numeric / lag(payers) over (order by month)
* orders_per_payer / lag(orders_per_payer) over (order by month)
* aov / lag(aov) over (order by month) as k_check
from m
order by month;ARPU вырос на 7,8%, но дорогих покупателей не стало больше
Для отношений разложение выглядит иначе, и тут легче всего ошибиться. ARPU — это произведение доли плательщиков и средней выручки на плательщика. Было 10% × 5 000 ₽ = 500 ₽. Стало 11% × 4 900 ₽ = 539 ₽, то есть плюс 7,8%. Проверка: 1,10 × 0,98 = 1,078.
Вывод из этих двух множителей противоположен тому, который обычно делают из роста ARPU. Платить начали чаще — доля плательщиков выросла на целый процентный пункт, это десятая часть от исходного уровня. А средний плательщик стал платить меньше. Значит, в платящую базу пришли новые люди с меньшими чеками, и следующие вопросы не про цену, а про удержание этих плательщиков и про маржу в их заказах. Если они не вернутся во второй месяц, рост ARPU окажется разовым.
Работать декомпозиция должна сверху вниз и по одной ветке за раз: выбрал компонент с наибольшим вкладом — разрезай именно его. Разрезать всё сразу по десяти измерениям значит почти гарантированно найти красивую случайную историю в маленьком сегменте. И каждую ветку нужно помечать зрелостью: когорта последнего месяца ещё не дожила до второй покупки, поэтому её частота заведомо ниже, причём по арифметике, а не по поведению.
| Уровень | Компонент | Следующий вопрос |
|---|---|---|
| 1 | активные пользователи | изменился трафик или его микс? |
| 2 | доля плательщиков | кто именно начал платить? |
| 3 | частота покупок | вернулись старые плательщики или пришли новые? |
| 4 | средний чек и маржа | что со скидками и составом корзины? |
- Один grain и одно окно для всех компонентов — иначе сверка не сойдётся.
- Арифметический вклад не равен причинному эффекту: waterfall сужает список гипотез, а проверяет их эксперимент.
- Средние по сегментам нельзя складывать без весов — это возвращает нас к первому разделу.
- Зрелость когорты подписывается рядом с каждой веткой.
Порядок проверок: состав, календарь, база, и только потом продукт
Собирая всё вместе, получаем маршрут из пяти шагов, который занимает меньше часа и закрывает большинство ложных выводов. Сначала свежесть и полнота данных: неполный день и опоздавшая загрузка объясняют самые драматичные скачки. Потом календарь: сравни с тем же днём недели и с сезонно похожим периодом. Потом состав: пересчитай на фиксированных весах ключевых сегментов. Потом база: посмотри, не была ли предыдущая точка экстремальной и не отбирали ли объекты по самому показателю. И только после этого раскладывай изменение на множители и формулируй гипотезу о поведении.
Порядок не случаен: каждый следующий шаг дороже предыдущего, а первые четыре умеют полностью снимать вопрос. Если после пересчёта на фиксированных весах рост исчез, дальше идти некуда — у тебя изменение закупки, а не продукта. Экономия получается заметной: вместо двухнедельного эксперимента, который должен подтвердить несуществующий эффект, один запрос на пятнадцать минут.
У маршрута есть побочная польза. Он превращает обсуждение «мне кажется, это цена» в последовательность проверок, у каждой из которых есть ответ в данных. На встрече это меняет сам характер разговора: спорить о гипотезах становится не нужно, потому что видно, какие из них уже отсеяны арифметикой.
| Шаг | Что делаешь | Вывод, если проверка срабатывает |
|---|---|---|
| 1. Данные | freshness, полнота дня, дубли, NULL | это не метрика, это пайплайн |
| 2. Календарь | тот же день недели, YoY, нормализация по дням | изменение объясняет календарь |
| 3. Состав | пересчёт на фиксированных весах | изменился микс, а не поведение |
| 4. База | holdout, baseline до отбора | это возврат к среднему |
| 5. Разложение | множители и сверка | понятно, какой компонент проверять экспериментом |
Как записать вывод, чтобы его не прочитали как доказательство
Формулировка решает не меньше, чем расчёт. Одно и то же наблюдение можно записать так, что команда пойдёт увеличивать бюджет, и так, что она пойдёт проверять закупку. Рабочая схема — три части: что видно в данных, что мешает сделать более сильный вывод, какой следующий шаг это меняет.
Про микс: «Общая конверсия выросла с 13% до 32,4% за счёт роста доли канала B с 10% до 90% трафика. Внутри A показатель упал с 10% до 9%, внутри B — с 40% до 35%. На весах июня июль даёт 11,6%, то есть качество обоих каналов ухудшилось. Рост агрегата не является подтверждением того, что закупка стала лучше».
Про регрессию: «После включения баннера конверсия вернулась с 4,1% к 7,6%. В holdout с тем же правилом отбора она вернулась к 7,5%. Добавочный эффект не подтверждён; оставляем баннер как гипотезу и запускаем его с контрольной группой на полном трафике».
Про аномалию: «Заказы на 19% ниже сезонного baseline. Сырые события и платёжный провайдер в норме, падение локализовано в Android 6.4 после вчерашнего релиза. Статус — подтверждённый продуктовый инцидент, владелец mobile team, следующее обновление в 14:00».
Общее у трёх примеров то, что в каждом названа проверка, которая уже пройдена, и граница, за которую вывод не распространяется. Такая запись живёт дольше цифры: через месяц по ней видно, на каком основании было принято решение, и это снимает половину повторных разборов.
Практика на своих данных
Возьми метрику, которая заметно изменилась за последний месяц, и прогони её через четыре проверки, ничего не меняя в продукте. Первая: пересчитай показатель на весах прошлого периода по двум-трём ключевым разрезам и запиши разницу с фактом отдельной строкой «вклад микса». Вторая: сравни тот же период с тем же днём недели и с прошлым годом, нормализовав на число дней. Третья: проверь, не была ли предыдущая точка экстремальной, и если да — посмотри, как вели себя похожие экстремальные точки раньше без вмешательства.
Четвёртая проверка — разложение. Напиши формулу метрики на бумаге, посчитай множители за оба периода и убедись, что их произведение равно фактическому изменению. Если не сходится, останавливайся здесь: расхождение в сверке означает ошибку в зерне или в знаменателе, и до её исправления остальные выводы не имеют значения.
В конце сравни два вывода: тот, который сделала бы команда по исходной цифре, и тот, который получился после четырёх проверок. Если они расходятся — а они расходятся чаще, чем ожидаешь, — у тебя есть конкретный повод добавить в регулярный отчёт строку вклада микса и подпись периода сравнения. Это дешевле любого разбора постфактум.
Итог и куда дальше
Метрика не обманывает сама — она отвечает ровно на тот вопрос, который задан её формулой, и молчит обо всём остальном. Обманывает чтение: из арифметики агрегата делают вывод о поведении пользователей, пропуская состав, календарь и базу. Поэтому надёжность приходит не от более точного расчёта, а от порядка проверок, в котором дешёвые объяснения отсекаются первыми.
Декомпозиция стоит в конце не потому, что она сложнее, а потому что она закрывает все предыдущие ловушки разом: микс виден как изменение веса компонента, календарь — как сдвиг окна, экстремальная база — как аномальное значение множителя. Когда разложение сошлось, у команды остаётся один честный вопрос вместо пяти догадок: какой именно компонент мы проверяем экспериментом.
Материалы по теме
Собеседование аналитика данных: 30 задач и как решать их вслух
Большой практический гайд по собеседованию аналитика данных: SQL, Python, метрики, статистика, кейсы, дашборды и ответы, которые показывают ход мышления.

Как тестировать аналитические расчёты на Python и pandas
Практический гайд по тестам для аналитика: проверить метрики на маленьком датасете, поймать регрессию и защитить расчёт от тихих изменений.
Почему BI-дашборд не работает: 12 ошибок от данных до решений
Диагностика дашборда, которым не пользуются или которому не доверяют: проверяем данные, метрики, интерфейс, скорость, владельцев и связь с решениями.