Как анализировать исторические данные маркетплейсов и не сойти с ума: грамотный расчет выручки

—

от автора

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

В статье не будет сырых простыней кода — только математика, архитектура, реальные проблемы на боевых данных и их решения.


Глава 1. Период наивности: Ozon, данные без истории и отзывы вместо продаж

Путь начинался с простой задачи: был массив данных с Ozon, сгруппированный по категориям. Данные представляли собой простой снимок текущего состояния карточек: название, текстовые характеристики, цена, рейтинг и число отзывов. Исторических данных не было в принципе. Не было временных рядов остатков, не было динамики цен. Ничего не было, только данные в один конкретный день. Но даже с таким набором можно работать.

Блочная архитектура анализа

Исходя из доступных данных и желаемого результата было решено разделить систему на модули:

  1. Сегментация и поиск аномалий: выделение ценовых сегментов и составление топа товаров в каждом сегменте.

  2. Анализ наименований: выявление слов-триггеров в заголовках карточек, коррелирующих с успехом товара.

  3. Анализ структуры продавцов: оценка монополизации ниши через классические экономические метрики.

  4. Оценка характеристик: легковесный ML.

Метрики концентрации: HHI и CR

Для оценки конкурентности мы использовали индекс Херфиндаля-Хиршмана (HHI) и коэффициент концентрации CR_k:

HHI = \sum_{i=1}^{n} s_i^2

где s_i — рыночная доля i-го продавца в процентах. Если рынок поделен поровну между 100 продавцами, HHI = 100  \times  1^2 = 100 (высокая конкуренция). Если один монополист держит 70%, а остальные 30 делят остаток, HHI \approx  4900 (жесткая монополия).

HHI родился как экономический инструмент для измерения концентрации, но его математическое ядро — мера неравномерности распределения долей. Поэтому он естественным образом переносится на любые задачи, где есть набор взаимно исключающих категорий и их доли. В нашем случае мы использовали HHI по сути, как готовый инструмент и обратились к его историческому назначению — определению неравномерности долей.

К нему добавили CR_k — кумулятивная доля k крупнейших игроков:

CR_k = \sum_{i=1}^{k} s_{(i)}

В чем была главная проблема?

Единственной доступной метрикой спроса выступало число отзывов (F).

Мы понимали, что отзывы — это прокси-метрика. Причем крайне инертная:

  • Карточка, продающаяся 4 года, накопила 5 000 отзывов, но за последний месяц могла не сделать ни одной продажи.

  • Новинка с быстрым взлетом имеет 15 отзывов и 2 000 заказов за неделю, но в аналитике по отзывам она выглядит как абсолютный аутсайдер.

В общем-то, даже так результаты нам показались удовлетворительными — бились со здравым смыслом и для нас это было достаточным показателем качества. Тем более, мы осознавали, что с ограниченным набором данных и отсутствием исторических данных не получится сделать что-то принципиально иное — данных просто недостаточно.

Так выглядит анализ наименований - размер окружности показывает эффективность слова-триггера (насколько сильно влияет слово на уровень спроса)

Так выглядит анализ наименований — размер окружности показывает эффективность слова-триггера (насколько сильно влияет слово на уровень спроса)

Глава 2. «Теперь-то мы посчитаем всё точно!»: расчет продаж по складским остаткам и ценам Wildberries

В какой-то момент все-таки появились исторические данные, правда на WB. Ежедневные снимки складов и цен Wildberries, казалось, что святой Грааль найден. Больше никаких косвенных метрик! Из самого важного в распоряжении были данные:

  • product_id (SKU);

  • wh (идентификатор склада: Коледино, Электросталь, Казань и т.д.);

  • qty (физический остаток товара на складе);

  • price_product (история цен);

  • changed_at (временная метка фиксации).

Наивная модель восстановления продаж

Логика казалась железобетонной. Для каждого товара j на складе w строим временной ряд остатков q_{j, w}(t).

Изменение остатка между соседними днями t-1 и t:

