Продуктовая аналитика и принятие решений на данных
В https://courses.digitable.life/post/product-management/02-metrics/ мы выбирали, какие числа считать. В https://courses.digitable.life/post/product-management/05-mvp-and-experiments/ — как ставить эксперимент, чтобы получить честный ответ. Эта статья про то, что происходит между ними и после них: как из сырых событий получается решение, за которое не стыдно через полгода.
Ключевая мысль всей статьи: аналитика — это не про графики. Аналитика — это дисциплина уменьшения неопределённости ровно настолько, насколько это оправдано ценой ошибки. Любой анализ, который не меняет ни одного решения, — потраченные деньги, даже если он методологически безупречен.
1. Зачем это нужно: аналитика как экономика информации
1.1. Решение первично, данные вторичны
Классическая ошибка: «давайте построим дашборд, а потом посмотрим, что интересного». Правильный порядок обратный — от решения к данным:
- Какое решение мы принимаем? (запускать / не запускать, повышать цену / нет, куда идёт квартал)
- Какие альтернативы рассматриваем?
- Что должно быть правдой, чтобы выбрать альтернативу А, а не Б?
- Какое наблюдение отличит эти миры друг от друга?
- И только теперь — какой запрос/эксперимент даст это наблюдение?
Пункт 4 — самый важный и самый пропускаемый. Если вы не можете назвать наблюдение, которое изменит ваше решение, вы собираете данные не для решения, а для комфорта.
1.2. Ценность информации (EVSI) — почему не всё стоит измерять
У любой информации есть цена и есть ценность. Ценность ограничена сверху: информация полезна ровно настолько, насколько она может переключить ваш выбор.
Простая модель. Решение: катить фичу или нет. Если катим и она хорошая — +300 $k, если катим и плохая — −100 $k. Априорная вероятность «хорошая» = 0.6.
# Ожидаемая ценность идеальной информации (EVPI):
# сколько максимум стоит потратить на исследование/эксперимент.
p_good, gain, loss = 0.6, 300_000, -100_000
ev_without_info = max(p_good * gain + (1 - p_good) * loss, 0) # решаем «вслепую» или не делаем
# С идеальной информацией мы катим только в хорошем мире:
ev_with_perfect_info = p_good * gain + (1 - p_good) * 0
evpi = ev_with_perfect_info - ev_without_info
print(ev_without_info, ev_with_perfect_info, evpi) # 140000 180000 40000
Вывод: даже идеальное знание здесь стоит не больше 40 $k. Двухмесячное исследование
командой из трёх человек — уже дороже, чем весь возможный выигрыш от него.
А если бы p_good = 0.95, EVPI ≈ 5k $ — измерять почти бессмысленно, надо просто катить.
Это формальное обоснование двух практических правил:
- Обратимые решения (two-way doors, по формулировке Джеффа Безоса в письме акционерам Amazon 2015) принимаются быстро и на слабых данных: цена ошибки = стоимость отката.
- Необратимые (миграция тарифов, смена позиционирования, удаление фичи с контрактными обязательствами) требуют дорогих методов с верхних ступеней лестницы достоверности.
1.3. Четыре уровня аналитических вопросов
| Уровень | Вопрос | Инструменты | Типичная ошибка |
|---|---|---|---|
| Дескриптивный | Что произошло? | дашборды, воронки, когорты | принять корреляцию за причину |
| Диагностический | Почему произошло? | декомпозиция, сегменты, mix-анализ | остановиться на первом «виновнике» |
| Предиктивный | Что будет? | прогнозы, churn-модели, LTV | доверять прогнозу вне области определения |
| Прескриптивный | Что делать? | эксперименты, causal inference, оптимизация | оптимизировать метрику вместо ценности |
95% продуктовой работы живёт на первых двух уровнях, а ценность решения создаётся на четвёртом. Задача продакта — не застревать на первом.
2. Фундамент: модель данных и tracking plan
Никакая аналитика не спасёт кривой сбор данных. Прежде всего нужна явная модель событий.
2.1. Событийная модель
Базовая единица — событие: кто, что сделал, когда, в каком контексте. Всё остальное (воронки, retention, LTV) — производные агрегаты.
Три детали, которые чаще всего забывают и потом дорого чинят:
occurred_at≠received_at. Мобильный клиент в офлайне отправит событие через три дня. Если считать поreceived_at, вы «переоткроете» вчерашний день и сломаете исторические отчёты; если только поoccurred_at— отчёты за последние дни будут недосчитывать. Практика: считать поoccurred_at, но помечать последние N дней как «неполные» и мерить долю опоздавших.anonymous_id→user_id(identity stitching). Пользователь ходит анонимно, потом регистрируется. Без склейки вы теряете весь путь до регистрации — и воронка активации превращается в фантазию.schema_version. Свойствоpriceбыло в рублях, стало в копейках — без версии схемы вы никогда не узнаете, где произошёл сдвиг.
2.2. Tracking plan: контракт между продуктом и данными
Tracking plan — это документ (лучше — YAML в репозитории с CI-валидацией), где для каждого события зафиксированы имя, владелец, свойства и типы.
# tracking/checkout.yaml — версионируется в репозитории, проверяется в CI
- name: checkout_started
owner: team-payments
description: "Пользователь открыл экран оплаты. Одно событие на попытку."
version: 2
properties:
cart_value_minor: { type: integer, required: true, unit: "копейки" }
currency: { type: string, required: true, enum: ["RUB", "USD", "EUR"] }
items_count: { type: integer, required: true, min: 1 }
payment_method: { type: string, required: false, enum: ["card", "sbp", "wallet"] }
experiment_ids: { type: array, required: false }
- name: checkout_completed
owner: team-payments
description: "Платёж подтверждён провайдером. Источник истины — бэкенд, не клиент."
version: 2
properties:
order_id: { type: string, required: true }
cart_value_minor: { type: integer, required: true }
revenue_net_minor:{ type: integer, required: true, description: "за вычетом комиссии" }
Правила именования, которые окупаются на горизонте года:
object_actionв прошедшем времени:checkout_started,invite_sent. НеClick Button.- Деньги — только в минорных единицах целым числом. Floating point в выручке — источник вечных расхождений с финансами.
- Критичные для денег события пишутся с бэкенда. Клиентские события теряются на 5–30% (блокировщики, офлайн, краши) и легко подделываются.
- Одно событие — один смысл.
page_viewedсо свойствомtypeлучше, чем 40 отдельных событий.
Хороший разбор дисциплины трекинга — в документации Segment Tracking Plan и Amplitude Data Taxonomy.
2.3. Слои данных
web / iOS / Android"] A2["Бэкенд-события"] A3["OLTP-реплики
users, orders"] A4["Внешнее
реклама, биллинг, CRM"] end A1 & A2 & A3 & A4 --> B["Raw / bronze
сырое, неизменяемое, append-only"] B --> C["Staging / silver
типизация, дедуп, identity stitching"] C --> D["Marts / gold
витрины: fct_events, dim_users, fct_orders"] D --> E["Metrics layer
единые определения метрик"] E --> F1["BI и дашборды"] E --> F2["Платформа экспериментов"] E --> F3["Ad-hoc SQL / ноутбуки"] E --> F4["Обратный ETL
сегменты в CRM"] style B fill:#b1becd,stroke:#7f8c9b style C fill:#86a9c2,stroke:#5c7f98 style D fill:#5891b1,stroke:#3d6c86 style E fill:#3f8f6b,stroke:#2c6a4e,color:#ffffff
Смысл разделения: сырой слой никогда не переписывается, поэтому любую ошибку трансформации можно исправить пересчётом задним числом. Metrics layer (dbt Semantic Layer, Cube, Malloy) существует ради одной цели — чтобы «активный пользователь» означал одно и то же в отчёте маркетинга, в дашборде команды и в анализе эксперимента. Без него компания тратит месяцы на споры о том, чьё число верное.
3. Качество данных: единственная вещь, которую нельзя починить постфактум
3.1. Как ломается трекинг
| Симптом | Частая причина | Как ловить |
|---|---|---|
| Метрика упала ровно в полночь на 100% | релиз уронил событие / переименовали | алерт на объём событий, а не только на метрику |
| Конверсия выросла в 2 раза за день | дедупликация сломалась, ретраи считаются | проверка count(distinct event_id) vs count(*) |
| Резкий рост «пользователей» | боты, скрейперы, нагрузочное тестирование | фильтр по user-agent, аномально высокая частота |
| Разошлись с финансами на 3% | клиентские платёжные события | сверка с биллингом ежедневно |
| «Дыра» в данных за вчера | опоздавшие события | доля событий с лагом received_at − occurred_at |
3.2. Тесты данных как код
Data quality проверяется автоматически, а не глазами. Минимальный набор в dbt/Great Expectations:
-- tests/assert_event_sanity.sql — падает, если возвращает хотя бы одну строку
with daily as (
select
date_trunc('day', occurred_at) as d,
name,
count(*) as events,
count(distinct event_id) as unique_events,
count(distinct user_id) as users,
avg(extract(epoch from received_at - occurred_at)) as avg_lag_sec
from {{ ref('stg_events') }}
where occurred_at >= current_date - interval '30 days'
group by 1, 2
),
stats as ( -- ожидаемый уровень: медиана и MAD за 28 дней
select
name, d, events, unique_events, avg_lag_sec,
percentile_cont(0.5) within group (order by events)
over (partition by name) as median_events
from daily
)
select *
from stats
where events <> unique_events -- дубликаты
or events < median_events * 0.5 -- обвал объёма
or events > median_events * 2.0 -- всплеск (боты/ретраи)
or avg_lag_sec > 3600; -- аномальная задержка доставки
Правило, которое стоит закрепить организационно: алерт на метрику без алерта на объём исходных событий бесполезен. Половина «падений метрик» — это падение трекинга, и дешевле узнать об этом за 20 минут, чем через неделю расследования.
4. Диагностика: почему метрика изменилась
4.1. Ловушка агрегата — парадокс Симпсона
Самый частый источник неверных выводов в продукте. Агрегированная конверсия падает, хотя в каждом сегменте она растёт, потому что изменился состав аудитории.
Формально: агрегат — это средневзвешенное, p = Σ wᵢ · pᵢ, где wᵢ — доля сегмента.
Изменение агрегата раскладывается на эффект ставки (менялись pᵢ) и эффект состава
(менялись wᵢ). Академический разбор — у Джудеи Перла в
«The Book of Why» и статье
Simpson’s Paradox (Stanford Encyclopedia of Philosophy).
4.2. Декомпозиция mix / rate
Каноническое разложение (аналог shift-share анализа):
Δp = Σᵢ w⁰ᵢ · (p¹ᵢ − p⁰ᵢ) ← rate effect: изменилось качество внутри сегментов
+ Σᵢ p⁰ᵢ · (w¹ᵢ − w⁰ᵢ) ← mix effect: изменился состав аудитории
+ Σᵢ (w¹ᵢ − w⁰ᵢ)(p¹ᵢ − p⁰ᵢ) ← interaction: остаток
from dataclasses import dataclass
@dataclass
class Segment:
name: str
users_before: int
conv_before: float # доля 0..1
users_after: int
conv_after: float
def decompose(segments: list[Segment]) -> dict:
"""Разложение изменения агрегированной конверсии на rate / mix / interaction.
Сложность: O(S) по времени и O(1) по дополнительной памяти, где S — число сегментов.
Само получение segments из событий — это group by, O(N) при хешировании
или O(N log N) при сортировке, N — число событий.
"""
n0 = sum(s.users_before for s in segments)
n1 = sum(s.users_after for s in segments)
p0 = sum(s.users_before * s.conv_before for s in segments) / n0
p1 = sum(s.users_after * s.conv_after for s in segments) / n1
rate = mix = inter = 0.0
per_segment = {}
for s in segments:
w0, w1 = s.users_before / n0, s.users_after / n1
dp, dw = s.conv_after - s.conv_before, w1 - w0
r, m, i = w0 * dp, s.conv_before * dw, dw * dp
rate += r; mix += m; inter += i
per_segment[s.name] = {"rate": r, "mix": m, "interaction": i, "total": r + m + i}
return {
"p_before": p0, "p_after": p1, "delta": p1 - p0,
"rate_effect": rate, "mix_effect": mix, "interaction": inter,
"by_segment": per_segment,
}
segments = [
Segment("Новые", users_before=1000, conv_before=0.04, users_after=4000, conv_after=0.05),
Segment("Старые", users_before=1000, conv_before=0.20, users_after=1000, conv_after=0.22),
]
r = decompose(segments)
print(f"было {r['p_before']:.1%} → стало {r['p_after']:.1%} (Δ = {r['delta']:+.1%})")
print(f" качество (rate): {r['rate_effect']:+.2%}")
print(f" состав (mix): {r['mix_effect']:+.2%}")
print(f" взаимодействие: {r['interaction']:+.2%}")
# было 12.0% → стало 8.4% (Δ = -3.6%)
# качество (rate): +1.50%
# состав (mix): -4.80%
# взаимодействие: -0.30%
Интерпретация: продукт стал лучше (+1.5 п.п.), а падение агрегата на 4.8 п.п. — целиком результат маркетингового решения залить дешёвый трафик. Действие — не «чинить продукт», а обсуждать с маркетингом качество канала и, возможно, менять целевую метрику на посегментную.
Тот же разбор в SQL, если сегментов много и нужен рейтинг вкладов:
with base as (
select segment,
sum(case when period = 'before' then users end) as u0,
sum(case when period = 'before' then conv * users end)
/ nullif(sum(case when period = 'before' then users end), 0) as p0,
sum(case when period = 'after' then users end) as u1,
sum(case when period = 'after' then conv * users end)
/ nullif(sum(case when period = 'after' then users end), 0) as p1
from segment_period_stats
group by segment
),
tot as (select sum(u0) as n0, sum(u1) as n1 from base)
select b.segment,
(u0 / t.n0) * (p1 - p0) as rate_effect,
p0 * (u1 / t.n1 - u0 / t.n0) as mix_effect,
(u1 / t.n1 - u0 / t.n0) * (p1 - p0) as interaction
from base b cross join tot t
order by abs(rate_effect + mix_effect + interaction) desc
limit 20; -- 20 сегментов с наибольшим вкладом в изменение
4.3. Алгоритм расследования: от «упало» до «понял»
ожидаемого коридора"] --> Q0{"Изменение статистически
отличимо от шума?"} Q0 -- "нет" --> NOISE["Не действовать.
Расширить окно наблюдения"] Q0 -- "да" --> Q1{"Объём событий
и тесты данных в норме?"} Q1 -- "нет" --> DATA["Инцидент данных:
релиз, SDK, пайплайн, дедуп"] Q1 -- "да" --> Q2{"Есть внешнее событие?
праздник, сбой, конкурент, регуляторика"} Q2 -- "да" --> EXT["Отметить в календаре событий.
Сравнить с прошлым аналогом"] Q2 -- "нет" --> Q3{"Падение во всех сегментах
или в отдельных?"} Q3 -- "во всех" --> GLOBAL["Глобальная причина:
релиз, цена, инфраструктура, performance"] Q3 -- "в отдельных" --> Q4{"Mix или rate?
декомпозиция"} Q4 -- "mix" --> MIX["Изменился состав трафика.
Разговор с маркетингом, а не с продуктом"] Q4 -- "rate" --> Q5{"Где именно в воронке
потеря?"} Q5 --> FUNNEL["Локализовать шаг → сессии с потерей →
записи сессий, логи ошибок, support-тикеты"] FUNNEL --> HYP["Сформулировать гипотезу
с проверяемым предсказанием"] GLOBAL --> HYP MIX --> HYP EXT --> HYP HYP --> TEST{"Гипотеза предсказывает что-то,
чего мы ещё не смотрели?"} TEST -- "да" --> CHECK["Проверить предсказание
на независимых данных"] TEST -- "нет" --> WEAK["Гипотеза нефальсифицируема.
Переформулировать"] CHECK --> DEC["Решение + запись в decision log"] style DATA fill:#d98d5a,stroke:#a9633a style NOISE fill:#b1becd,stroke:#7f8c9b style DEC fill:#3f8f6b,stroke:#2c6a4e,color:#ffffff style WEAK fill:#c0503f,stroke:#8d3b2e,color:#ffffff
Два узла здесь чаще всего пропускают, и оба дорого стоят.
Проверка на шум. Прежде чем расследовать, посчитайте, насколько метрика вообще
колеблется в норме. Дневная конверсия при 5000 визитов и p ≈ 0.12 имеет
стандартное отклонение sqrt(p(1−p)/n) ≈ 0.46 п.п., то есть колебания ±1 п.п.
день ко дню — обычный вторник, а не повод собирать митинг.
import math
def is_signal(p_hat: float, n: int, p_baseline: float, z: float = 3.0) -> bool:
"""Грубый контроль: отличается ли дневное значение от базового более чем на z сигм.
Сложность O(1). Для метрик с автокорреляцией лучше STL-декомпозиция или
доверительный интервал по историческому распределению остатков."""
se = math.sqrt(p_baseline * (1 - p_baseline) / n)
return abs(p_hat - p_baseline) > z * se
print(is_signal(0.113, 5000, 0.12)) # False — шум
print(is_signal(0.098, 5000, 0.12)) # True — сигнал
Фальсифицируемость. «Пользователям не понравился новый дизайн» — не гипотеза, её нельзя опровергнуть. «Новый дизайн увеличил время загрузки экрана каталога на мобильных Android > 2 с, из-за чего просела конверсия именно там» — гипотеза: она предсказывает конкретные числа в конкретном разрезе, которых мы ещё не смотрели.
5. Причинность: почему корреляция почти всегда врёт
5.1. Три источника ложного вывода
- Конфаундер. Пользователи, включившие уведомления, удерживаются лучше — но они изначально более вовлечены. Включение уведомлений не делает их лояльными.
- Обратная причинность. «Те, кто пользуется поиском, чаще покупают» — возможно, наоборот: те, кто уже решил купить, идут в поиск.
- Отбор (survivorship / selection bias). Опрос активных пользователей о причинах оттока не покажет причин оттока — ушедшие в выборку не попали.
5.2. Инструменты, когда A/B невозможен
Полный A/B разобран в https://courses.digitable.life/post/product-management/05-mvp-and-experiments/. Здесь — что делать, когда рандомизация недоступна: изменение затрагивает всех (цена, бренд, логистика), эффект «перетекает» между пользователями (маркетплейс, соцсеть), или тест уже прошёл.
Difference-in-Differences (DiD). Есть тестовая и контрольная группы (например, города), изменение выкатили только в тестовой. Считаем изменение «после − до» в обеих и вычитаем:
def did(y_treat_pre, y_treat_post, y_ctrl_pre, y_ctrl_post) -> float:
"""Оценка эффекта методом разность разностей.
Критическое допущение — parallel trends: без вмешательства обе группы
менялись бы одинаково. Проверяется на исторических периодах ДО вмешательства.
Сложность O(1) для точечной оценки; для доверительного интервала —
кластерный bootstrap O(B·K), B ~ 1000 итераций, K — число кластеров."""
return (y_treat_post - y_treat_pre) - (y_ctrl_post - y_ctrl_pre)
# Конверсия в тестовых городах 12.0% → 14.5%, в контрольных 11.5% → 12.3%
print(f"{did(0.120, 0.145, 0.115, 0.123):+.2%}") # +1.70% — чистый эффект
Обязательная проверка: постройте графики обеих групп за 8–12 периодов до вмешательства. Если линии не параллельны — DiD неприменим, оценка будет смещённой.
Switchback. Для маркетплейсов и логистики, где эффект глобальный: включаем и выключаем изменение по расписанию в одном регионе (например, чередование часовыми блоками). Единица рандомизации — интервал времени, а не пользователь. Это устраняет интерференцию, но требует аккуратного учёта carryover-эффектов (первые минуты после переключения обычно выбрасывают). Разбор — в инженерных блогах Doordash и Lyft.
Synthetic control. Строим «синтетический контроль» — взвешенную комбинацию непострадавших регионов, которая максимально повторяет динамику тестового до вмешательства, и сравниваем после. Метод Абади: Abadie, Diamond, Hainmueller, 2010.
CUPED — не про причинность, а про мощность: используем предпериодные данные, чтобы снизить дисперсию метрики и увидеть эффект на меньшей выборке.
import numpy as np
def cuped(y: np.ndarray, x: np.ndarray) -> np.ndarray:
"""CUPED: корректируем метрику y ковариатой x (та же метрика в предпериоде).
y_adj = y − θ(x − E[x]), где θ = cov(x,y)/var(x).
Матожидание не смещается, дисперсия падает в (1 − ρ²) раз.
Сложность O(n) по времени и O(n) по памяти."""
theta = np.cov(x, y, ddof=1)[0, 1] / np.var(x, ddof=1)
return y - theta * (x - x.mean())
rng = np.random.default_rng(42)
x = rng.gamma(2.0, 3.0, 200_000) # активность в предпериоде
y = 0.8 * x + rng.normal(0, 2.0, 200_000) # активность в эксперименте
y_adj = cuped(y, x)
print(f"дисперсия: {y.var():.2f} → {y_adj.var():.2f}") # 15.55 → 3.98
print(f"выборка сокращается во столько же раз: {y.var()/y_adj.var():.1f}x") # 3.9x
Оригинальная работа: Deng, Xu, Kohavi, Walker, «Improving the Sensitivity of Online Controlled Experiments by Utilizing Pre-Experiment Data», WSDM 2013.
Условие корректности: x вычисляется строго до старта эксперимента. Ковариата,
затронутая вмешательством, вносит смещение.
6. Статистика ровно в том объёме, который нужен продакту
6.1. Интервал важнее p-value
p < 0.05 говорит только «эффект вряд ли ноль». Решение принимается не по этому,
а по величине эффекта и её неопределённости. Всегда смотрите доверительный интервал
и сравнивайте его с порогом практической значимости.
| Результат | CI эффекта | Что делать |
|---|---|---|
| Значимо, CI = [+0.1%; +0.3%] | эффект есть, но мизерный | не катить: не окупит поддержку |
| Незначимо, CI = [−0.2%; +0.2%] | эффект точно мал | решать по другим критериям (простота, стратегия) |
| Незначимо, CI = [−5%; +7%] | ничего не знаем | тест был бессмысленным: не хватило мощности |
| Значимо, CI = [+2%; +9%] | эффект есть и крупный | катить |
Третья строка — самая частая и самая опасная: «различий не обнаружено» интерпретируют как «различий нет». Это не одно и то же.
import math
def sample_size_per_arm(p: float, mde_rel: float, alpha: float = 0.05, power: float = 0.8) -> int:
"""Размер выборки на вариант для двусторонней проверки долей.
n ≈ (z_{1−α/2} + z_{1−β})² · (p1(1−p1) + p2(1−p2)) / (p2 − p1)²
Ключевое: n растёт как 1/MDE² — чтобы поймать вдвое меньший эффект,
нужно вчетверо больше трафика. Сложность O(1)."""
z_a, z_b = 1.959964, 0.841621 # для alpha=0.05, power=0.8
p2 = p * (1 + mde_rel)
num = (z_a + z_b) ** 2 * (p * (1 - p) + p2 * (1 - p2))
return math.ceil(num / (p2 - p) ** 2)
for mde in (0.20, 0.10, 0.05, 0.02):
print(f"MDE {mde:>5.0%}: {sample_size_per_arm(0.05, mde):>10,} пользователей на вариант")
# MDE 20%: 8,155
# MDE 10%: 31,231
# MDE 5%: 122,121
# MDE 2%: 752,700
Практический вывод: продукт с 20k пользователей в месяц физически не может измерить эффект в 2% на конверсии 5%. Не надо делать вид, что может, — надо либо выбирать более чувствительные метрики (ближе к вмешательству), либо применять CUPED, либо принимать решение на качественных данных и осознанно.
6.2. Подглядывание и множественные сравнения
Две ошибки, которые обесценивают половину продуктовых экспериментов:
- Peeking. Смотреть на тест каждый день и остановиться, когда «стало значимо», раздувает ложноположительные с 5% до 20–30%. Лекарство: фиксировать длительность заранее либо использовать методы, корректные при непрерывной проверке — sequential testing / always-valid inference (Johari, Pekelis, Walsh).
- Множественные метрики. 20 метрик при α = 0.05 дадут в среднем одну «значимую»
случайно:
1 − 0.95²⁰ ≈ 64%вероятность хотя бы одного ложного срабатывания. Лекарство: одна заранее объявленная primary-метрика, остальные — guardrail (проверяются на «не ухудшилось») и exploratory (только для генерации гипотез).
6.3. Guardrail-метрики
Отдельный класс метрик, которые не должны улучшаться — они должны не ломаться: время загрузки, доля ошибок, отток, обращения в поддержку, доля возвратов. Формально проверяются на non-inferiority: «не хуже, чем на X%». Именно guardrails ловят классику вида «конверсия в подписку +8%, отмены в первый месяц +30%».
7. От анализа к решению
7.1. Жизненный цикл решения
Ветка Запись → Проверка → Калибровка — то, что отличает организацию, которая учится,
от организации, которая просто много анализирует. Без замыкания петли команда годами
повторяет одни и те же ошибки оценки, потому что никто не измеряет качество прогнозов.
7.2. Decision log
Шаблон записи, который занимает 10 минут и экономит месяцы:
id: DEC-2026-041
date: 2026-05-14
decision: "Убрать обязательную регистрацию перед первым заказом"
type: two-way-door # откат ~ 3 дня работы
owner: "@product-checkout"
context: |
Воронка теряет 34% на шаге регистрации. Интервью (n=12) показывают недоверие
к передаче телефона до понимания цены.
alternatives:
- "Оставить как есть"
- "Гостевой заказ с post-purchase регистрацией" # выбрано
- "Соцавторизация" # отклонено: 2 недели интеграции
evidence:
- "A/B n=180k, конверсия оплаты +6.1% [CI +3.4%; +8.9%]"
- "Guardrail: доля фрода +0.02 п.п., в пределах допустимого"
- "Guardrail: повторные заказы за 30 дней −0.4% [CI −1.6%; +0.8%] — не отличимо"
prediction: "За Q3 доля повторных заказов не упадёт более чем на 1 п.п."
review_at: 2026-10-01
outcome: null # заполняется на review
Поля prediction и review_at обязательны. Именно они превращают решение в проверяемое
утверждение, а команду — в калибруемую систему. Родственная практика в инженерии — ADR
(Architecture Decision Records); смежная тема разобрана в
https://courses.digitable.life/post/architecture-patterns/00-overview/.
7.3. Кто и как принимает решение по данным
Обратите внимание на порядок: сначала данные, потом продукт. И на последний шаг — каждый инцидент должен оставлять после себя новый автоматический контроль, иначе организация ловит одну и ту же проблему повторно.
8. Как это выглядит в проде
8.1. Слои потребления аналитики
| Слой | Кто пользуется | Требование | Антипаттерн |
|---|---|---|---|
| Health-дашборд | вся команда, ежедневно | 5–9 метрик, коридоры, алерты | 60 графиков «на всякий случай» |
| Weekly business review | продукт + бизнес | метрика → декомпозиция → действие | чтение чисел вслух без решений |
| Self-serve исследование | продакты сами | metrics layer, документированные витрины | очередь в 3 недели к аналитику |
| Глубокий анализ | аналитик | причинность, моделирование | ad-hoc «посчитай мне цифру» |
| Платформа экспериментов | все | автоматический расчёт, guardrails | Excel и ручные t-тесты |
Практическое правило зрелой аналитики: доля вопросов, на которые продакт отвечает сам, за минуты, без аналитика. Если она ниже 70%, узкое место — не люди, а доступность и документированность данных.
8.2. Дизайн дашборда, по которому принимают решения
- Одна страница, ≤ 9 чисел. Что не помещается — не смотрят.
- Каждое число с контекстом: значение, изменение к прошлому периоду, ожидаемый коридор. Число без коридора не позволяет отличить сигнал от шума.
- Аннотации на графиках: релизы, промо, праздники, инциденты. Без них любой скачок порождает получасовое расследование заново.
- Сегменты рядом с агрегатом — защита от парадокса Симпсона на уровне интерфейса.
- У каждого блока есть владелец и вопрос, на который блок отвечает. Нет вопроса — удалить.
Про визуальную часть — Edward Tufte, «The Visual Display of Quantitative Information» и Stephen Few, «Information Dashboard Design».
8.3. Метрика становится целью — и перестаёт быть метрикой
Закон Гудхарта: «Когда мера становится целью, она перестаёт быть хорошей мерой». В продукте это выглядит буднично:
| Цель по метрике | Как её «достигают» | Что ломается |
|---|---|---|
| DAU | пуши каждый день | отписки, удаление приложения |
| Время в приложении | бесконечная лента, замедление навигации | удовлетворённость, отток |
| Число регистраций | принудительная регистрация до ценности | конверсия в оплату |
| Тикеты закрыты быстро | закрывают без решения | повторные обращения, NPS |
Защита: (1) каждой цели соответствует guardrail-пара; (2) целевая метрика измеряет полученную ценность, а не активность; (3) метрики привязываются к результату, а не к личным бонусам за конкретное число. Подробнее про подбор такой пары — в https://courses.digitable.life/post/product-management/02-metrics/ и https://courses.digitable.life/post/product-management/07-pricing-and-growth/.
9. Типичные ошибки
- Анализ без решения. «Интересный инсайт», после которого никто ничего не делает. Лекарство: начинать с вопроса «какое решение это изменит?».
- HARKing (Hypothesizing After the Results are Known). Нарезали данные на 30 сегментов, нашли «значимый» эффект в одном и объявили его гипотезой. Лекарство: заранее зафиксированный план анализа; найденное постфактум — только кандидат на новую проверку.
- Смешение корреляции и причины. «Пользователи фичи X удерживаются в 3 раза лучше» — почти всегда отбор, а не эффект фичи.
- Средние по несимметричным распределениям. Средний чек при лог-нормальном распределении сдвигают несколько китов. Смотрите медиану, перцентили и trimmed mean.
- Игнорирование выживших. Retention считается только по тем, кто дожил; когорты обязательны, иначе вы измеряете состав, а не поведение.
- Сравнение неполных периодов. «Этот месяц хуже прошлого» — а прошедших дней в нём 19 против 31, и в нём меньше выходных.
- Один тест = одна правда. Эффект в A/B — это оценка на конкретной популяции в конкретное время. Повторяемость проверяется репликацией, а не убеждённостью.
- Данные вместо суждения. Данные говорят о прошлом. Стратегические ставки (https://courses.digitable.life/post/product-management/04-strategy-and-roadmap/) по определению не имеют данных — для них есть суждение, дискавери (https://courses.digitable.life/post/product-management/01-discovery-and-research/) и дешёвые обратимые эксперименты.
- Дашборд как замена мышлению. Числа показывают, что; понимание почему приходит из разговоров с пользователями и просмотра сессий.
10. Мини-итог
- Аналитика начинается с решения, а не с данных. Ценность информации ограничена тем, насколько она способна изменить выбор — считайте EVPI до, а не после исследования.
- Качество сбора (tracking plan, версии схем,
occurred_atvsreceived_at, identity stitching) невозможно починить задним числом. Тесты данных — часть CI. - Прежде чем расследовать падение метрики, проверьте, что это не шум и не поломка трекинга. Это отсекает половину «инцидентов».
- Агрегат врёт: раскладывайте изменение на rate и mix. Парадокс Симпсона — повседневность, а не экзотика.
- Причинность — лестница: от корреляции через когорты и DiD к switchback и A/B. Поднимайтесь ровно настолько, насколько дорога ошибка.
- Смотрите доверительный интервал, а не p-value. «Незначимо» ≠ «эффекта нет».
- Решения записывайте вместе с прогнозом и датой ревью — это единственный способ калибровать команду.
Источники
- Ron Kohavi, Diane Tang, Ya Xu. Trustworthy Online Controlled Experiments, Cambridge University Press, 2020 — базовая книга по продуктовым экспериментам.
- Deng, Xu, Kohavi, Walker. CUPED: Improving the Sensitivity of Online Controlled Experiments, WSDM 2013.
- Johari, Pekelis, Walsh. Always Valid Inference: Continuous Monitoring of A/B Tests, 2015.
- Judea Pearl, Dana Mackenzie. The Book of Why, 2018 — причинность для практиков.
- Scott Cunningham. Causal Inference: The Mixtape — DiD, matching, synthetic control, свободно доступна онлайн.
- Angrist, Pischke. Mostly Harmless Econometrics — строгая версия того же.
- Abadie, Diamond, Hainmueller. Synthetic Control Methods for Comparative Case Studies, JASA 2010.
- Douglas Hubbard. How to Measure Anything — EVPI и измерение «неизмеримого».
- Annie Duke. Thinking in Bets — про разделение качества решения и качества исхода.
- Alistair Croll, Benjamin Yoskovitz. Lean Analytics.
- Segment. Tracking Plan documentation.
- dbt Labs. Best practices for data modeling.
- Edward Tufte. The Visual Display of Quantitative Information.
Что дальше
Это последняя статья трека «Product Management». Собранная картина выглядит так: дискавери (https://courses.digitable.life/post/product-management/01-discovery-and-research/) находит проблему, метрики (https://courses.digitable.life/post/product-management/02-metrics/) делают её измеримой, приоритизация (https://courses.digitable.life/post/product-management/03-prioritization/) и стратегия (https://courses.digitable.life/post/product-management/04-strategy-and-roadmap/) выбирают, куда идти, MVP и эксперименты (https://courses.digitable.life/post/product-management/05-mvp-and-experiments/), требования и UX (https://courses.digitable.life/post/product-management/06-ux-and-requirements/), монетизация (https://courses.digitable.life/post/product-management/07-pricing-and-growth/) превращают выбор в продукт — а аналитика замыкает петлю обратной связи.
Куда идти дальше:
- https://courses.digitable.life/post/project-management/00-overview/ — как доводить решения до релиза: планирование, риски, работа с командой и стейкхолдерами.
- https://courses.digitable.life/post/data-engineering/00-overview/ — инженерия того самого слоя данных, на который опирается вся эта статья: пайплайны, хранилища, качество данных.
- https://courses.digitable.life/post/machine-learning/00-overview/ — когда описательной аналитики мало и нужны предсказательные модели: churn, ранжирование, рекомендации.
- https://courses.digitable.life/post/architecture-patterns/00-overview/ — как продуктовые решения ложатся на архитектуру системы.
- https://courses.digitable.life/post/algorithms/00-overview/ и https://courses.digitable.life/post/golang/00-overview/ — если хочется укрепить инженерный фундамент.
Общая карта портала и рекомендуемый порядок изучения — в https://courses.digitable.life/post/roadmap/.