Продажи в декабре всегда выше, чем в июле. Трафик на сайт проседает по выходным. Количество обращений в поддержку скачет каждый понедельник. Когда смотришь на такой график, возникает вопрос: это реальный рост, сезонный эффект или просто шум? Без ответа на него легко принять неверное решение - порадоваться «рекордному» декабрю, который на самом деле просто декабрь.
Разложение ряда на составляющие - тренд, сезонность и остаток - позволяет увидеть каждую из них отдельно. Это не абстрактная математика, а конкретный инструмент: понять, растет ли бизнес на самом деле, выделить аномалии, очистить данные перед прогнозированием.
В этой статье разберем, как работает декомпозиция временных рядов, чем отличаются основные подходы, и как провести анализ руками - в Excel и Python. Без формул ради формул, с акцентом на то, что делать с результатом.
Коротко:
- Временной ряд состоит из трех компонент: тренд (долгосрочное направление), сезонность (повторяющийся паттерн) и остаток (всё остальное, включая шум и аномалии).
- Аддитивная модель подходит, когда амплитуда сезонных колебаний не меняется со временем. Мультипликативная - когда колебания растут вместе с рядом.
- В Python удобнее всего использовать
seasonal_decomposeиз statsmodels или STL-декомпозицию для более гибкого анализа. - В Excel можно получить тренд через скользящее среднее, а сезонность - вручную через сезонные индексы.
- Главная польза - не сам график компонент, а интерпретация: растет ли метрика без учета сезонности, есть ли аномалии в остатке.
- Перед прогнозированием очищенный от сезонности ряд дает более точные модели.
Из чего состоит временной ряд
Любой временной ряд можно представить как сумму (или произведение) нескольких слоев.
Тренд - это долгосрочное направление движения. Выручка растет год к году, аудитория постепенно снижается, средний чек медленно увеличивается. Тренд виден, если убрать все краткосрочные колебания.
Сезонность - регулярно повторяющийся паттерн с фиксированным периодом. Еженедельный цикл (выходные vs будни), ежемесячный (конец месяца - всплеск), ежегодный (лето, праздники, отчетные периоды). Ключевое слово - регулярность. Если паттерн повторяется с одинаковой частотой, это сезонность.
Остаток (residual, или шум) - всё, что не объяснено трендом и сезонностью. Здесь живут аномалии, случайные события, ошибки данных. Если в остатке видна структура - значит, модель что-то не уловила.
Иногда выделяют еще цикличность - долгосрочные волны без фиксированного периода (экономические циклы, например). Но в большинстве бизнес-задач с ней не работают отдельно.
Аддитивная и мультипликативная модели
Прежде чем запускать любой инструмент, нужно выбрать тип модели. Это влияет на интерпретацию результата.
Аддитивная модель: Y = Тренд + Сезонность + Остаток
Подходит, когда сезонные колебания примерно одинаковы по амплитуде независимо от уровня ряда. Например, трафик всегда проседает на 500 визитов по выходным - и при общем уровне 5000, и при 10000.
Мультипликативная модель: Y = Тренд × Сезонность × Остаток
Подходит, когда амплитуда колебаний растет вместе с рядом. Если в низкий сезон продажи падают на 20%, то при выручке 1 млн это минус 200 тыс., а при 5 млн - уже минус 1 млн. Паттерн в процентах стабилен, но в абсолютных числах - нет.
Как выбрать: посмотрите на график. Если «зубцы» сезонности одинаковые по высоте - аддитивная. Если они растут вместе с рядом - мультипликативная. Можно также прологарифмировать ряд: если после этого амплитуда выровнялась, исходная модель была мультипликативной.
Как это работает: логика метода
Классический алгоритм разложения выглядит так:
- Выделить тренд. Обычно через скользящее среднее с окном, равным периоду сезонности. Для месячных данных с годовой сезонностью - скользящее среднее за 12 месяцев. Это сглаживает сезонные колебания и оставляет только долгосрочное движение.
- Убрать тренд из ряда. В аддитивной модели - вычесть, в мультипликативной - разделить. Получается «детрендированный» ряд.
- Оценить сезонность. Усреднить значения по каждому периоду (все январи, все понедельники и т.д.). Это и есть сезонный паттерн.
- Получить остаток. Убрать из детрендированного ряда сезонность. Что осталось - шум и аномалии.
Звучит просто, но на практике есть нюансы: что делать с пропусками, как обрабатывать края ряда, как выбрать ширину окна. Именно поэтому удобнее использовать готовые реализации.
Декомпозиция в Python: seasonal_decompose и STL
seasonal_decompose из statsmodels
Самый быстрый способ получить разложение - функция seasonal_decompose из библиотеки statsmodels. Она реализует классический метод на основе скользящего среднего.
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose
import matplotlib.pyplot as plt
# Загружаем данные
df = pd.read_csv('sales.csv', parse_dates=['date'], index_col='date')
# Декомпозиция
result = seasonal_decompose(
df['revenue'],
model='additive', # или 'multiplicative'
period=12 # период сезонности (12 для месячных данных)
)
# Визуализация
result.plot()
plt.tight_layout()
plt.show()
# Компоненты как отдельные Series
trend = result.trend
seasonal = result.seasonal
residual = result.resid
Функция возвращает объект с четырьмя атрибутами: observed (исходный ряд), trend, seasonal и resid. Каждый из них - обычный pandas Series, с которым можно работать дальше.
Важный момент: на краях ряда тренд будет содержать NaN. Это нормально - скользящее среднее не может посчитаться без достаточного количества точек с обеих сторон. Для ряда с периодом 12 потеряете по 6 точек с каждого конца.
STL-декомпозиция
STL (Seasonal and Trend decomposition using Loess) - более гибкий метод. Он лучше справляется с выбросами и позволяет настраивать степень сглаживания отдельно для тренда и сезонности.
from statsmodels.tsa.seasonal import STL
stl = STL(df['revenue'], period=12, robust=True)
result = stl.fit()
result.plot()
plt.show()
# Компоненты
trend = result.trend
seasonal = result.seasonal
residual = result.resid
Параметр robust=True делает метод устойчивым к выбросам - они меньше влияют на оценку тренда и сезонности. Это полезно, если в данных есть аномальные периоды (пандемия, акции, сбои).
| Параметр | seasonal_decompose | STL |
|---|---|---|
| Метод сглаживания | Скользящее среднее | Loess (локальная регрессия) |
| Устойчивость к выбросам | Низкая | Высокая (при robust=True) |
| Гибкость настройки | Минимальная | Высокая |
| Скорость работы | Быстрее | Чуть медленнее |
| Когда выбирать | Быстрый первичный анализ | Данные с выбросами, тонкая настройка |
Что делать с результатом
После получения компонент начинается самое интересное - интерпретация.
Смотрим на тренд. Растет ли он? Есть ли перелом? Если тренд плоский, а исходный ряд показывает рост - значит, рост объясняется только сезонностью, а не реальным развитием.
Анализируем сезонность. Какой месяц или день самый сильный? Насколько велика амплитуда? Если сезонная компонента составляет 30% от среднего значения ряда - это существенно и нужно учитывать при планировании.
Изучаем остаток. Если в нем видны крупные выбросы - это аномалии, которые стоит проверить. Если остаток не похож на белый шум (есть автокорреляция, структура) - модель не полностью объяснила данные.
Пример интерпретации: интернет-магазин видит рост выручки 15% год к году. После разложения оказывается, что тренд вырос только на 4%, а остальное - усиление сезонного пика в декабре. Вывод: органический рост слабее, чем казалось, и декабрьский всплеск стал больше (возможно, из-за акций). Это меняет оценку ситуации.
Декомпозиция в Excel: ручной подход
Excel не имеет встроенной функции для полного разложения, но базовый анализ можно сделать вручную. Это полезно, когда Python недоступен или нужно быстро показать результат нетехническому коллеге.
Шаг 1: Скользящее среднее для тренда
Для месячных данных с годовой сезонностью используйте скользящее среднее за 12 периодов. В Excel это делается через функцию СРЗНАЧ или через встроенный инструмент «Скользящее среднее» в надстройке «Анализ данных».
Формула для ячейки C7 (если данные в B2:B100, начало с января):
=СРЗНАЧ(B2:B13) - для первой точки скользящего среднего
=СРЗНАЧ(B3:B14) - сдвигаем окно на один период
Для чётного периода (12 месяцев) правильнее использовать центрированное скользящее среднее: среднее двух соседних 12-точечных средних. Это дает более точное выравнивание с исходным рядом.
Шаг 2: Сезонные индексы
Разделите исходные значения на тренд (мультипликативная модель) или вычтите тренд (аддитивная). Затем усредните все значения для каждого месяца по всем годам.
Например, для января: возьмите все значения (Исходный / Тренд) для всех январей и усредните. Получите сезонный индекс января. Если он равен 1.25 - январь в среднем на 25% выше тренда.
Шаг 3: Остаток
Разделите детрендированный ряд на сезонный индекс (мультипликативная) или вычтите сезонность (аддитивная). Что осталось - остаток.
Ограничение Excel: ручной подход трудоемок, легко ошибиться при сдвигах строк, а обновление при добавлении новых данных требует переделки формул. Для регулярного использования лучше автоматизировать в Python или хотя бы в VBA-макросе.
Вакансии для аналитиков
Как проверить качество разложения
Хорошее разложение - это не красивый график, а корректно выделенные компоненты. Вот на что смотреть:
- Остаток похож на белый шум. Нет видимых паттернов, нет автокорреляции. Проверить можно визуально или через тест Льюнга-Бокса (
acorr_ljungboxв statsmodels). - Сезонность стабильна. Если сезонный паттерн явно меняется год от года, классическая модель с фиксированной сезонностью не подходит. Нужна STL или более сложные методы.
- Тренд сглаженный. Если в тренде видны резкие скачки - скорее всего, период сезонности задан неверно или в данных есть структурный перелом.
- Сумма компонент воспроизводит исходный ряд. Для аддитивной модели: Тренд + Сезонность + Остаток = Исходный ряд. Если есть расхождение - ошибка в расчетах.
Типичные ошибки при работе с компонентами
Неверный период сезонности. Если у вас дневные данные с недельной сезонностью, период = 7. Если месячные с годовой - период = 12. Ошибка здесь ломает всё разложение. Перед запуском проверьте автокорреляционную функцию (ACF) - она покажет, на каком лаге есть значимые пики.
Применение аддитивной модели к мультипликативному ряду. Результат - остаток с явной структурой: в периоды высоких значений он положительный, в периоды низких - отрицательный. Если видите такой паттерн, переключитесь на мультипликативную модель или прологарифмируйте ряд.
Интерпретация остатка как «просто шума». Крупный выброс в остатке - это сигнал. Возможно, в этот период произошло что-то значимое: акция, сбой, внешнее событие. Не игнорируйте такие точки.
Использование разложения на коротком ряду. Для надежной оценки сезонности нужно минимум 2-3 полных цикла. Для годовой сезонности - минимум 2-3 года данных. На более коротком ряду результаты ненадежны.
Забыть про NaN на краях. После разложения тренд содержит пропуски на краях. Если вы используете очищенный ряд дальше (например, для прогноза), нужно явно обработать эти пропуски.
Практическое применение: зачем это нужно бизнесу
Разложение само по себе - не цель. Вот конкретные задачи, где оно помогает:
Оценка реального роста. Сравнивать декабрь с ноябрем бессмысленно без поправки на сезонность. Очищенный от сезонности ряд (trend + residual) показывает, есть ли органический рост.
Планирование и бюджетирование. Зная сезонные индексы, можно корректно распределить годовой план по месяцам. Если январь исторически на 20% ниже среднего, план января должен это учитывать.
Обнаружение аномалий. Крупные выбросы в остатке - кандидаты на проверку. Это могут быть ошибки данных, разовые акции или реальные события, которые нужно учесть в анализе.
Подготовка данных для прогнозирования. Многие модели (ARIMA, экспоненциальное сглаживание) работают лучше на стационарных рядах. Убрав тренд и сезонность, вы упрощаете задачу для модели.
Сравнение периодов. «Как мы выросли по сравнению с прошлым годом, исключая сезонный эффект?» - классический вопрос от руководства. Ответ дает именно тренд-компонента.
Чеклист: разложение временного ряда
- Проверить длину ряда: минимум 2-3 полных сезонных цикла
- Визуально оценить тип модели: аддитивная или мультипликативная
- Определить период сезонности через ACF или предметное знание
- Запустить разложение (seasonal_decompose или STL в Python; скользящее среднее + сезонные индексы в Excel)
- Проверить остаток: нет ли в нем структуры или крупных выбросов
- Убедиться, что сумма компонент воспроизводит исходный ряд
- Интерпретировать тренд: есть ли реальный рост без учета сезонности
- Проверить аномалии в остатке и выяснить их природу
- Задокументировать выбранные параметры (период, тип модели) для воспроизводимости
Когда стандартное разложение не работает
Классические методы хорошо справляются с регулярными паттернами, но есть ситуации, когда результат вводит в заблуждение.
Несколько наложенных циклов. Дневные данные могут содержать одновременно недельный и годовой паттерн. seasonal_decompose умеет работать только с одним периодом за раз. Если задать период 7, годовой цикл уйдет в остаток. Решение: либо агрегировать данные до нужного уровня, либо использовать MSTL (Multiple Seasonal-Trend decomposition using Loess) из statsmodels, который поддерживает несколько периодов одновременно.
Структурный перелом в тренде. Если в середине ряда произошло резкое изменение уровня или наклона (смена бизнес-модели, пандемия, крупное обновление продукта), скользящее среднее размажет этот перелом. Тренд будет выглядеть плавным там, где на самом деле произошел разрыв. В таких случаях разумнее разбить ряд на два отрезка и анализировать каждый отдельно, или использовать Prophet, который явно моделирует точки изменений.
Нерегулярные пропуски. Если в данных есть пропущенные периоды (праздники, технические сбои, отсутствие данных), скользящее среднее даст смещенный тренд. Перед разложением пропуски нужно либо заполнить (интерполяция, среднее соседних значений), либо явно обозначить и учесть при интерпретации.
Очень короткий или очень зашумленный ряд. На зашумленных данных остаток будет доминировать над трендом, и компоненты потеряют смысл. В этом случае стоит сначала сгладить ряд или агрегировать до более крупного периода, а потом уже делать разложение.
Как использовать компоненты для сравнения периодов
Один из самых частых запросов от бизнеса: «Мы выросли или нет?» Ответить на него честно без поправки на цикличность практически невозможно.
Рассмотрим типичный сценарий. Выручка в марте составила 4.2 млн, в феврале было 3.8 млн. Рост 10.5%. Хороший результат? Не факт: март исторически сильнее февраля просто потому, что в нем больше рабочих дней и он ближе к концу квартала. Если сезонный индекс марта равен 1.08, а февраля 0.97, то скорректированные значения почти одинаковы, и реального роста нет.
Правильный подход для сравнения: работать с очищенным рядом (тренд + остаток, без сезонной компоненты). Именно он отвечает на вопрос об органическом росте.
Практический прием: если нужно быстро ответить на вопрос «как мы растем», постройте два графика рядом: исходный ряд и ряд без сезонной компоненты. Второй покажет реальную динамику. Часто оказывается, что «рекордный» месяц на очищенном ряду выглядит скромнее, а «провальный» месяц в низкий период на самом деле был выше тренда.
Сравнение инструментов: что выбрать под задачу
Ниже краткое сравнение подходов, которые чаще всего используют аналитики в реальных проектах.
| Инструмент | Когда подходит | Ограничения |
|---|---|---|
| Excel (ручной) | Разовый анализ, нетехническая аудитория, нет доступа к Python | Трудоемко, сложно обновлять, нет устойчивости к выбросам |
| seasonal_decompose | Быстрый первичный анализ, чистые данные без выбросов | Один период, NaN на краях, слабая устойчивость к аномалиям |
| STL | Данные с выбросами, нужна гибкая настройка сглаживания | Только один период, чуть сложнее в настройке |
| MSTL | Несколько наложенных циклов (например, недельный + годовой) | Требует явного задания всех периодов |
| Prophet | Структурные переломы, изменяющаяся цикличность, праздники | Тяжелее в интерпретации, дольше обучается |
Что делать после разложения: следующие шаги
Разложение редко является конечной точкой анализа. Вот логичные следующие шаги в зависимости от задачи.
Если цель - прогноз. Используйте очищенный ряд (без сезонной компоненты) для построения модели. После получения прогноза добавьте обратно сезонный паттерн. Это стандартная схема для SARIMA и методов Хольта-Уинтерса.
Если цель - обнаружение аномалий. Сосредоточьтесь на остатке. Задайте пороговые значения: например, точки, где остаток превышает 2-3 стандартных отклонения, считаются аномальными. Проверьте каждую такую точку вручную - это почти всегда дает полезный инсайт.
Если цель - отчет для руководства. Покажите три вещи: исходный ряд, тренд и очищенный от цикличности ряд. Объясните разницу между «декабрь был рекордным» и «декабрь был выше тренда на X%». Второй формат намного информативнее для принятия решений.
Если цель - планирование бюджета. Возьмите сезонные индексы за последние 2-3 года, усредните их и примените к годовому плану. Это даст помесячное распределение, которое учитывает исторические паттерны. Не забудьте проверить, не изменилась ли структура циклов за последний год.
Как выбрать правильный период: частые ошибки и способы проверки
Один из самых распространенных источников проблем при разложении - неверно заданный период. Если период указан неточно, тренд получается смещенным, а паттерн уходит в остаток вместо нужной компоненты.
Несколько способов проверить период до запуска разложения:
- Автокорреляционная функция (ACF). Постройте ACF для исходного ряда. Значимые пики на лагах 7, 12, 52 и т.д. укажут на соответствующие периоды. В Python:
from statsmodels.graphics.tsaplots import plot_acf, затемplot_acf(df['value'], lags=40). - Предметное знание. Если вы работаете с розничными продажами, годовой цикл почти гарантирован. Для трафика сайта - недельный. Не игнорируйте контекст в пользу «чистой математики».
- Визуальный осмотр. Наложите несколько лет данных на один график по месяцам. Если кривые похожи по форме - паттерн есть и период очевиден.
- Спектральный анализ. Для сложных случаев используйте преобразование Фурье:
import numpy as np; np.fft.fft(df['value']). Пики в спектре соответствуют доминирующим частотам.
Если после разложения в остатке видна явная волнообразная структура с тем же ритмом, что и в исходном ряду - это верный признак, что период задан неверно. Попробуйте соседние значения (например, 11 или 13 вместо 12) и сравните остатки.
Интерпретация остатка: как отличить аномалию от шума
Остаток - самая информативная часть разложения для поиска нестандартных ситуаций. Но не каждый выброс в остатке требует внимания, и важно уметь их различать.
Практический подход к анализу остатка:
Шаг 1. Вычислите среднее и стандартное отклонение остатка (исключая NaN). В норме среднее близко к нулю.
Шаг 2. Отметьте точки, где остаток превышает 2 стандартных отклонения. Это кандидаты на проверку.
Шаг 3. Для каждой такой точки задайте вопрос: что происходило в этот период? Акция, сбой, внешнее событие, ошибка в данных?
Шаг 4. Если причина найдена и она разовая - точка является аномалией. Если объяснения нет - возможно, модель не учла какой-то паттерн.
Хороший остаток выглядит как случайный шум: нет видимых волн, нет нарастания дисперсии, нет группировки положительных или отрицательных значений в одном периоде. Если остаток выглядит иначе - стоит пересмотреть тип модели или метод разложения.
Формальная проверка на автокорреляцию в остатке - тест Льюнга-Бокса. Если p-value теста ниже 0.05, в остатке есть структура, которую модель не объяснила:
from statsmodels.stats.diagnostic import acorr_ljungbox
result_lb = acorr_ljungbox(residual.dropna(), lags=[10], return_df=True)
print(result_lb)
Что делать с данными, у которых нет четкого цикла
Не все временные ряды имеют выраженный повторяющийся паттерн. Иногда данные слишком зашумлены, период нестабилен или бизнес работает в нише без выраженной цикличности.
| Ситуация | Признак | Рекомендация |
|---|---|---|
| Слабый или отсутствующий паттерн | ACF не показывает значимых пиков | Сосредоточьтесь на тренде, не пытайтесь выделить цикличность принудительно |
| Нестабильный паттерн | Форма цикла меняется год от года | Используйте STL с низким параметром сглаживания или Prophet |
| Несколько наложенных циклов | Несколько пиков на ACF | Агрегируйте данные или используйте MSTL |
| Очень короткий ряд | Менее двух полных циклов | Откажитесь от разложения, работайте с исходным рядом и скользящим средним |
| Структурный перелом | Резкий скачок уровня в середине ряда | Разбейте ряд на два отрезка или используйте Prophet с changepoints |
Принудительное разложение на данных без четкого цикла дает бессмысленные компоненты. Лучше честно сказать, что паттерн не выражен, чем строить красивый график с артефактами.
Документирование и воспроизводимость анализа
Разложение временного ряда часто делается не один раз: данные обновляются, появляются новые периоды, коллеги хотят воспроизвести результат. Без документирования параметров анализ превращается в черный ящик.
Минимальный набор того, что стоит зафиксировать:
- Источник данных и дата выгрузки
- Тип модели (аддитивная или мультипликативная) и обоснование выбора
- Заданный период и способ его определения
- Метод разложения (seasonal_decompose, STL, ручной в Excel) и версия библиотеки
- Как обработаны пропуски и выбросы перед разложением
- Выводы по каждой компоненте: что наблюдается в тренде, каков масштаб паттерна, есть ли аномалии в остатке
Если анализ делается в Python, оформите его в Jupyter Notebook с комментариями к каждому шагу. Это занимает 10-15 минут, но экономит часы при повторном обращении к задаче через месяц.
FAQ
Чем декомпозиция временных рядов отличается от декомпозиции метрик?
Это разные техники с разными задачами. Декомпозиция метрик - структурное дробление показателя по формуле (выручка = трафик × конверсия × чек) для поиска причин изменения. Разложение временного ряда - математическое выделение тренда, сезонности и остатка из одного показателя во времени. Первое отвечает на вопрос «что именно изменилось», второе - «как меняется показатель во времени и что в этом изменении сезонное, а что реальное».
Что такое STL и чем он лучше классического метода?
STL (Seasonal and Trend decomposition using Loess) использует локальную регрессию вместо скользящего среднего. Это делает его устойчивым к выбросам и позволяет гибко настраивать степень сглаживания. Классический seasonal_decompose быстрее и проще, но плохо справляется с данными, где есть аномальные периоды или сезонность меняется со временем.
Как выбрать период сезонности?
Исходите из природы данных: дневные данные с недельным паттерном - период 7, месячные с годовым - период 12, квартальные - период 4. Если не уверены, постройте автокорреляционную функцию (ACF): значимые пики на определенных лагах укажут на период. В Python это делается через plot_acf из statsmodels.
Можно ли делать прогноз на основе компонент?
Да, это один из стандартных подходов. Тренд экстраполируется (линейно или через более сложную модель), сезонность переносится из прошлых периодов, остаток обычно считается нулевым или моделируется отдельно. Именно так работают методы Хольта-Уинтерса и ряд других моделей прогнозирования.
Что делать, если сезонность меняется год от года?
Классическая модель с фиксированной сезонностью не подойдет. Используйте STL с меньшим параметром сглаживания сезонности, или рассмотрите модели с динамической сезонностью - например, Prophet от Meta, который явно моделирует изменяющуюся сезонность.
Сколько данных нужно для надежного разложения?
Минимум - 2 полных сезонных цикла, но лучше 3 и больше. Для годовой сезонности это 2-3 года месячных данных. На более коротком ряду оценки будут нестабильными, особенно для тренда.
Итог
Разложение временного ряда - это способ увидеть данные честнее. Когда тренд, сезонность и остаток разделены, намного проще ответить на реальные бизнес-вопросы: растем ли мы на самом деле, что происходит в аномальные периоды, как корректно сравнивать разные сезоны.
Для большинства задач достаточно seasonal_decompose или STL из statsmodels - пяти строк кода, чтобы получить все компоненты. В Excel это займет больше времени, но базовый анализ доступен и без Python. Главное - не останавливаться на красивом графике, а идти дальше: интерпретировать тренд, проверять остаток и использовать результат для решений.