\Delta q = q(t) - q(t-1)

Интерпретация:

  • Если \Delta q < 0 — это продажа:

\text{sold\_qty}(t) = -\Delta q\text{revenue}(t) = \text{sold\_qty}(t) \times  \min(p(t-1), p(t))

  • Если \Delta q > 0 — это поставка:

\text{supply\_qty}(t) = \Delta q, \quad  \text{sold\_qty}(t) = 0

  • Если \Delta q = 0 — движения не было.

Казалось бы, просуммируй по всем складам и дням — получишь выручку ниши или товара. Мы написали код агрегации, запустили расчет и… на выходе получили абсолютный бред.


Глава 3. Жестокое столкновение с реальностью: Почему дельта остатков дает сбой

Первые же тесты на реальных категориях указали нам наше место.

Проблема №1: Дырчатый календарь и фантомные нули

Получить данные с миллионов карточек — процесс вероятностный:

  • Бывают дни, когда данные просто отсутствуют (по техническим или иным причинам просто нет данных за этот день/промежуток).

  • Бывают дни, когда конкретный артикул просто не вернулся в поисковой выдаче. Например, если товар закончился.

Если 26-го числа у товара было 3 единицы, 27-го данные отсутствуют, а 28-го товара нет выдаче — это 0 остатков или может продавец сменил фото карточки и товар ушел на модерацию?

Если считать отсутствие данных за 0, то:

  • День 1: остаток 3.

  • День 2: остаток 0 \to записываем 3 продажи.

  • День 3: товар вернулся, остаток 3 \to записываем поставку 3.

  • День 4: товар снова мигнул в выдаче\to снова 3 продажи.

В итоге товар, лежащий мертвым грузом, «генерировал» продажи каждые два дня.

Проблема №2: «Синдром дачника»

На маркетплейсах тысячи селлеров работают по системе FBS (продажи со своего склада).

Представьте типичного продавца:

  • У него карточка с 2 отзывами.

  • На складе числится 50 чехлов для телефонов. Продаж нет неделями.

  • В пятницу днем продавец решает уехать на выходные на дачу. Чтобы в субботу не упал заказ, который он не сможет вовремя упаковать (иначе WB влепит штраф за срыв логистики), он заходит в личный кабинет и руками выставляет остаток 0.

  • В понедельник он возвращается и выставляет остаток обратно: 50.

Что видит наивный расчет?

  • Пятница: остаток упал с 50 до 0. \Delta q = -50. Продано 50 штук!

  • При цене чехла 800 рублей алгоритм рисует карточке с 2 отзывами 40 000 рублей выручки за день.

  • Модуль аналитики моментально поднимает этот мусорный товар, искажая всю статистику категории, так как это место мог занять реально продающийся товар.

Мы назвали эту проблему «Синдром дачника» — конечно, не факт, что товар просто убирается руками и потому его остатки на складах скачут. Но в дальнейшем будет понятно, что нам в принципе и не нужно искать причину таких скачков.

Проблема №3: Загадочный график «трех пиков»

Мы открыли детальный отчет по категории аксессуаров для волос (реальный артикул из наших логов: Крабики для волос маленькие, цена 181 руб., 1 858 отзывов, расчетная выручка 28 683 руб.).

Вот как выглядел график выручки по дням:

График выручки, совершенно очевидно наличие выбросов

График выручки, совершенно очевидно наличие выбросов

Три дня (11, 16 и 19 июля) показывают всплески ровно по 9 000 – 9 500 рублей (~50-54 штуки проданных крабиков).

Во все остальные 27 дней месяца выручка — строгий ноль.

Почему живой товар с 1800 отзывами продается строго три раза в месяц пачками по 50 штук, а между ними пустота? В этот момент стало ясно: мы моделируем не поведение покупателей, а какую-то техническую неточность.


Глава 4. Битва с ветряными мельницами: Почему сломался математический аппарат

