Нашли ошибку?
Скачать чистый
урок 3 из 4≈ 20 минупражнения: 0 из 4

Окна и сдвиги

Скользящее окно rolling сглаживает случайные колебания; shift сдвигает ряд, чтобы сравнить день с тем же днём неделей раньше; diff и pct_change считают изменение и рост к прошлому периоду.

Python запустится при первом выполнении

Дневная выручка прыгает: сегодня 9 тысяч, завтра 18, послезавтра 4. За этими скачками трудно разглядеть, растут продажи или падают. resample из прошлого урока укрупняет данные — но вместе с шумом теряет и подробности: из 365 точек остаётся 12. Есть способ сгладить ряд, сохранив точку на каждый день.

Скользящее окно

Идея: для каждого дня взять не одно его значение, а итог по нескольким последним дням. Метод rolling(n) «прикладывает» к ряду окно из n значений и двигает его шаг за шагом.

[ ]Окно из трёх значений: сумма и среднее
import pandas as pd

steps = pd.Series([1, 2, 3, 4, 5, 6])
print(steps.rolling(3).sum().tolist())
print(steps.rolling(3).mean().tolist())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[nan, nan, 6.0, 9.0, 12.0, 15.0]
[nan, nan, 2.0, 3.0, 4.0, 5.0]

Третье значение суммы — 1 + 2 + 3 = 6, четвёртое — 2 + 3 + 4 = 9: окно сдвинулось на одно значение. Первые два результата — nan: окно из трёх значений там ещё не набралось.

Теперь выручка магазина по дням — ряд с полным календарём из прошлых уроков:

[ ]Первые восемь дней года
orders = pd.read_csv("data/shop_orders.csv", parse_dates=["date"])
orders["revenue"] = orders["price"] * orders["quantity"]
sales = orders.groupby("date")["revenue"].sum().reindex(pd.date_range("2025-01-01", "2025-12-31"), fill_value=0)
sales.head(8).tolist()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[9060, 10450, 18620, 4090, 8610, 14550, 5400, 13070]
[ ]Среднее за три дня
sales.rolling(3).mean().head(8).round(1).tolist()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[nan, nan, 12710.0, 11053.3, 10440.0, 9083.3, 9520.0, 11006.7]

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

Вопрос

Что выведет print(pd.Series([10, 20, 30, 40]).rolling(2).mean().tolist()[-1])?

[ ]Упражнение 1Сглаженная выручка решено

Сохраните в smooth скользящее среднее ряда sales с окном 7 дней. В n_empty — сколько в нём пропусков. В peak_day — день, на который приходится наибольшее значение smooth, в peak_value — само это значение.

smooth = ...
n_empty = ...
peak_day = ...
peak_value = ...

Лучшие семь дней подряд закончились 16 апреля: в среднем 14.3 тысячи в день. Это не календарная неделя из прошлого урока, а любое окно из семи дней — поэтому и дата другая.

Пояснение с ответом появится, когда решите упражнение «Сглаженная выручка».

Пропуски в начале можно убрать параметром min_periods — «считать, если в окне есть хотя бы столько значений»:

[ ]Неполное окно в начале
sales.rolling(7, min_periods=1).mean().head(3).round(1).tolist()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[9060.0, 9755.0, 12710.0]

Первое значение — один день, второе — среднее двух, третье — трёх. Это удобно для графика, но помните: первые точки посчитаны по меньшему числу дней и менее надёжны.

Сдвиг

Метод shift(n) сдвигает значения на n шагов вперёд, оставляя метки на месте. На каждой дате оказывается значение, которое было n дней назад:

[ ]Сегодня, вчера и завтра
print(sales.head(4).tolist())
print(sales.shift(1).head(4).tolist())
print(sales.shift(-1).head(4).tolist())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[9060, 10450, 18620, 4090]
[nan, 9060.0, 10450.0, 18620.0]
[10450.0, 18620.0, 4090.0, 8610.0]

shift(1) — «вчера»: у 2 января стоит выручка 1 января. shift(-1) сдвигает в обратную сторону — «завтра». В начале (или в конце) появляются пропуски: сдвигать туда нечего.

Сдвиг нужен, чтобы сравнивать ряд с самим собой в прошлом. Разность с прошлым значением встречается так часто, что для неё есть метод diff:

[ ]Изменение за день — двумя способами
print(sales.diff().head(4).tolist())
print((sales - sales.shift(1)).head(4).tolist())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[nan, 1390.0, 8170.0, -14530.0]
[nan, 1390.0, 8170.0, -14530.0]
[ ]Упражнение 2Лучше, чем неделю назад? решено

Сравнивать вторник с понедельником нечестно — у дней недели разный уровень продаж. Честнее сравнить с тем же днём недели неделей раньше. Сохраните в week_ago ряд sales, сдвинутый на 7 дней, в change — разность sales и week_ago. В better — число дней, когда выручка выше, чем неделей раньше. В better_share — долю таких дней среди дней, для которых сравнение возможно.

week_ago = ...
change = ...
better = ...
better_share = ...

Рост в процентах

Разность в рублях мало говорит без масштаба: плюс 50 тысяч — это много для июля и немного для декабря. Метод pct_change считает относительное изменение: текущее значение, делённое на предыдущее, минус один.

[ ]Рост на маленьком ряду — и выручка первых трёх месяцев
print(pd.Series([100, 120, 90]).pct_change().round(2).tolist())
monthly = sales.resample("ME").sum()
print(monthly.head(3).tolist())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[nan, 0.2, -0.25]
[318940, 250430, 297780]

0.2 значит «на 20 % больше предыдущего значения» (120 против 100), −0.25 — «на 25 % меньше» (90 против 120). У первого значения сравнивать не с чем — пропуск. Во второй строке — начало ряда monthly: выручка по месяцам.

[ ]Упражнение 3Динамика по месяцам решено

Сохраните в growth рост месячной выручки monthly к прошлому месяцу, округлённый до трёх знаков. В best_growth — номер месяца с наибольшим ростом, в falls — сколько раз за год выручка упала по сравнению с прошлым месяцем.

growth = ...
best_growth = ...
falls = ...

Окна побольше

Размер окна подбирают под задачу. Неделя убирает недельный ритм, месяц — показывает сезон:

[ ]Температура в Москве: среднее за 7 дней
weather = pd.read_csv("data/weather.csv", parse_dates=["date"])
temp = weather[weather["city"] == "Москва"].set_index("date")["temp_max"]
temp.rolling(7).mean().round(2).iloc[6:9]
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
date
2025-01-07   -5.76
2025-01-08   -5.90
2025-01-09   -5.84
Name: temp_max, dtype: float64
[ ]Упражнение 4Волны тепла и холода решено

В temp — дневной максимум температуры в Москве. Сохраните в month_avg скользящее среднее с окном 30 дней, в warmest_end — дату, которой заканчиваются самые тёплые 30 дней года. В jump сохраните изменение температуры за сутки, в biggest_drop — день самого резкого похолодания.

month_avg = ...
warmest_end = ...
jump = ...
biggest_drop = ...

Итоги

  • rolling(n).mean() — скользящее среднее: итог по последним n значениям для каждой точки.
  • В начале ряда окно не набрано — NaN; min_periods разрешает неполное окно.
  • shift(n) — значение n шагов назад; diff() — разность с ним; pct_change() — относительный рост.
  • Сравнивайте с тем же днём недели: shift(7).
  • Окно считает строки — сначала восстановите полный календарь.

Подробнее в справочнике

Нашли ошибку?