Мира · маркетолог магазина настолок «Кубик»
В декабре у нас был рекорд, а в январе заказов на 41% меньше. Мы теряем покупателей? И ещё все говорят, что летом настолки не покупают. Это правда или отговорка для плохих месяцев?
Мира смотрит на два соседних месяца и делает вывод о бизнесе. Так делают почти все, и почти всегда это ошибка. Заказы по дням или месяцам это временной ряд, и у него свои правила чтения. В уроке про ловушки мы чистили данные. Сегодня данные чистые, но врать может само сравнение.
База boardshop: заказы «Кубика» с января 2025 по июнь 2026, полтора года. Заказ считаем без отменённых, status <> 'cancelled'.
Море у берега
Стоишь на пирсе и смотришь на воду. Волны бьют то выше, то ниже, каждые несколько секунд. Под ними за шесть часов поднимается прилив. А летом и зимой у моря разный характер. Если по двум волнам решить, что море уходит, ошибёшься: просто одна волна была выше.
У ряда те же три слоя:
- тренд, прилив: магазин растёт или усыхает;
- сезонность, характер моря по временам года: декабрь, лето, выходные;
- шум, отдельные волны: в один день 14 заказов, в соседний 32.
month | orders | к прошлому месяцу | к тому же месяцу 2025
------------+--------+-------------------+-----------------------
2025-01-01 | 123 | |
2025-07-01 | 257 | −0,8% |
2025-08-01 | 231 | −10,1% |
2025-11-01 | 590 | +55,3% |
2025-12-01 | 720 | +22,0% |
2026-01-01 | 427 | −40,7% | +247,2%
2026-06-01 | 397 | −12,6% | +53,3%
Январь 2026 на 40,7% ниже декабря и в 3,5 раза выше января 2025. Оба числа верны. Первое меряет сезон, второе рост магазина. Задача урока: научиться разделять эти слои запросом.
Скользящее среднее: убираем волны
Начнём с шума. Заказы по дням скачут: 16 декабря 32 заказа, 18 декабря 14. Сгладить ряд помогает скользящее среднее: среднее за день и шесть предыдущих. Рамку окна ты уже знаешь по уроку 40.
Одна тонкость. Окно ROWS BETWEEN 6 PRECEDING AND CURRENT ROW считает строки, а не дни. Если в какой-то день заказов не было, его строки в группировке нет, и «семь строк» растянутся на восемь дней. Поэтому сначала строим календарь через generate_series:
WITH days AS (
SELECT generate_series(min(created_at)::date, max(created_at)::date, interval '1 day')::date AS day
FROM orders
), daily AS (
SELECT d.day, count(o.id) AS orders
FROM days d
LEFT JOIN orders o ON o.created_at::date = d.day AND o.status <> 'cancelled'
GROUP BY d.day
)
SELECT day, orders,
round(avg(orders) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW), 2) AS ma7
FROM daily
ORDER BY day;
day | orders | ma7
------------+--------+-------
2025-12-16 | 32 | 20.86
2025-12-17 | 23 | 21.43
2025-12-18 | 14 | 21.29
...
2026-01-04 | 15 | 18.71
2026-01-05 | 7 | 16.86
2026-01-08 | 9 | 13.57
2026-01-11 | 13 | 10.29
Сырые числа прыгают вдвое за два дня, а скользящее среднее держится около 21 весь декабрь и сползает к 10-13 после 7 января. В «Кубике» день без заказов был один, 15 января 2025, но запрос без календаря на нём уже ошибся бы.
Семь дней берут не случайно: в окне всегда ровно по одному понедельнику, вторнику и так далее, и недельная сезонность в нём гасится сама.
урок из курса
Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.