Мы обратились к математике. У нас была возможность обратиться к человеку с сильным математическим бэкграундом, и первой мыслью было: «Сейчас мы почистим данные нормальными статистическими фильтрами и все будет в масле».

Мы сели проверять гипотезы:

  1. Распределение Стьюдента и 3\sigma (трехсигмовые интервалы):

Мы пытались считать скользящее среднее и стандартное отклонение \sigma по продажам или остаткам и отрезать всё, что вылетает за доверительный интервал.

  • Почему развалилось: Данные продаж на маркетплейсах не имеют нормального распределения. Мы вообще в принципе не знаем, какое точно у нас распределение — оно сильно зависит от категории, а также от фильтров, которые мы выставим на набор данных. В ВУЗе мы обычно использовали статистику, имея некоторые допущения, гипотезы, предположения. От этого можно было оттолкнуться. А как определить в случае, когда мы не знаем распределение, верно ли мы применяем алгоритмы?

  • Всплеск продаж в день акции или после захода с рекламой может превышать среднее на 2–3 порядка. Если вы отсекаете по 2\sigma или 3\sigma, вы срезаете реальных лидеров рынка, но при этом пропускаете систематические выбросы у мелких товаров.

  1. Проблема уровня агрегации (товар vs рынок):

Если проверять выбросы внутри одного товара, то у товара-пустышки дроп с 50 до 0 — это единственное событие, у него дисперсия равна нулю. Такой алгоритм не считает это выбросом.

А если проверять по всему рынку, то продажа 50 крабиков — обычное дело для крупного оптовика, но невозможная аномалия для селлера с нулевым рейтингом.

Переход к устойчивой (робастной) статистике: Log-IQR

Мы отказались от среднего и дисперсии в пользу медиан и квантильных размахов в логарифмическом пространстве.

Мы полагаем, что распределение цен и выручки мультипликативно, потому перевели метрики в логарифмическую шкалу:

z = \log(1 + x)

Затем считали интерквартильный размах (IQR):

Q_1 = Q_{0.25}(z), \quad Q_3 = Q_{0.75}(z), \quad IQR = Q_3 - Q_1

Верхняя граница отсечения выбросов:

U = \exp(Q_3 + k \cdot IQR) - 1

  • Для цен в rule-based сегментации мы взяли мягкий коэффициент, а для кластеризации на основе нейросетей — более строгий. Здесь не так важно значение коэффициента, так как оно подбирается экспериментально.

Создание централизованного фильтра: Revenue Quality Filter

Чтобы остановить поток фантомной выручки, мы спроектировали трехуровневый алгоритм верификации качества. Алгоритм не удаляет товар из выборки, но помечает его флагом артифакта и обнуляет расчетную выручку и продажи.

Блок-схема обработки выручки

Блок-схема обработки выручки

Математика трех правил:

  1. Правило 1 (Глобальный экстремум):

R_i^{\text{raw}} > \exp(Q_3(\log(1+R)) + 7.0  \cdot IQR(\log(1+R))) - 1

Отсекает выбросы, когда товару приписывалась выручка в сотни миллиардов рублей.

  1. Правило 2 (Аномальная выручка на один отзыв):

Для товаров из нижнего 5%-хвоста по отзывам (F_i \le Q_{0.05}(F^+)) рассчитывается показатель удельной выручки:

\rho_i = \frac{R_i^{\text{raw}}}{\max(F_i, 1)}

Если \rho_i превышает квантильный порог референсной группы рынка (товары с F > Q_{0.20}):

\rho_i > T_{\rho} \quad (\text{Log-IQR с } k=5.0  \text{ внутри ценового бина})

товар помечается флагом артифакта.

  1. Правило 3 (Фантомные новички):

Если у товара 0 отзывов (F_i = 0), а его выручка превышает 5 медианных выручек зрелых товаров в своем ценовом бине:

R_i^{\text{raw}} > 5  \times  \text{median}(R_{\text{ref}})

он признается артефактом.

Шок от реальных данных: десятки миллионов фантомных рублей

