Описательная статистика ряда

Считаем числовые характеристики ряда и учимся видеть, что они сами по себе меняются во времени.

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

Зачем считать статистики по окну

Глобальное среднее по всему ряду часто бессмысленно: если продажи выросли вдвое за год, среднее «по всему» не описывает ни начало, ни конец. Гораздо информативнее скользящие характеристики — они показывают, как уровень и разброс эволюционируют. Растущее скользящее среднее — это тренд. Растущий скользящий разброс — намёк на мультипликативность или нестационарность.

Представьте, что вы отвечаете за прогноз трафика растущего сервиса. Руководитель спрашивает: «Сколько у нас в среднем запросов в секунду?». Ответ «по всей истории — 400» бесполезен и даже вреден: год назад было 100, сейчас 800, а среднее 400 не описывает ни одного реального момента. Полезный ответ опирается на скользящее среднее последних недель — оно показывает текущий уровень, на который и нужно закладывать инфраструктуру. Скользящие статистики переводят разговор из плоскости «какой ряд в целом» в плоскость «какой ряд сейчас и куда он движется» — а именно это интересует бизнес.

Скользящий разброс не менее важен. Допустим, среднее число продаж стабильно, но разброс от недели к неделе растёт. Это значит, что планировать запасы становится труднее: тот же средний спрос теперь приходит всё более рваными порциями. Растущая волатильность — самостоятельный сигнал риска, который глобальная дисперсия полностью скрывает, размазывая его по всей истории.

Скользящее среднее и размах

Посчитаем среднее в окне ширины 3 и заодно размах (max - min) в том же окне.

x = [10, 12, 11, 20, 22, 21, 35, 36, 34]
w = 3
for i in range(w - 1, len(x)):
    win = x[i-w+1:i+1]
    avg = sum(win) / w
    rng = max(win) - min(win)
    print(f"i={i} окно={win} среднее={avg:.1f} размах={rng}")

Вывод:

i=2 окно=[10, 12, 11] среднее=11.0 размах=2
i=3 окно=[12, 11, 20] среднее=14.3 размах=9
i=4 окно=[11, 20, 22] среднее=17.7 размах=11
i=5 окно=[20, 22, 21] среднее=21.0 размах=2
i=6 окно=[22, 21, 35] среднее=26.0 размах=14
i=7 окно=[21, 35, 36] среднее=30.7 размах=15
i=8 окно=[35, 36, 34] среднее=35.0 размах=2

Скользящее среднее уверенно растёт с 11 до 35 — это тренд. А размах скачет в моменты «ступенек» — там, где ряд резко переходит на новый уровень.

Присмотритесь к ширине окна и началу цикла. Цикл стартует не с i=0, а с i = w - 1 = 2, потому что раньше окно из трёх элементов просто не наберётся — первые w-1 точек остаются без значения. Это типичный «эффект краёв» скользящих статистик: чем шире окно, тем длиннее обрезанный хвост в начале ряда. Размах внутри окна работает как простой детектор разладки: пока ряд держится на одном уровне, в окно попадают похожие числа и размах мал (2 на ровных участках); как только в окно заезжает «ступенька», размах подскакивает (9, 11, 14, 15). По этим всплескам легко найти моменты, где ряд сменил режим.

Стандартное отклонение через stdlib

Модуль statistics даёт готовые функции. Сравним разброс в первой и второй половине ряда.

import statistics
x = [10, 12, 11, 20, 22, 21, 35, 36, 34]
half = len(x) // 2
print("σ первой половины:", round(statistics.pstdev(x[:half]), 2))
print("σ второй половины:", round(statistics.pstdev(x[half:]), 2))
print("σ всего ряда:", round(statistics.pstdev(x), 2))

Вывод:

σ первой половины: 3.96
σ второй половины: 6.65
σ всего ряда: 9.88

Разброс по всему ряду (9.88) сильно больше, чем внутри половин: это потому, что большую часть «разброса» создаёт тренд, а не локальная случайность. Важный урок: глобальные статистики смешивают тренд и шум.

Этот эффект стоит прочувствовать до конца, потому что он встречается постоянно. Стандартное отклонение измеряет, насколько точки разбросаны вокруг своего среднего. Когда мы берём среднее по всему растущему ряду, оно оказывается где-то посередине между низким началом и высоким концом — и каждая точка отстоит от него далеко просто потому, что ряд движется, а не потому, что он шумный. Внутри же каждой половины тренд почти не успевает развернуться, поэтому отклонение там отражает настоящий локальный шум (3.96 и 6.65). Практический вывод: если хотите измерить именно волатильность, сначала удалите тренд (например продифференцируйте ряд или вычтите скользящее среднее), а уже потом считайте σ. Иначе вы будете измерять не шум, а наклон.

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

Скользящее окно — это свёртка ряда с прямоугольным ядром: каждая выходная точка есть среднее w входных. Сложность наивной реализации O(n·w), но её можно ускорить до O(n), поддерживая бегущую сумму: при сдвиге окна прибавляем новый элемент и вычитаем выпавший. Этот приём — основа эффективных скользящих статистик.

Тот же трюк с инкрементальным пересчётом работает и для дисперсии, только аккуратнее. Можно держать одновременно бегущую сумму значений и бегущую сумму их квадратов; тогда дисперсию в окне получают по формуле «среднее квадратов минус квадрат среднего». На практике эту наивную формулу избегают из-за потери точности при вычитании близких больших чисел и применяют численно устойчивые схемы (алгоритм Уэлфорда и его скользящие варианты). Для размаха (max и min) бегущего пересчёта за O(1) уже не выходит так же просто — выпавший элемент мог быть как раз максимумом, и тогда максимум приходится искать заново; для этого используют специальные структуры вроде монотонной очереди. Знать про эти детали полезно: на длинных рядах разница между O(n) и O(n·w) — это разница между мгновенным дашбордом и минутами ожидания.

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

  • Описывать растущий ряд одним глобальным средним — оно не отражает ни начало, ни конец.
  • Путать рост разброса из-за тренда с ростом из-за настоящей волатильности.
  • Брать слишком широкое окно и сглаживать как раз тот эффект, который искали.
  • Забывать про обрезанный хвост в начале ряда: первые w-1 точек скользящей статистики не имеют значения.
  • Считать σ на ряде с трендом и принимать наклон за шум — сначала тренд надо удалить.

Итоги

  • Скользящие статистики показывают эволюцию уровня и разброса во времени.
  • Растущее скользящее среднее — это тренд; растущий разброс — намёк на нестационарность.
  • Глобальные среднее и σ смешивают тренд с шумом и часто вводят в заблуждение.
  • Ширина окна — компромисс: узкое реагирует на шум, широкое запаздывает и сглаживает искомый эффект.
  • Скользящие статистики можно считать за O(n) бегущей суммой — это важно на больших данных.
Проверьте себя
1. Почему стандартное отклонение по всему растущему ряду часто больше, чем внутри его половин?
AИз-за ошибки округления
BБольшую часть разброса создаёт тренд, а не локальный шум
CПотому что точек больше
Dstatistics считает неправильно
2. Как ускорить скользящее среднее с O(n·w) до O(n)?
AИспользовать рекурсию
BПоддерживать бегущую сумму: прибавлять новый и вычитать выпавший элемент
CУвеличить окно
DОтсортировать ряд