rolling() в Pandas — скользящие окна

Коротко

rolling() — метод для вычислений в скользящем окне: скользящее среднее, скользящая сумма, стандартное отклонение за последние N дней. Основной инструмент для сглаживания временных рядов и анализа трендов. Аналитику rolling нужен для дашбордов (7-дневное среднее DAU), поиска аномалий и расчёта скользящих метрик. Аналог оконных функций в SQL.

Базовое использование

import pandas as pd

df = pd.DataFrame({
    'date': pd.date_range('2025-01-01', periods=10),
    'revenue': [100, 120, 90, 150, 200, 180, 160, 210, 190, 230]
})

# Скользящее среднее за 3 дня
df['ma_3'] = df['revenue'].rolling(3).mean()

# Скользящая сумма за 7 дней
df['sum_7'] = df['revenue'].rolling(7).sum()
date revenue ma_3 sum_7
01-01 100 NaN NaN
01-02 120 NaN NaN
01-03 90 103.3 NaN
01-04 150 120.0 NaN
01-05 200 146.7 NaN
01-06 180 176.7 NaN
01-07 160 180.0 1000
01-08 210 183.3 1110
01-09 190 186.7 1190
01-10 230 210.0 1320

Первые (window-1) строк — NaN, потому что окно ещё не заполнено.

Параметры rolling

# Размер окна
df['revenue'].rolling(window=7).mean()

# Минимальное количество значений в окне
df['revenue'].rolling(7, min_periods=1).mean()  # NaN только если 0 значений

# Центрирование окна
df['revenue'].rolling(7, center=True).mean()  # текущее значение в центре окна

# Закрытие окна
df['revenue'].rolling(7, closed='left').mean()  # не включает текущую строку

min_periods=1 — вычислять, даже если окно не полностью заполнено. Полезно для первых дней данных.

Доступные агрегации

s = df['revenue']

s.rolling(7).mean()    # среднее
s.rolling(7).sum()     # сумма
s.rolling(7).std()     # стандартное отклонение
s.rolling(7).min()     # минимум
s.rolling(7).max()     # максимум
s.rolling(7).median()  # медиана
s.rolling(7).count()   # количество непустых
s.rolling(7).var()     # дисперсия
s.rolling(7).quantile(0.9)  # 90-й перцентиль

# Произвольная функция
s.rolling(7).apply(lambda x: x.max() - x.min())  # размах

Практические примеры

7-дневное скользящее среднее DAU

# Ежедневные метрики
daily = pd.DataFrame({
    'date': pd.date_range('2025-01-01', periods=90),
    'dau': [1500 + i * 10 + (-1)**i * 200 for i in range(90)]
})

daily['dau_ma7'] = daily['dau'].rolling(7, min_periods=1).mean().round(0)

# Визуализация
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 5))
plt.plot(daily['date'], daily['dau'], alpha=0.3, label='DAU')
plt.plot(daily['date'], daily['dau_ma7'], linewidth=2, label='7-дн. среднее')
plt.title('DAU со скользящим средним')
plt.legend()
plt.show()

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

Скользящая конверсия

df['visitors_7d'] = df['visitors'].rolling(7).sum()
df['purchases_7d'] = df['purchases'].rolling(7).sum()
df['conversion_7d'] = df['purchases_7d'] / df['visitors_7d'] * 100

7-дневная конверсия стабильнее дневной — меньше колебаний из-за маленькой выборки.

Обнаружение аномалий

# Bollinger Bands: среднее ± 2 стандартных отклонения
df['ma'] = df['revenue'].rolling(14).mean()
df['std'] = df['revenue'].rolling(14).std()
df['upper'] = df['ma'] + 2 * df['std']
df['lower'] = df['ma'] - 2 * df['std']

# Аномалии — точки за пределами полос
anomalies = df[(df['revenue'] > df['upper']) | (df['revenue'] < df['lower'])]

Если метрика вышла за 2σ от скользящего среднего — вероятно, аномалия. Подробнее о выбросах.