Когда мы прогнали этот фильтр на реальных выгрузках, результат все еще был далеко от идеала. Вот пример реального расчета:

Пример 1: Категория «Кинетический песок»

  • Всего проанализировано товаров: 1 581 SKU

  • Количество товаров, признанных артефактами: 742 SKU (почти 47% товаров из выборки!)

  • Сырая выручка, рассчитанная по дельте остатков: 59 818 625 руб.

  • Очищенная выручка после фильтра: 2 890 246 руб.

  • Срезано выручки: 56 928 379 руб. (95.2% расчетной выручки было мусором!)

  • Причина: 741 товар имел 0 отзывов, но алгоритм дельты нарисовал им 56 миллионов несуществующих продаж!

Пример 2: Категория «Аксессуары для волос»

  • Всего проанализировано товаров: 74 127 SKU

  • Товаров-артефактов: 5 710 SKU

  • Сырая выручка: 2 917 609 966 руб. (2.91 млрд руб.)

  • Очищенная выручка: 2 172 959 466 руб.

  • Срезано фантомной выручки: 744 650 500 руб. (почти три четверти миллиарда рублей мусора!)

Фильтр спасал от бреда, но оставалось чувство неудовлетворенности: мы просто зануляли половину рынка. Неужели нельзя восстановить истину?


Глава 5. Байесовское сглаживание: Приручение шума

Параллельно мы искали математический способ не бинарно вырезать товары, а плавно штрафовать метрики за недостаток данных.

Байесовское сглаживание в модуле Нейминга

В модуле текстового анализа названий наивное среднее тоже приводило к абсурду, хотя и не так часто: редкое слово (например, «экстра-супер-увлажнение»), встретившееся ровно 1 раз у товара-лидера, получало среднюю выручку в 500 000 рублей и вылетало на 1-е место рейтинга эффективности.

Мы применили формулу байесовского сглаживания с k = 200 псевдонаблюдениями:

R_m(g) = \frac{S_m(g) + k \cdot  \mu_m}{n_g + k} = w_g \cdot  \overline{x}_g + (1 - w_g) \cdot  \mu_m

где:

  • S_m(g) — сумма метрики (выручки или отзывов) товаров, содержащих n-грамму g;

  • n_g — реальное число вхождений фразы;

  • \mu_m — глобальное среднее по всему рынку;

  • w_g = \frac{n_g}{n_g + k} — статистический вес эмпирических данных.

Частота слова n_g

Доля фактического среднего w_g

Доля общерыночного среднего 1 - w_g

3

1.48%

98.52%

20

9.09%

90.91%

200

50.00%

50.00%

1000

83.33%

16.67%

Если слово встретилось 3 раза, его оценка на 98.5% определяется рыночным фоном, и лишь при сотнях повторений слово завоевывает право называться драйвером продаж.

Не думаю, что про Байесовское сглаживание стоит подробно рассказывать. Это достаточно популярная вещь — можно найти много информации в интернете. Мне например очень понравилась вот эта статья.

Внедрение внутреннего индекса доверия

Мы решили адаптировать этот принцип к выручке товаров. Вместо жесткого отсечения мы ввели внутренний весовой коэффициент доверия к выручке на основе отзывов:

\text{confidence}_i = \text{floor} + (1 - \text{floor}) \cdot  \frac{F_i}{F_i + K}

где:

  • \text{floor} = 0.05 (минимальный вес доверия);

  • F_i — количество отзывов карточки;

  • K = Q_{0.75}(F^+) — 75-й перцентиль отзывов по рынку.

Внутренняя метрика \text{revenue\_confidence} = R_i \times  \text{confidence}_i использовалась для ранжирования карточек в выдаче, не позволяя товарам без валидации на основе данных группы пробиваться в топы.

Но оставался главный нерешенный вопрос: откуда всё-таки брались те загадочные пачки продаж по 44, 50, 53 штуки?


Глава 6. Момент истины: Секрет, который обнулил все расчеты по остаткам (как кэп остатков ломает аналитику)

