Анализ временных рядов — что нужно знать аналитику
Что такое временные ряды
Временной ряд — это последовательность значений, упорядоченных по времени. DAU по дням, выручка по неделям, конверсия по месяцам — всё это временные ряды. Аналитик работает с ними постоянно, даже если не называет это «анализом временных рядов».
Ключевое отличие от обычных данных: порядок имеет значение. Если перемешать строки в таблице пользователей — ничего не изменится. Если перемешать строки временного ряда — данные потеряют смысл.
Компоненты временного ряда
Любой временной ряд можно разложить на компоненты:
Тренд (trend) — долгосрочное направление: рост, падение или стагнация. DAU растёт на 5% в месяц — это тренд.
Сезонность (seasonality) — повторяющиеся паттерны с фиксированным периодом. Каждый понедельник DAU падает, а в пятницу — растёт. Каждый декабрь выручка выше из-за праздников. Период может быть дневным, недельным, месячным, годовым.
Остаток (residual / noise) — всё, что не объясняется трендом и сезонностью. Случайные колебания, разовые акции, баги.
Формально:
- Аддитивная модель: Y = Тренд + Сезонность + Остаток
- Мультипликативная модель: Y = Тренд x Сезонность x Остаток
Аддитивная подходит, когда амплитуда сезонности постоянна. Мультипликативная — когда амплитуда растёт вместе с уровнем ряда (чем выше выручка, тем больше сезонные колебания в абсолютных числах).
Декомпозиция в Python
Python с библиотекой statsmodels делает декомпозицию в три строки:
import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
import matplotlib.pyplot as plt
# Генерируем пример: DAU с трендом и недельной сезонностью
dates = pd.date_range("2025-01-01", periods=180, freq="D")
trend = pd.Series(range(180)) * 10 + 5000
seasonality = pd.Series([300, -200, -100, 0, 100, 400, -500] * 26)[:180]
noise = pd.Series(np.random.normal(0, 100, 180))
dau = trend + seasonality + noise
df = pd.DataFrame({"date": dates, "dau": dau}).set_index("date")
# Декомпозиция
result = seasonal_decompose(df["dau"], model="additive", period=7)
result.plot()
plt.tight_layout()
plt.savefig("decomposition.png")На графике увидите четыре компоненты: оригинальный ряд, тренд, сезонность и остаток. Это первый шаг любого анализа временного ряда — понять структуру.
Скользящее среднее
Скользящее среднее (moving average) — простейший способ сгладить ряд и увидеть тренд. Для каждой точки берёте среднее за окно (например, 7 дней) и заменяете ей исходное значение.
В SQL скользящее среднее считается через оконные функции:
SELECT
DATE,
dau,
AVG(dau) OVER (
ORDER BY DATE
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS dau_ma_7d
FROM daily_metrics
ORDER BY DATE;ROWS BETWEEN 6 PRECEDING AND CURRENT ROW — это окно в 7 дней (текущий + 6 предыдущих).
Для недельной сезонности — берите окно 7. Для месячной — 30. Для годовой — 365. Главное, чтобы окно было кратно периоду сезонности — тогда сезонные колебания «усреднятся».
Обнаружение аномалий
Аналитик часто отвечает на вопрос: «А это нормально или нет?». DAU упал на 15% — это сезонное падение или баг?
Простой подход — правило сигм: если значение отклоняется от скользящего среднего больше чем на 2-3 стандартных отклонения — это аномалия.
WITH stats AS (
SELECT
DATE,
dau,
AVG(dau) OVER (
ORDER BY DATE
ROWS BETWEEN 27 PRECEDING AND CURRENT ROW
) AS ma_28d,
STDDEV(dau) OVER (
ORDER BY DATE
ROWS BETWEEN 27 PRECEDING AND CURRENT ROW
) AS std_28d
FROM daily_metrics
)
SELECT
DATE,
dau,
ma_28d,
CASE
WHEN ABS(dau - ma_28d) > 3 * std_28d THEN 'anomaly'
ELSE 'normal'
END AS status
FROM stats
ORDER BY DATE;На практике этого достаточно для 80% случаев. Более продвинутые методы — Isolation Forest, Prophet от Meta, алгоритмы на основе STL-декомпозиции.
SQL для временных рядов
Оконные функции — ваш главный инструмент. Помимо скользящего среднего:
LAG — сравнение с предыдущим периодом:
SELECT
DATE,
revenue,
LAG(revenue, 7) OVER (ORDER BY DATE) AS revenue_7d_ago,
revenue - LAG(revenue, 7) OVER (ORDER BY DATE) AS wow_change
FROM daily_metrics;Кумулятивная сумма (running total):
SELECT
DATE,
revenue,
SUM(revenue) OVER (ORDER BY DATE) AS cumulative_revenue
FROM daily_metrics;Процент роста week-over-week:
SELECT
DATE,
revenue,
ROUND(100.0 * (revenue - LAG(revenue, 7) OVER (ORDER BY DATE))
/ NULLIF(LAG(revenue, 7) OVER (ORDER BY DATE), 0), 1) AS wow_pct
FROM daily_metrics;Если вы строите аналитику временных рядов — визуализация обязательна. Числа в таблице не покажут паттерн, а линейный график покажет.
Прогнозирование: базовые подходы
На собеседованиях вряд ли попросят строить ARIMA, но знать основы полезно:
- Наивный прогноз: значение = прошлый период (или прошлый год для сезонных данных). Удивительно часто работает не хуже сложных моделей.
- Экспоненциальное сглаживание: как скользящее среднее, но недавние наблюдения имеют больший вес.
- Prophet: библиотека от Meta для прогнозирования с трендом, сезонностью и праздниками. Простой API, хорошо работает из коробки.
- ARIMA / SARIMA: классические статистические модели. Мощные, но требуют подбора параметров и стационарности ряда.
Для аналитика важнее уметь анализировать и интерпретировать временные ряды, чем строить сложные модели прогнозирования.
Вопросы с собеседований
— Из каких компонент состоит временной ряд? — Тренд (долгосрочное направление), сезонность (повторяющиеся паттерны с фиксированным периодом) и остаток (шум, случайные колебания). Модель может быть аддитивной (компоненты складываются) или мультипликативной (умножаются).
— Как бы вы определили, что падение метрики — аномалия, а не сезонность? — Посчитал бы скользящее среднее и стандартное отклонение за предыдущий период. Если текущее значение отклоняется от MA больше чем на 2-3 сигмы — скорее аномалия. Также сравнил бы с аналогичным периодом прошлого года — если паттерн повторяется ежегодно, это сезонность.
— Как в SQL посчитать week-over-week изменение?
— Используя LAG с окном 7: LAG(metric, 7) OVER (ORDER BY date). Разность текущего и прошлого значения — абсолютное изменение, отношение разности к прошлому значению — процентное.
— Когда использовать аддитивную, а когда мультипликативную модель? — Аддитивную — когда амплитуда сезонности постоянна (сезонные колебания +-1000 независимо от уровня). Мультипликативную — когда амплитуда растёт пропорционально уровню ряда (декабрь всегда +30% от текущего уровня, а не +1000 в абсолюте).
FAQ
Что такое декомпозиция временного ряда?
Декомпозиция — это разложение ряда на составляющие: тренд, сезонность и остаток. Позволяет отделить долгосрочные изменения от циклических паттернов и шума. В Python делается через seasonal_decompose из statsmodels — достаточно указать период сезонности.
Какие оконные функции SQL нужны для анализа временных рядов?
Основные: LAG/LEAD (сравнение с предыдущим/следующим периодом), AVG/SUM с ROWS BETWEEN (скользящее среднее, кумулятивная сумма), STDDEV (для обнаружения аномалий). Этих функций достаточно для 90% задач аналитика по временным рядам.
Как выбрать размер окна для скользящего среднего?
Окно должно быть кратно периоду сезонности. Для дневных данных с недельной сезонностью — 7 дней. Для месячных данных с годовой сезонностью — 12 месяцев. Чем больше окно, тем сильнее сглаживание, но тем больше запаздывание: тренд «реагирует» на изменения с задержкой.
Потренируйте вопросы по SQL и аналитике — откройте тренажёр. 1500+ вопросов для собеседования аналитика. Бесплатно.