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'] * 1007-дневная конверсия стабильнее дневной — меньше колебаний из-за маленькой выборки.
Обнаружение аномалий
# 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'] * 100expanding — нарастающее окно
# 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 — в тренажёре Карьерник. Больше вопросов — в разделе с примерами.