Аналитика по дельте остатков системно дает сбой, поскольку интерфейс Wildberries кэпирует остатки в корзине, превращая колебания лимита в ложные продажи.

Конец июля 2026 года. Очередная ночная сессия дебага логов и поиска закономерностей.

Мы взяли один из тестовых аккаунтов селлера, загрузили на склад 100 единиц товара и открыли карточку в браузере как обычный покупатель. Нажали «Добавить в корзину» и попробовали выкрутить счетчик количества на максимум.

И тут наступил шок.

Wildberries кэпирует остатки на витрине!

Оказалось, что интерфейс корзины Wildberries не показывает реальный остаток товара на складе, если он превышает определенный лимит.

  • На витрине отдается искусственно обрезанное число.

  • Оно колеблется в диапазоне.

  • Более того, это значение может динамически меняться в зависимости от геолокации, типа склада, сессии пользователя или иных параметров.

Что это значило для нашего пайплайна?

Представьте:

  1. У продавца на складе лежит 5 000 единиц товара.

  2. Попытка узнать остатки: qty = 54 (сработал витринный кэп).

  3. На следующий день стучимся на витрину снова, но получаем другой лимит qty = 44.

  4. Наш алгоритм делает расчет: 54 - 44 = 10. Зафиксировано 10 продаж!

  5. На третий день получаем qty = 53. Алгоритм видит увеличение остатка \to считает это поставкой.

  6. На четвертый день карточка на секунду выпала из выдачи: qty = 0. Алгоритм радостно пишет: продано 53 штуки!

Мы неделями искали идеальные формулы математической статистики, чтобы победить шум, который не являлся шумом на самом деле.

Считать продажи вычитанием складских остатков на Wildberries — это попытка измерить глубину линейкой, длина которой произвольно меняется.


Глава 7. Архитектурный разворот: Как считать продажи, если остаткам верить нельзя

Осознав природу данных, мы совершили фундаментальный разворот в архитектуре.

Мы вернулись к отзывам, но не в их примитивном виде, а через иерархическую систему калибровки продаж продавца.

Пайплайн расчета выручки через отзывы

Пайплайн расчета выручки через отзывы

В чем суть нового подхода?

Есть не только данные по товарам, но и периодические снимки по продавцам в целом:

  • sold_products_count — общее число заказов / проданных товаров всего магазина селлера;

  • product_feedbacks_count — суммарное число отзывов на всех товарах селлера.

За любой интервал времени [\tau_1, \tau_2] для продавца известны два фундаментальных макро-показателя:

  1. Сколько реально продал весь магазин: \Delta  \text{Sold}_{\text{supplier}}.

  2. Сколько реально новых отзывов получил весь магазин: \Delta  \text{Feedbacks}_{\text{supplier}}.

Это дает нам фактическую эмпирическую конверсию отзывов в заказы именно для этого продавца в этот конкретный календарный период:

C_{\text{supplier}} = \frac{\Delta  \text{Sold}_{\text{supplier}}}{\Delta  \text{Feedbacks}_{\text{supplier}}}

При расчете выручки через отзывы есть тоже свои нюансы. Не должно быть никаких догадок из серии «на WB один отзыв приходится в среднем на 30–50 покупок». Такие догадки никуда не годятся. Для бренда дешевых носков этот коэффициент может быть равен 80, а для премиальной бытовой техники — 12.

Спуск на уровень товара

Когда у конкретного товара j, принадлежащего данному продавцу, за интервал времени вырастает число отзывов на \Delta F_j, его доля в общем пуле продаж магазина определяется пропорцией прироста его отзывов к общему приросту отзывов селлера:

\text{raw\_sales}_j = \frac{\Delta F_j}{\Delta  \text{Feedbacks}_{\text{supplier}}} \times  \Delta  \text{Sold}_{\text{supplier\_pool}}

Целочисленное квантование: Метод Гамильтона