Скользящий retention

# Retention за скользящее 7-дневное окно
df['new_users_7d'] = df['new_users'].rolling(7).sum()
df['returned_7d'] = df['returned_day1'].rolling(7).sum()
df['retention_7d'] = df['returned_7d'] / df['new_users_7d'] * 100

expanding — нарастающее окно

# expanding: окно от начала до текущей строки
df['cumulative_revenue'] = df['revenue'].expanding().sum()
df['cumulative_avg'] = df['revenue'].expanding().mean()
df['running_max'] = df['revenue'].expanding().max()

expanding = rolling с окном от первой строки до текущей. Аналог SUM() OVER (ORDER BY date ROWS UNBOUNDED PRECEDING) в SQL.

ewm — экспоненциальное сглаживание

# EWM: недавние значения имеют больший вес
df['ema_7'] = df['revenue'].ewm(span=7).mean()

EWM (exponentially weighted moving average) — альтернатива rolling. Последние значения весят больше → EMA быстрее реагирует на изменения, чем простое скользящее среднее.

rolling по группам

# Скользящее среднее по каждому пользователю
df['user_ma3'] = (
    df.sort_values('date')
    .groupby('user_id')['revenue']
    .transform(lambda x: x.rolling(3, min_periods=1).mean())
)

groupby().transform() — применяет rolling внутри каждой группы. Аналог OVER (PARTITION BY user_id ORDER BY date) в SQL.

Аналог в SQL

-- Скользящее среднее за 7 дней (PostgreSQL)
SELECT
    order_date,
    revenue,
    AVG(revenue) OVER (
        ORDER BY order_date
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    ) AS ma_7
FROM daily_revenue;

-- Нарастающая сумма (expanding)
SELECT
    order_date,
    revenue,
    SUM(revenue) OVER (
        ORDER BY order_date
        ROWS UNBOUNDED PRECEDING
    ) AS cumulative
FROM daily_revenue;
Pandas SQL
rolling(7).mean() AVG() OVER (ROWS 6 PRECEDING)
rolling(7).sum() SUM() OVER (ROWS 6 PRECEDING)
expanding().sum() SUM() OVER (ROWS UNBOUNDED PRECEDING)
groupby().transform(rolling) OVER (PARTITION BY ... ORDER BY ...)

Типичные ошибки

NaN в начале. Первые (window-1) значений — NaN. Используйте min_periods=1 или .fillna().

Данные не отсортированы. rolling работает по порядку строк. Если данные не отсортированы по дате — результат бессмысленный. Всегда: df.sort_values('date') перед rolling.

rolling по календарным дням. rolling(7) — 7 строк, не 7 дней. Если есть пропуски дат, используйте rolling('7D') с DatetimeIndex.

df = df.set_index('date')
df['ma_7d'] = df['revenue'].rolling('7D').mean()  # 7 календарных дней

Вопросы с собеседований

-- Зачем нужно скользящее среднее? -- Сглаживает шум в данных и показывает тренд. 7-дневное MA убирает эффект дня недели. 30-дневное — сезонные колебания.

-- Чем rolling отличается от expanding? -- rolling — фиксированное окно (последние N строк). expanding — растущее окно (от начала до текущей строки). rolling → скользящее среднее, expanding → нарастающая сумма.

-- Аналог rolling в SQL? -- Оконные функции с frame: AVG(col) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW).


Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.

FAQ

Какой размер окна выбрать?

7 дней — убирает эффект дня недели. 14/30 дней — для более гладкого тренда. Для SLA-метрик — 1 час или 1 день. Зависит от частоты данных и цели анализа.

rolling vs resample?

rolling — скользящее окно фиксированного размера. resample — агрегация по фиксированным периодам (неделя, месяц). rolling('7D').mean() ≠ resample('W').mean(). Первый — скользящий, второй — фиксированные недели.

Как тренироваться

rolling — must-have для аналитических дашбордов. Задачи на pandas и SQL — в тренажёре Карьерник. Больше вопросов — в разделе с примерами.