Дашборды, которые не врут: принципы продуктовой визуализации

Кратко

Дашборд — это не картинка, а утверждение. «Удержание 45%» на графике выглядит так же аккуратно, как «удержание 45%, правда только для когорт старше 3 месяцев». Визуализация обязана нести оговорки, а не прятать их.

Пять принципов, которые держат дашборды честными:

  • Дашборд перекладывает, а не агрегирует. Расчёт живёт в SQL/пайплайне один раз; дашборд читает готовые метрики и раскладывает их по осям.
  • Подпись точнее графика. Единица измерения, окно, observation age, дефиниция «активного» — всё это в заголовке, а не в комментарии.
  • Сравнимое сравнивается со сравнимым. Когорты одного возраста, периоды одной длины, сегменты с одинаковым знаменателем.
  • Пустое — это данные. Нет данных ≠ ноль. Незакрытые когорты, отсутствующие события, N<30 — показывать явно.
  • Одна панель — один вопрос. Дашборд-зоопарк из 30 графиков не отвечает ни на один вопрос.

Пример

Ошибка: дашборд сам пересчитывает метрику на лету из сырых событий.

# ❌ дашборд агрегирует заново — каждый разработчик получит свой ответ
df = read_all_events()
retention = df.groupby(["cohort", "age"]).agg(...)

Правильно: расчёт в пайплайне, дашборд раскладывает готовое.

# ✅ пайплайн посчитал один раз, дашборд читает результат
retention = pd.read_parquet("metrics/retention.parquet")
st.line_chart(retention.pivot(index="cohort", columns="age", values="pct"))

Как пишется

  1. Метрика — в пайплайне, оговорка — в подписи. Название графика: «Удержание M1–M12, когорты по месяцу регистрации, %». Всё, что важно для интерпретации, — в заголовке.
  2. Observation age в каждом когортном графике. Незакрытые ячейки не заполнять нулями; подписывать «когорты моложе 6 месяцев не показаны» или оставлять пустыми.
  3. Дефиниции рядом с метрикой. «Активный = ≥1 сессия», «выручка = net, после возвратов» — первой строкой описания панели.
  4. Порог значимости. Маленькие N выводить серым или с пометкой «<30 — не выводить вывод».
  5. Одна панель = один вопрос. Воронка онбординга, когортное удержание, MRR по планам — три панели, три вопроса.

Как понять

Почему «дашборд не агрегирует» — принцип, а не архитектурная деталь

Если два дашборда считают «удержание» из одних событий разными запросами, они дадут разные цифры, и начнётся война «чей график правильный». Когда метрика посчитана в одном месте (SQL-вьюха, parquet, пайплайн), дашборды расходятся только по осям, а не по цифрам. Разночтения метрик — это почти всегда разный код агрегации, а не разное зрение.

Пустое — это данные

Когортная матрица с пустым правым нижним углом сообщает «эти когорты ещё молоды» — это информация. Дашборд, который молча превращает пустое в ноль, сообщает ложь: «удержание упало до нуля». То же с отсутствием событий, с N<30, с отключённым трекингом.

Красота не заменяет честность

Градиентные палитры и анимированные переходы не делают цифру верной. Первое правило ревью дашборда — не «красиво?», а «какие данные не показаны и почему».

Подсказки

  • Подпись каждой оси и каждой единицы — обязательно, «шт», «%», «€», «месяц с регистрации».
  • Сортировка по сегментам по размеру, а не по алфавиту — глаза найдут главное быстрее.
  • Чек «перекладывания»: удалите дашборд — цифры в SQL-пайплайне должны остаться.
  • Для порогов (KPI) показывайте и целевую линию, и доверительный диапазон.
  • Мобильный контекст: таблицы скроллятся, ключевые KPI — сверху, не в глубине.

На практике

В Product Analytics Dashboard (Streamlit) дашборд читает заранее посчитанные метрики по синтетическому SaaS-датасету (8 000 пользователей): AARRR-воронка, когортное удержание, MRR/ARPU/logo churn, сегменты. Панель «перекладывает» готовые агрегаты, а не пересчитывает их из сырых событий — единый ответ, независимо от того, кто открыл дашборд.

В Sales Calls Analytics Dashboard — по той же схеме: 4-шаговая воронка с разбором утечки по типам, взвешенные потери контактов, вовлечённость по часам и дням. И в Supabase — дашборд, который «reshapes, does not aggregate», поверх RLS.

На собеседовании

❓ Почему дашборд не должен агрегировать данные сам?

Потому что две панели, считающие одну метрику разным кодом, дадут разные цифры и развяжут войну за «правильность». Когда метрика посчитана один раз в SQL-пайплайне, дашборды расходятся только по осям и фильтрам, а не по значениям. Разночтение метрик почти всегда значит «разный код агрегации», а не «разное зрение».

— Nikita Boyarkin

❓ Как показывать незакрытые когорты и маленькие выборки?

Незакрытые когорты — пустой треугольник в матрице с явной подписью observation age, не ноль. Маленькие выборки (N<30) — серым цветом и без выводов, с порогом значимости. Правило: пустое — это данные, и дашборд должен говорить об этом словами, а не молча превращать пустое в ноль.

— Nikita Boyarkin

Ссылки

Смотрите также

← К статьям