Как проверить прогноз продаж до закупки товара
Коротко
- Сравните прогноз поставщика с простым правилом на одних будущих датах, не подмешивая их в историю.
- Ниже — готовый учебный расчёт: таблицы продаж, два прогноза и программа, которая проверяет входы и считает ошибку.
- Продажи и оба прогноза вымышлены: на них учимся проверять расчёт, а не оцениваем точность ИИ.
Что понадобится
Выгрузка ежедневных продаж одного товара, сведения о наличии и прогноз с датой его выпуска. У поставщика заранее запросите горизонт, единицу измерения, время доступности признаков и версию модели. Одинаковые колонки ещё не означают одинаковые условия: прогноз, обновлённый вчера, нельзя честно сравнить с прогнозом месячной давности.
Скачайте учебный
набор: CSV и Python, ZIP. В файле supplied-forecast.csv
лежит прогноз условного поставщика. Программа работает без сети и
дополнительных пакетов; можно ограничиться CSV и пересчитать формулы в
таблице.
Официальный пример scikit-learn показывает, как случайное перемешивание временных наблюдений даёт слишком оптимистичную оценку модели. Мы взяли из него принцип временного порядка, но не переносили результаты велопроката на торговлю. Источник: пример с временными признаками.
Сам пример не требует подписки или API. Для бюджета подготовки задайте свои часы и ставку: например, условный один час при ставке 1 000 ₽/ч даст 1 000 ₽ затрат времени. Это допущение, а не замер; подготовка реальных данных и интеграция считаются отдельно.
1. Отделите будущие дни
В нашем примере один SKU, DEMO-001. Учебная история
содержит 84 последовательных дня: первые 56 оставлены для подготовки
прогноза, последние 28 — для контроля. Считаем, что прогноз выпущен
вечером после закрытия 26 июля 2026 года: на эту минуту доступны только
продажи по этот день включительно. Даты и строки — в учебном
наборе.
| Часть примера | Где в ряду | Для чего |
|---|---|---|
| История, 56 дней | Начало ряда | Подготовить прогноз |
| Последняя известная неделя | Последние семь дней истории | Построить простой ориентир |
| Контроль, 28 дней | Сразу после истории | Посчитать ошибку |
Это схема собственного учебного примера; точные календарные даты есть в CSV. Исходные CSV и протокол.
После границы историю не дополняем. Для реального запуска сохраните исходные выгрузки и прогноз до появления контрольных продаж; не позволяйте поставщику незаметно заменить файл. Если вы уже видели эти продажи, такой разбор полезен для проверки процедуры, но не заменяет следующую проверку на ещё не наступившем периоде.
2. Постройте простой ориентир
Для первого контрольного понедельника берём продажи последнего известного понедельника. Для второго, третьего и четвёртого — того же известного понедельника, а не фактические продажи предыдущей контрольной недели. Так повторяется последняя доступная неделя. Это редакционное правило для фиксированного горизонта, а не ежедневно обновляемый прогноз.
В seasonal-naive.csv есть source_date:
дата, откуда взято значение. Проверяющая программа требует, чтобы она не
заходила за границу истории, совпадала с нужным днём последней недели, а
прогноз — с продажами того дня. Второй прогноз в нашем наборе просто
прибавляет к этому ориентиру четыре штуки. Код создания и проверки
примера.
Если вместо этого брать продажи ровно за семь дней до каждой целевой
даты, для 21 из 28 прогнозов потребуются факты, которых ещё не было в
момент выпуска. Такой файл нельзя сравнивать с прогнозом, выпущенным
сразу на весь месяц. При ежедневном пересчёте правило допустимо, но
тогда оба участника должны ежедневно выпускать прогноз на одинаковый
срок вперёд. Расчёт
нарушения — bad_rolling_lag7 в результатах набора.
3. Проверьте файлы перед подсчётом
Запустите из распакованной папки:
python3 evaluate.pyКоманда читает три CSV рядом со скриптом и перезаписывает сводку
расчёта и построчные ошибки в этой папке. Откройте
results.json: ожидается 26 включённых дней и суммы
абсолютных ошибок 79 и 33; затем откройте errors.csv для
сверки по датам. Ожидаемые выходы и
выполненный расчёт. Не подставляйте рабочие файлы магазина поверх
учебных: сначала сделайте отдельную копию и адаптируйте заданные в коде
SKU, даты и горизонт. Программа намеренно рассчитана на этот пример, это
не универсальная система прогнозирования.
Проверка останавливается, если отсутствует день истории или прогноза, дата повторяется, SKU не совпадает, горизонт отличается от разницы дат, дата выпуска сдвинулась, число отрицательное или не конечное. Для простого ориентира дополнительно проверяется происхождение каждого значения. Ошибка входов — причина исправить выгрузку, а не продолжать с меньшим числом строк. Реализованные проверки и намеренно испорченные входы.
В реальной выгрузке добавьте сведения о признаках: что было известно на момент выпуска, как учитывались будущие акции, цены и поставки. Проверка даты в CSV не доказывает, что поставщик не использовал будущие продажи внутри модели. Без снимка входов и описания подготовки данных этот вопрос остаётся открытым.
4. Не считайте отсутствие товара нулевым спросом
Для примера мы приняли правило: если товар не был доступен весь день,
такой день исключается из основной оценки обоих прогнозов. У нас это два
контрольных дня — поэтому знаменатель одинаков и равен 26. Продажи в эти
дни записаны нулём, но неизвестно, сколько люди купили бы при наличии.
Это допущение нашего расчёта, не единственный возможный способ учёта
дефицита. Маска
available и список исключений.
Если товар был доступен, но продаж не было, ноль остаётся в расчёте. Не заменяйте такой день пропуском и не делите процентную ошибку на искусственную единицу. Здесь мы вообще не используем MAPE: выбираем ошибку в штуках.
Отдельно сохраните долю исключённых дней, причины отсутствия и журнал поставок. Если дефицит частый, оценка только по дням наличия описывает ограниченную часть продаж: по ней нельзя объявлять точность прогноза всего спроса. Изменять маску после просмотра победителя нельзя.
5. Посчитайте ошибку в штуках
Для каждой включённой даты:
абсолютная ошибка = |прогноз − продажи|. Затем
MAE = сумма абсолютных ошибок / число включённых дней. Ещё
считаем смещение: сумма (прогноз − продажи) / число дней;
плюс означает завышение, минус — занижение. Смещение — дополнительный
редакционный расчёт; определение MAE
сверено с документацией scikit-learn.
Ниже результат учебного расчёта. Оба прогноза выпущены на одну дату, проверены на одних датах и одной маске. Значения округлены только для показа; CSV сохраняет построчный расчёт. Запись расчёта: протокол, исходники, сводка и построчные ошибки.
| Учебный прогноз | Дней в расчёте | Сумма ошибок, шт. | MAE, шт./день | Смещение, шт./день |
|---|---|---|---|---|
| Повтор последней известной недели | 26 | 79 | 3,04 | −3,04 |
| Вымышленный прогноз поставщика | 26 | 33 | 1,27 | +0,96 |
Источник всех ячеек: реально выполненный
учебный расчёт. Например, 79 / 26 = 3,038…, а
33 / 26 = 1,269…. Это не проценты и не доля правильно
угаданных дней.
В этом расчёте второй файл ближе к продажам по MAE. Простое правило «прибавить четыре» выбрано нами заранее, его происхождение открыто в коде. Проверка со всеми 28 днями тоже сохранена, но её ошибка относится к наблюдаемым продажам, а не к неизвестному спросу в дни дефицита. Полный результат с ограничениями.
Что сломает перемешивание дат
В отдельной демонстрации программа случайно делит те же 84 дня на 56
и 28 с seed=42. Получается, что 27 из 28 контрольных дней
идут раньше последнего дня «обучения». Так модель получила бы
возможность учиться на более поздних наблюдениях. Модель мы не
обучали, величину завышения точности не измеряли. Воспроизводимая
демонстрация — random_split_demo.
Сохранение временного порядка убирает именно эту ошибку процедуры, но не все утечки. Для более надёжной проверки повторите сравнение на нескольких последовательных периодах с разными сезонами и акциями, не меняя правила после просмотра результата. В документации TimeSeriesSplit объяснено, почему обычное разбиение не подходит, когда оно приводит к обучению на будущем и оценке на прошлом. Для нашего одного отложенного периода сам scikit-learn не нужен.
Как принять решение
До получения контрольных продаж зафиксируйте: какие даты и SKU сравниваете, когда выпускается прогноз, какие дни исключаются, какую ошибку допускаете и сколько стоят недозаказ и излишек. Универсального проходного MAE нет: одна и та же ошибка в штуках по-разному влияет на дешёвый расходник и дорогую технику.
Если поставщик не обошёл простой ориентир по заранее выбранному критерию, оснований покупать более сложный прогноз пока нет. Если обошёл — проверьте результат на следующих периодах и переведите разницу в стоимость решений. Из одной таблицы ошибок нельзя вывести объём закупки: нужны остаток, ожидаемые поставки, срок пополнения и запас на неопределённость.
Время подготовки сотрудником не замерялось. К затратам времени отдельно прибавьте стоимость самого прогноза и интеграции; окупаемость считайте по изменениям в закупках, а не по снижению MAE.
Как мы проверяли
Проверено: Python 3.14.4 и стандартная библиотека; расчёт выполнен без scikit-learn и подключения сервисов.
Редакция Кометы выполнила F01 локально в Python 3.14.4: создала учебные CSV, запустила расчёт и проверку испорченных входов. Девять намеренных дефектов были отвергнуты; отдельная проверка подтвердила, что доступный товар с нулевыми продажами остаётся в знаменателе. Все десять проверок прошли. Подробности, версии и ограничения — в записи F01 внутри набора.
Данные синтетические; один SKU, один период, без реального ML, API и коммерческих обещаний. Набор нужен для освоения процедуры. Для магазина потребуются свои выгрузки и отдельная оценка по товарам, горизонтам и условиям наличия.
Источники
- Учебный набор F01 — собственные вымышленные данные, код и выполненные результаты; не данные стороннего сервиса.
- scikit-learn: пример временных признаков — риск случайного перемешивания. Проверено 17.10.2026.
- scikit-learn: TimeSeriesSplit — обучение на прошлом и проверка на будущем. Проверено 17.10.2026.
- scikit-learn: mean_absolute_error — определение MAE. Проверено 17.10.2026.
Смежные задачи: почему скорость вычисления прогноза не равна его точности и как сравнивать бизнес-эффект ИИ на сопоставимых метриках.