Владелец «Кофейной лавки» уверен: в холод чай покупают охотнее. Если это так, по прогнозу погоды можно
планировать закупки. Проверим на данных одного города — возьмём продажи в Москве и московскую погоду.
В проекте один новый метод — corr, остальное — приёмы этого модуля.
import pandas as pd
orders = pd.read_csv("data/shop_orders.csv", parse_dates=["date"])
orders["revenue"] = orders["price"] * orders["quantity"]
msk = orders[orders["city"] == "Москва"]
weather = pd.read_csv("data/weather.csv", parse_dates=["date"])
moscow = weather[weather["city"] == "Москва"].set_index("date")
print(len(msk), len(moscow))
moscow.head(3)948 365
| city | temp_min | temp_max | precip_mm | wind_ms | |
|---|---|---|---|---|---|
| date | |||||
| 2025-01-01 | Москва | -9.9 | -3.2 | 0.0 | 1.9 |
| 2025-01-02 | Москва | -9.7 | -3.2 | 0.0 | 2.3 |
| 2025-01-03 | Москва | -9.0 | -3.7 | 0.0 | 5.2 |
Шаг 1. Продажи по дням
По таблице msk соберите два ряда, приведённых к календарю calendar (в дни без продаж — ноль):
total — выручка за день, tea — выручка за день только от товаров категории «Чай».
msk.groupby("date")["revenue"].sum().reindex(calendar, fill_value=0).msk[msk["category"] == "Чай"], дальше то же самое.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
calendar = pd.date_range("2025-01-01", "2025-12-31")
total = ...
tea = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
calendar = pd.date_range("2025-01-01", "2025-12-31")
total = msk.groupby("date")["revenue"].sum().reindex(calendar, fill_value=0)
tea = msk[msk["category"] == "Чай"].groupby("date")["revenue"].sum().reindex(calendar, fill_value=0)Шаг 2. Погода и продажи рядом
У рядов total и tea и у таблицы moscow одинаковый индекс — дни года. Значит, их можно поставить рядом,
и pandas сам сопоставит строки по датам.
Соберите таблицу data из четырёх столбцов: temp — temp_max из moscow, rain — precip_mm из moscow,
tea и total — ряды из шага 1. В gaps сохраните число пропусков в каждом столбце.
pd.DataFrame({"temp": moscow["temp_max"], "rain": moscow["precip_mm"], "tea": tea, "total": total}).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
data = ...
gaps = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
data = pd.DataFrame({"temp": moscow["temp_max"], "rain": moscow["precip_mm"], "tea": tea, "total": total})
gaps = data.isna().sum()Шаг 3. Как измерить связь
«Чем холоднее, тем больше чая» — это утверждение о связи двух величин. Её силу измеряет коэффициент корреляции — число от −1 до 1:
- около 1 — величины растут вместе;
- около −1 — одна растёт, другая падает;
- около 0 — линейной связи нет.
Метод corr считает его для двух Series:
height = pd.Series([150, 160, 170, 180, 190])
print(round(height.corr(pd.Series([50, 58, 66, 75, 84])), 3))
print(round(height.corr(pd.Series([9, 8, 6, 5, 3])), 3))
print(round(height.corr(pd.Series([4, 9, 2, 8, 5])), 3))1.0 -0.993 0.055
Первая пара растёт вместе — почти 1. Вторая — в противоположные стороны, почти −1. Третья — случайные числа, и коэффициент близок к нулю.
Сохраните в r_day корреляцию температуры (temp) и выручки от чая (tea) по дням. В r_rain — корреляцию
осадков (rain) и общей выручки (total).
data["temp"].corr(data["tea"]).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
r_day = ...
r_rain = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
r_day = data["temp"].corr(data["tea"])
r_rain = data["rain"].corr(data["total"])Минус есть — в тёплые дни чая продают меньше, — но связь слабая: −0.17. А между осадками и выручкой
линейной связи не видно: 0.06. (В столбце rain один пропуск — corr такую пару значений просто не учитывает.)
Выходит, владелец ошибался?
Не спешите. В половине дней чай в Москве вообще не покупали — дневные продажи очень «шумные»: всё решает случай. Шум заглушает закономерность.
Шаг 4. От дней к неделям
Случайности разных дней гасят друг друга, если сложить дни в недели.
Сохраните в weekly таблицу по полным неделям: temp — средняя температура недели, tea — выручка
от чая за неделю. В r_week сохраните корреляцию этих двух столбцов.
data.resample("W").agg(temp=("temp", "mean"), tea=("tea", "sum"))..iloc[1:-1].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
weekly = ...
r_week = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
weekly = data.resample("W").agg(temp=("temp", "mean"), tea=("tea", "sum")).iloc[1:-1]
r_week = weekly["temp"].corr(weekly["tea"])Шаг 5. От недель к месяцам
Сохраните в monthly такую же таблицу по месяцам, округлённую до одного знака. В r_month — корреляцию
temp и tea, в tea_peak — номер месяца с наибольшей выручкой от чая.
"ME"; неполных месяцев нет.monthly["tea"].idxmax().month.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
monthly = ...
r_month = ...
tea_peak = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
monthly = data.resample("ME").agg(temp=("temp", "mean"), tea=("tea", "sum")).round(1)
r_month = monthly["temp"].corr(monthly["tea"])
tea_peak = monthly["tea"].idxmax().monthmonthly| temp | tea | |
|---|---|---|
| 2025-01-31 | -3.8 | 27400 |
| 2025-02-28 | -4.7 | 26300 |
| 2025-03-31 | 1.6 | 17910 |
| 2025-04-30 | 10.9 | 11600 |
| 2025-05-31 | 15.5 | 8630 |
| 2025-06-30 | 20.1 | 18940 |
| 2025-07-31 | 21.7 | 10110 |
| 2025-08-31 | 21.6 | 19580 |
| 2025-09-30 | 16.0 | 17040 |
| 2025-10-31 | 10.2 | 10450 |
| 2025-11-30 | 2.1 | 27610 |
| 2025-12-31 | -2.6 | 35120 |
Теперь связь видна и без коэффициента: в морозные месяцы чая продают на 26–35 тысяч, в тёплые — на 9–20.
Шаг 6. Морозные, прохладные и тёплые дни
Другой способ увидеть ту же связь — без корреляции. Разделим дни на три группы по температуре и сравним средние.
Добавьте в data столбец band — группу дня по temp: до 0 — «мороз», до 15 — «прохладно», выше — «тепло»
(границы [-50, 0, 15, 50]). Сохраните в tea_by_band среднюю дневную выручку от чая в каждой группе,
округлённую до одного знака.
pd.cut(data["temp"], bins=[-50, 0, 15, 50], labels=["мороз", "прохладно", "тепло"]).data.groupby("band")["tea"].mean().round(1).Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
# добавьте в data столбец band
tea_by_band = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
data["band"] = pd.cut(data["temp"], bins=[-50, 0, 15, 50], labels=["мороз", "прохладно", "тепло"])
tea_by_band = data.groupby("band")["tea"].mean().round(1)Шаг 7. Сезон или погода?
Остался главный вопрос владельца: поможет ли прогноз погоды? Чай хорошо продаётся в холодные месяцы —
но, может быть, дело просто во времени года? Проверим, что происходит внутри месяца: вычтем из температуры
и из продаж чая их средние за свой месяц (это transform из модуля о группировке) и посмотрим, связаны ли
отклонения.
data["month"] = data.index.month
temp_dev = data["temp"] - data.groupby("month")["temp"].transform("mean")
tea_dev = data["tea"] - data.groupby("month")["tea"].transform("mean")
print(round(temp_dev.corr(tea_dev), 2))0.05
Почти ноль. День, который холоднее обычного для своего месяца, продаж чая не прибавляет.
Итог
print("Корреляция температуры и продаж чая:")
print(" по дням ", round(r_day, 2))
print(" по неделям", round(r_week, 2))
print(" по месяцам", round(r_month, 2))
print("Осадки и выручка:", round(r_rain, 2))
print(tea_by_band)Корреляция температуры и продаж чая: по дням -0.17 по неделям -0.41 по месяцам -0.7 Осадки и выручка: 0.06 band мороз 913.7 прохладно 544.3 тепло 489.4 Name: tea, dtype: float64
Выводы
- В холодные месяцы чая покупают больше. В морозный день — в среднем на 914 ₽, в тёплый — на 489 ₽, почти вдвое меньше.
- Связь тем заметнее, чем крупнее период: −0.17 по дням, −0.41 по неделям, −0.70 по месяцам. Данные те же — укрупнение убирает дневной шум и оставляет сезон, а связь, как показал шаг 7, сезонная.
- Но эта связь — сезонная. Внутри месяца её нет (0.05): погода конкретного дня сверх времени года ничего не добавляет. Планировать закупки чая стоит по календарю, а не по прогнозу погоды.
- Между осадками и выручкой линейной связи в данных не видно: 0.06.
И два предостережения о корреляции — наш проект показал оба.
Корреляция — не причина. Температура и продажи чая меняются вместе, потому что обе зависят от времени года: в декабре холодно — и в декабре же покупают подарки к Новому году. Мы увидели это, когда убрали сезон: связь исчезла. Общая причина, которая двигает обе величины, — самое частое объяснение «красивой» корреляции.
Ноль — не значит «связи нет». Коэффициент ловит только связь вида «больше — больше» или «больше — меньше». Если продажи высоки и в мороз, и в жару, а посередине низкие, корреляция будет около нуля — хотя связь есть. Поэтому про осадки мы говорим осторожно: «линейной связи не видно».