Приведение к стационарности: разности и логарифм

Осваиваем два главных инструмента стабилизации ряда: разности убирают тренд, логарифм усмиряет растущую амплитуду.

Дифференцирование — замена ряда на ряд его приращений (x_t - x_{t-1}); оно убирает тренд и часто делает ряд стационарным.

Зачем преобразовывать ряд

Если ADF сказал «нестационарен», ряд нельзя подавать в ARIMA как есть. Его приводят к стационарному виду двумя приёмами: разности убирают тренд (и сезонные разности — сезонность), а логарифм стабилизирует дисперсию у мультипликативных рядов. Часто применяют оба: сначала лог, потом разность.

За этой технической формулировкой стоит очень понятная бизнес-логика. Когда продажи растут из месяца в месяц, аналитику почти никогда не интересен сам уровень — интересен прирост: на сколько мы выросли по сравнению с прошлым месяцем, ускоряемся мы или замедляемся. Дифференцирование как раз и переводит разговор с языка «сколько всего» на язык «насколько изменилось», и именно в этом языке ряд оказывается стационарным. А логарифм отвечает на другую типичную проблему: у растущего бизнеса колебания тоже растут — сезонный всплеск на ранний миллион выручки и на поздние десять миллионов выглядит как ±10%, но в абсолютных числах различается в десять раз. Логарифм уравнивает эти проценты, превращая «качели, что раскачиваются всё сильнее» в ровную полосу.

Обычное дифференцирование

Первая разность превращает растущий ряд в ряд приращений, у которого среднее уже не уходит.

x = [100, 110, 121, 133, 146, 161]   # растёт примерно на 10%
d1 = [x[i]-x[i-1] for i in range(1, len(x))]
print("Исходный:", x)
print("1-я разность:", d1)
print("Среднее разностей:", round(sum(d1)/len(d1), 1))

Вывод:

Исходный: [100, 110, 121, 133, 146, 161]
1-я разность: [10, 11, 12, 13, 15]
Среднее разностей: 12.2

Тренд из «уровня» ушёл: вместо растущих чисел мы получили ряд приращений вокруг постоянного значения. Число выполненных дифференцирований — это параметр d в ARIMA.

Обратите внимание на цену: разность всегда короче исходного ряда на один элемент — у самой первой точки нет предшественника, из которого можно вычесть. На длинной истории потеря одного-двух наблюдений несущественна, но на коротком ряде каждый отброшенный отсчёт ощутим, и это ещё одна причина не дифференцировать сверх необходимого. Заметьте также, что приращения здесь не идеально постоянны (10, 11, 12, 13, 15) — они слегка подрастают, потому что исходный рост был не линейным, а примерно процентным. Это намёк: чисто разностью такой ряд до конца не выпрямить, и тут на сцену выходит логарифм.

Логарифм против растущей амплитуды

Когда колебания растут вместе с уровнем, логарифм сжимает их к постоянному размаху.

import math
mult = [100, 120, 90, 200, 240, 180, 400, 480, 360]  # амплитуда растёт
logged = [round(math.log(v), 3) for v in mult]
print("Лог-ряд:", logged)
# разброс соседних разностей в лог-шкале стабилен
d = [round(logged[i]-logged[i-1], 3) for i in range(1, len(logged))]
print("Разности лога:", d)

Вывод:

Лог-ряд: [4.605, 4.787, 4.5, 5.298, 5.481, 5.193, 5.991, 6.174, 5.886]
Разности лога: [0.182, -0.287, 0.798, 0.183, -0.288, 0.798, 0.183, -0.288]

В лог-шкале сезонный паттерн повторяется почти точь-в-точь (примерно 0.18, -0.29, 0.80 снова и снова), хотя в исходных числах амплитуда утраивалась. Это и есть превращение мультипликативной структуры в аддитивную.