Поскольку \text{raw\_sales}_j получается дробным числом, а товар измеряется в целых штуках, мы применили классический метод распределения мест Гамильтона (метод наибольших остатков, allocateinteger_sales):

  1. Каждому товару с положительным приростом отзывов гарантированно отдается целая часть \lfloor  \text{raw\_sales}_j \rfloor (но не менее 1 шт).

  2. Оставшийся нераспределенный остаток заказов поочередно раздается товарам с максимальной дробной частью.

Консервативная привязка цен

Чтобы исключить раздувание выручки, если продавец кратковременно менял цену (например, задрал перед акцией), мы сопоставляем интервал продажи с историей изменения цен:

p_{\text{event}} = \min(p(\text{start}), p(\text{end}))

Берется консервативный минимум цены за интервал наблюдения.

Результат архитектурного перехода

  1. Исчезли фантомные миллионы: товары с 0 отзывов физически больше не могут получить ни единой продажи из-за скачков складских лимитов.

  2. Ушел эффект кэпирования корзины: алгоритм вообще не смотрит на остаток товара, если тот подвержен витринным ограничениям.

  3. Сохранилась адекватная динамика: если селлер запустил рекламу и пошли продажи с отзывами, они пропорционально и честно распределяются между его SKU.

    Пример графика выручки товара-лидера в категории Зоотовары / Для собак / Ветаптека при расчетах выручки через отзывы.

    Пример графика выручки товара-лидера в категории Зоотовары / Для собак / Ветаптека при расчетах выручки через отзывы.

Глава 8. Чек-лист по расчету аналитики маркетплейсов

Сравнительный анализ методов расчета продаж

Параметр сравнения

Дельта остатков

Отзывы

Исходные данные

Остатки на складах

Отзывы на карточке и продавца

Точность

**

*****

Устойчивость к выбросам

**

****

Задержка данных

24 часа

несколько дней — несколько недель

Применимость для анализа

Аккуратно, требует хорошую фильтрацию данных

Оптимален для оценок

Чек-лист: Как построить надежный пайплайн e-commerce аналитики

Никогда не доверяйте исходным числам

Забудьте про стандартные распределения

Никаких M \pm  3\sigma. Используйте только непараметрические и робастные статистики: квантили (Q_{0.25}, Q_{0.75}), межквартильный размах (IQR), медианы и логарифмирование (z = \log(1+x)).

Внедряйте многоуровневую верификацию артефактов и выбросов

Если ваш алгоритм насчитал товару с 0 отзывов миллион рублей выручки — в 99.9% случаев неправ ваш алгоритм, а не покупатели смели склад.

Используйте алгоритмы по типу байесовского сглаживания, они реально работают

Редкие события должны притягиваться к среднему по рынку. Для текстовых признаков (нейминг, теги) и для метрик надежности используйте фиктивные псевдонаблюдения.

Связывайте микро-уровень с макро-уровнем:

Если метрика на микро-уровне дает сомнительный сигнал, проверяйте ее через родительскую сущность.


Заключение: Почему ни один внешний сервис не знает 100% правды

Если вам в рекламе очередного сервиса аналитики заявляют: «Мы знаем точные продажи каждого товара на Wildberries с точностью до одной копейки по нашим секретным данным» — теперь вы знаете, что эти секретные данные могут оказаться не очень верными.

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

Когда мы перестали пытаться выжать из дырявых складских остатков несуществующую точность и построили математически выверенную модель, хоть и не дающую точность до рубля, система наконец начала выдавать отчеты, на которые можно опереться при принятии реальных бизнес-решений.

Внешняя аналитика — это всегда вероятностная модель и баланс компромиссов. Если вы сейчас проектируете свой пайплайн или хотите сопоставить данные своих категорий с нашими расчетами — базовый срез доступен в инструменте анализа рынка бесплатно и без необходимости привязывать карту.

А как вы боретесь с выбросами и аномалиями в данных? Делитесь в комментариях своим опытом — будет интересно обсудить!

ссылка на оригинал статьи https://habr.com/ru/articles/1086850/