Нашли ошибку?
продвинутыйpandas 3.0.6

Окна: rolling, expanding, ewm

Скользящее среднее, максимум «на текущий момент» и экспоненциальное сглаживание временного ряда.

Нужно знать:date_range и временной индексОписательная статистика

Содержание статьи

Коротко

  • rolling(n) — окно из n последних строк; дальше агрегация: .mean(), .sum(), .max(), .agg([...]). Первые n − 1 значений — NaN.
  • min_periods — сколько значений достаточно для расчёта; center=True — окно вокруг точки, а не до неё.
  • rolling("7D") при датах в индексе — окно по времени: учитываются строки за последние 7 дней, даже если дней с данными меньше.
  • expanding() — окно от начала до текущей строки: максимум или среднее «на сегодняшний день».
  • ewm(span=…) — экспоненциальное сглаживание: недавние значения весят больше.

Синтаксис

s.rolling(window, min_periods=None, center=False).mean()
s.rolling("7D").sum()            # окно по времени
s.expanding(min_periods=1).max()
s.ewm(span=None, alpha=None, adjust=True).mean()
df.groupby("key")["col"].rolling(3).mean()

Параметры

ПараметрЧто делаетПо умолчанию
windowразмер окна: число строк или длительность вроде "7D", "12h"обязательный
min_periodsминимум значений в окне для результатаразмер окна
centerокно с центром в текущей строкеFalse
span / alphaв ewm: скорость забывания; alpha = 2 / (span + 1)обязательный
adjustв ewm: False — рекуррентная формула s_t = α·x_t + (1 − α)·s_{t−1}True

Примеры

Данные для примеровиспользуются во всех примерах ниже
days = pd.date_range("2024-03-01", periods=8, freq="D")
visits = pd.Series([120, 135, 128, 150, 162, 90, 85, 140], index=days, name="visits")
Скользящее среднее
visits.rolling(3).mean().round(1)                 # среднее за 3 дня; первые 2 — NaN
Вывод
2024-03-01      NaN
2024-03-02      NaN
2024-03-03    127.7
2024-03-04    137.7
2024-03-05    146.7
2024-03-06    134.0
2024-03-07    112.3
2024-03-08    105.0
Freq: D, Name: visits, dtype: float64
Без пропусков в начале
visits.rolling(3, min_periods=1).mean().round(1)  # считать, даже если данных меньше окна
Вывод
2024-03-01    120.0
2024-03-02    127.5
2024-03-03    127.7
2024-03-04    137.7
2024-03-05    146.7
2024-03-06    134.0
2024-03-07    112.3
2024-03-08    105.0
Freq: D, Name: visits, dtype: float64
Окно вокруг точки
visits.rolling(3, center=True).mean().round(1)    # окно вокруг дня, а не до него
Вывод
2024-03-01      NaN
2024-03-02    127.7
2024-03-03    137.7
2024-03-04    146.7
2024-03-05    134.0
2024-03-06    112.3
2024-03-07    105.0
2024-03-08      NaN
Freq: D, Name: visits, dtype: float64
Окно по времени
gaps = visits.drop(visits.index[[2, 3]])           # два дня без данных
gaps.rolling("3D").sum()                          # окно по времени, а не по числу строк
Вывод
2024-03-01    120.0
2024-03-02    255.0
2024-03-05    162.0
2024-03-06    252.0
2024-03-07    337.0
2024-03-08    315.0
Name: visits, dtype: float64
С начала до текущей строки
pd.DataFrame({"visits": visits, "max_so_far": visits.expanding().max(), "mean_so_far": visits.expanding().mean().round(1)})
Вывод
            visits  max_so_far  mean_so_far
2024-03-01     120       120.0        120.0
2024-03-02     135       135.0        127.5
2024-03-03     128       135.0        127.7
2024-03-04     150       150.0        133.2
2024-03-05     162       162.0        139.0
2024-03-06      90       162.0        130.8
2024-03-07      85       162.0        124.3
2024-03-08     140       162.0        126.2
Экспоненциальное сглаживание
visits.ewm(span=3, adjust=False).mean().round(1)  # недавние дни весят больше
Вывод
2024-03-01    120.0
2024-03-02    127.5
2024-03-03    127.8
2024-03-04    138.9
2024-03-05    150.4
2024-03-06    120.2
2024-03-07    102.6
2024-03-08    121.3
Freq: D, Name: visits, dtype: float64
Несколько функций
visits.rolling(3).agg(["min", "max"])
Вывод
              min    max
2024-03-01    NaN    NaN
2024-03-02    NaN    NaN
2024-03-03  120.0  135.0
2024-03-04  128.0  150.0
2024-03-05  128.0  162.0
2024-03-06   90.0  162.0
2024-03-07   85.0  162.0
2024-03-08   85.0  140.0

Подводные камни

Окна через границы групп

Если в одной таблице несколько рядов, простое rolling смешает их. Группируйте перед окном — результат получит MultiIndex (группа, исходная метка).

rolling по группам
sales = pd.DataFrame({
    "shop": ["A", "A", "A", "B", "B", "B"],
    "cups": [10, 20, 30, 5, 15, 25],
})
sales.groupby("shop")["cups"].rolling(2).mean()   # окна не переходят через границу групп
Вывод
shop
A     0     NaN
      1    15.0
      2    25.0
B     3     NaN
      4    10.0
      5    20.0
Name: cups, dtype: float64

Окно по строкам при пропусках в датах

rolling(3) берёт 3 строки, даже если между ними неделя. Для «последних 3 дней» нужен rolling("3D") и даты в индексе, как в примере выше.