Стоит проговорить, почему так получается. Если значения умножаются на одни и те же коэффициенты (×1.2, ×0.75, ×2.2 в каждом «сезоне»), то после логарифма умножение становится сложением: log(a·b) = log(a) + log(b). Поэтому повторяющийся мультипликативный узор превращается в повторяющийся аддитивный сдвиг, не зависящий от того, на каком уровне мы находимся — отсюда и одинаковые числа в разностях лога. У этого приёма есть и приятный побочный эффект для интерпретации: разность логарифмов приближённо равна относительному приросту, так что значение 0.182 читается примерно как «+18% за шаг». Поэтому в финансах и аналитике лог-разности — это, по сути, лог-доходности, понятная всем мера темпа.

Сезонное дифференцирование

Если период сезонности равен m, берут разность x_t - x_{t-m}. Она убирает повторяющийся паттерн: «сколько прибавилось по сравнению с тем же днём прошлой недели».

Выбор m диктуется природой данных, а не вкусом: для дневных продаж с недельным циклом m = 7, для месячных данных с годовой сезонностью m = 12, для почасового трафика с суточным ритмом m = 24. Сравнивая точку с её «прошлогодним близнецом», мы вычитаем именно ту часть колебаний, что объясняется календарём, и оставляем чистую динамику. Сезонную и обычную разность нередко комбинируют: сначала снимают сезонность, затем оставшийся тренд — так получаются сезонные модели вида SARIMA с двумя параметрами дифференцирования, обычным d и сезонным D.

Как работает под капотом

Дифференцирование — дискретный аналог производной. Линейный тренд (степень 1) убирается одной разностью, квадратичный — двумя. Но переусердствовать опасно: лишнее дифференцирование вносит искусственную отрицательную автокорреляцию и раздувает дисперсию. Поэтому d редко бывает больше 2. Логарифм же действует на дисперсию: производная log(x) равна 1/x, поэтому большие значения «сжимаются» сильнее малых.

Связь с производной даёт удобную проверку «достаточности». Подобно тому как первая производная многочлена понижает его степень на единицу, первая разность гасит линейный тренд, вторая — параболический, и так далее. Если после одной разности ряд всё ещё явно ползёт, берут вторую; если же и одна сделала его «плоским», вторая уже не нужна и только навредит. Хорошее эмпирическое правило: дифференцируйте, пока ADF не начнёт уверенно отвергать нестационарность, и остановитесь на этом — минимальное d, которое сработало, почти всегда и есть правильное. Логарифм и разность при этом отвечают за разные болезни (дисперсия против тренда), поэтому их не выбирают «или-или», а назначают по симптомам, и нередко применяют последовательно.

Частые ошибки

  • Дифференцировать «на всякий случай» лишний раз — добавляется ложная автокорреляция.
  • Логарифмировать ряд с нулями или отрицательными значениями (log не определён) без сдвига.
  • Забыть обратное преобразование: прогноз в разностях/логах нужно вернуть в исходную шкалу.
  • Снимать сезонность обычной разностью вместо сезонной — недельный или годовой узор так не уберёшь.
  • Применять логарифм там, где разброс на самом деле постоянен: преобразование без нужды только усложняет интерпретацию.

Итоги

  • Разности убирают тренд (обычные) и сезонность (сезонные), задают параметр d.
  • Логарифм стабилизирует растущую дисперсию, превращая мультипликативность в аддитивность.
  • Не переусердствуйте с d и не забывайте обратное преобразование прогноза.
  • Период сезонной разности m диктуется данными (7, 12, 24…), а не выбирается произвольно.
  • Логарифм и разность лечат разные болезни — дисперсию и тренд — и часто применяются вместе.
Проверьте себя
1. Что делает первая разность ряда (x_t - x_{t-1})?
AУсиливает тренд
BУбирает линейный тренд, оставляя ряд приращений
CДобавляет сезонность
DЛогарифмирует значения
2. Для чего применяют логарифмирование ряда перед моделированием?
AЧтобы убрать тренд
BЧтобы стабилизировать растущую дисперсию (мультипликативность → аддитивность)
CЧтобы добавить сезонность
DЧтобы ускорить ADF