Как отличить сигнал от шума в данных

Главная проблема

Вы смотрите на дашборд: DAU вчера был 102000, сегодня 99500. Упал на 2.5%. Это проблема или нет?

Ответ не очевиден. Любая метрика имеет естественную волатильность. Понедельник всегда меньше пятницы, праздники сильно искажают картину. Случайное колебание от 98 до 105 тысяч DAU может быть нормой.

Задача аналитика — отделить сигналы (реальные изменения, требующие внимания) от шума (случайные колебания вокруг среднего).

Базовый подход — смотреть std

Первое, что стоит посчитать для любой метрики, — её историческое стандартное отклонение. Например, за последние 3 месяца DAU колебалось от 95 до 110 тысяч со средним 102000 и std 3500.

Правило 2σ: если текущее значение отклоняется от среднего более чем на 2σ, это подозрительно. Только 5% значений в нормальном распределении выходят за эти границы случайно.

В примере: среднее 102000, std 3500. Границы 2σ: 95000-109000. Сегодняшние 99500 — в пределах нормы. Это шум, не сигнал.

Control chart

Классический инструмент из QC (quality control), применённый к аналитике. График метрики во времени с тремя линиями:

  • Центральная — среднее.
  • Верхняя — среднее + 2σ (или 3σ для более строгого control).
  • Нижняя — среднее − 2σ.

Точки внутри границ — это шум, нормальная волатильность. Точки за границами — сигналы, требующие внимания.

В pandas легко сделать:

rolling_mean = df['dau'].rolling(30).mean()
rolling_std = df['dau'].rolling(30).std()

df['upper_2sigma'] = rolling_mean + 2 * rolling_std
df['lower_2sigma'] = rolling_mean - 2 * rolling_std

import matplotlib.pyplot as plt
plt.plot(df['date'], df['dau'], label='DAU')
plt.plot(df['date'], rolling_mean, label='Mean', linestyle='--')
plt.fill_between(df['date'], df['lower_2sigma'], df['upper_2sigma'], alpha=0.2)
plt.legend()

Точки вне голубой области — candidates for investigation.

Правила Nelson

Одна точка за 2σ — уже signal. Но есть и более тонкие patterns:

Правило 1: одна точка за 3σ от среднего. Сильно аномальное событие.

Правило 2: 9 точек подряд с одной стороны от среднего. Это тренд, не просто шум.

Правило 3: 6 точек подряд растут или падают. Монотонный тренд.

Правило 4: 14 точек подряд чередуются вверх/вниз. Нестандартное поведение.

Nelson Rules — набор из 8 правил. В продуктовой аналитике обычно используют первые 3-4.

Прокачать тему на реальных задачах удобно в боте @kariernik_bot — база вопросов собрана с собеседований в Яндексе, Авито, Ozon, Тинькофф.

Day-of-week effect

Метрики часто имеют weekly pattern. DAU в пятницу выше понедельника просто потому, что такая природа поведения пользователей.

Сравнение «сегодня vs вчера» некорректно, если вчера была суббота, а сегодня понедельник.

Решение — сравнение year-over-year или week-over-week с учётом same day of week:

df['week_ago'] = df['dau'].shift(7)
df['yoy'] = df['dau'].shift(365)

df['wow_change'] = (df['dau'] - df['week_ago']) / df['week_ago'] * 100

WoW изменение на 5% — более надёжный signal, чем просто абсолютное число.

Сезонность

Для месячных и квартальных метрик важна годовая сезонность. Retention в августе (отпуска) всегда ниже, GMV в декабре (пик) всегда выше.

Без учёта сезонности легко испугаться от летнего «провала» или обрадоваться декабрьскому «росту».

Подходы:

YoY сравнение — базовый. «GMV в апреле вырос на 20% YoY» — значимо. «Вырос на 20% MoM» — может быть сезонность.

Декомпозиция ряда через statsmodels. Разделяет тренд, сезонность и шум. Показывает «очищенную» динамику.

Prophet от Meta — более продвинутый инструмент. Автоматически учитывает сезонности и праздники.

Статистические тесты

Для формальной оценки «изменилось ли что-то» используют тесты.

T-test сравнивает среднее двух периодов. Подходит, если данные примерно нормальны и выборка достаточная.

Mann-Whitney U — непараметрическая альтернатива. Робастен к выбросам.

Change point detection — алгоритмы (Bayesian change point, PELT) автоматически ищут точки изменения в ряду.

В продуктовой аналитике часто достаточно простого t-test:

from scipy.stats import ttest_ind

before = df[df['date'] < '2026-04-01']['dau']
after = df[df['date'] >= '2026-04-01']['dau']

t, p = ttest_ind(before, after)
print(f'p = {p}')

Если p < 0.05, изменение значимо, не шум.

Многократные сравнения

Опасность: если вы сравниваете 10 метрик, одна из них случайно окажется «значимой» (при α=0.05 — 40% вероятность хотя бы одного false positive).

Защита — Bonferroni correction или BH. Делите α на количество тестов. Для 10 метрик порог не 0.05, а 0.005.

Или pre-register: заранее решайте, какие метрики главные, и смотрите только на них.

На собесе такие штуки часто спрашивают. Быстрый способ довести до автоматизма — тренажёр в Telegram с задачами из реальных интервью.

Практический чеклист

Когда видите изменение метрики:

  1. Это внутри 2σ от historical? Если да — скорее всего шум.
  2. Сравнение корректное? С учётом day-of-week, сезонности?
  3. Статистический тест что говорит? p-value < 0.05?
  4. Есть ли plausible explanation? Релиз, событие, внешний фактор?
  5. Продолжается ли тренд в следующие дни?

Если 3+ из этих вопросов дают «это реально» — signal. Иначе — шум.

Интуиция и данные

Опытный аналитик имеет интуицию про свои метрики. Знает normal range DAU, типичную сезонность, обычную reaction на события.

Но интуиция ошибается. Всегда подтверждайте статистикой. Говоря «мне кажется, упало», вы не помогаете команде. «Упало на 2σ от нормы, вне ожидаемого диапазона» — совсем другое.

Читайте также

FAQ

2σ или 3σ?

2σ — стандарт для early warning. 3σ — для alerts, которые должны быть точно реальными.

Что если std меняется со временем?

Используйте rolling std (окно 30-90 дней), а не глобальное.

Как быстро реагировать?

На метрики уровня CEO — немедленно. На второстепенные — еженедельно. Не все аномалии требуют срочного action.

Всегда ли изменение — плохо?

Нет. Сигнал может быть хорошим (рост) или плохим (падение). Но и то и другое заслуживает investigation.