Возвраты по товарам в pandas: сравниваем партии с одинаковым сроком наблюдения

Новая модель обуви выглядит почти безвозвратной: за неделю вернулся один заказ. У старой модели возвратов больше, но её покупатели уже успели примерить товар. Чтобы сравнить партии по качеству, зададим одинаковый срок наблюдения после каждой покупки и посчитаем долю возвратов в pandas.

Какой вопрос задаём данным

Будем оценивать долю проданных единиц, возвращённых в течение 14 дней после покупки. Четырнадцать дней здесь — выбранное аналитическое окно, а не утверждение о юридическом сроке возврата. Показатель отвечает на вопрос о ранних возвратах. Если бизнесу важны все возвраты за 60 дней, окно и дату закрытия наблюдения нужно изменить.

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

Продажа попадает в знаменатель только после завершения полного окна. На 31 октября покупка 1 октября уже зрелая, а покупка 25 октября ещё нет. Исключаем незрелые продажи целиком, вместе с уже зарегистрированными по ним возвратами. Иначе быстрые возвраты попадут в числитель, а будущие случаи без возврата не успеют проявиться, что создаст другое смещение.

Какие проверки нужны перед группировкой

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

В коде обе даты преобразуются в datetime, затем определяется зрелость и возраст возврата в днях. between(0,14) включает обе границы. Это правило подходит для примера с датами без времени. В потоке с точным временем удобнее сравнивать timestamp возврата с timestamp покупки плюс Timedelta(days=14), чтобы граница не зависела от округления до целых дней.

Если на одну единицу приходится несколько событий возврата, перед расчётом нужно определить действующее событие. В системе могут быть заявка, фактическая приёмка и отмена заявки. Простое наличие строки возврата не всегда означает, что товар действительно вернулся. Для сопоставимости партий берите один подтверждённый статус и одинаковое правило во всех месяцах.

import pandas as pd
d = pd.DataFrame({"sku":["A","A","B","B"],"sold":["2026-10-01","2026-10-02","2026-10-01","2026-10-25"],"returned":["2026-10-05",None,None,"2026-10-27"]})
d["sold"] = pd.to_datetime(d.sold)
d["returned"] = pd.to_datetime(d.returned)
assert (d.returned.isna() | d.returned.ge(d.sold)).all()
cutoff = pd.Timestamp("2026-10-31")
d["mature"] = d.sold.add(pd.Timedelta(days=14)).le(cutoff)
age = (d.returned-d.sold).dt.days
d["return14"] = age.between(0,14)
r = d[d.mature].groupby("sku").agg(units=("sku","size"),returns=("return14","sum"))
r["rate"] = r.returns/r.units
print(r.to_string())
assert r.loc["A","rate"] == 0.5
assert r.loc["B","units"] == 1
assert r.loc["B","returns"] == 0

По A наблюдались две зрелые продажи и один ранний возврат: доля 50%. По B зрелая продажа одна, без возврата. Покупка 25 октября исключена, хотя её возврат уже известен. Такое правило выглядит непривычно, зато сохраняет одинаковую возможность наблюдения для всех сравниваемых продаж. Когда окно завершится, эта единица войдёт и в знаменатель, и в числитель.

Нулевой процент по одной продаже не делает B надёжнее A. Рядом с долей оставьте число зрелых единиц и период покупки. При большом ассортименте начните с моделей, где достаточно наблюдений, и проверьте причины возврата. Дефект ведёт к разговору с поставщиком, несоответствие размера — к размерной сетке и карточке товара, отказ при доставке — к логистике.

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

После оценки качества товара отдельно решается, в каком периоде отражать возврат в выручке: Возвраты в отчёте о выручке: к какому месяцу их относить.

Какой список передать операционной команде

После расчёта долей подготовьте список зрелых возвращённых единиц с исходным заказом и причиной возврата. По A это конкретная покупка 1 октября с возвратом 5 октября. Руководитель качества сможет открыть документ и проверить причину, а не только увидеть красную ячейку в сводной таблице.

Отдельно сохраните число незрелых продаж. У B одна единица ждёт закрытия окна, поэтому следующая версия отчёта ожидаемо изменится. Для еженедельного обзора такая пометка объясняет, почему новая партия пока не участвует в сравнении. Если закупку нужно решать раньше, используйте дополнительные признаки качества, а зрелую долю возвратов обновите после завершения срока наблюдения.

Следующая рабочая задача

В «Практической аналитике данных» вы задаёте паспорт метрики, проверяете данные и сопоставляете периоды. Продажи и клиенты разобраны на учебном магазине «Маяк»; расчёты через pandas или SQL переходят в графики и дашборд. Итоговая работа включает повторный анализ и короткую рекомендацию. Откройте программу курса «Практическая аналитика данных».

Продолжить на практике

  • Практическая аналитика данных ↗

    Превратите знания SQL или Python в аналитическую работу: проверяйте данные, считайте бизнес-метрики, стройте дашборды и объясняйте выводы на трёх прикладных кейсах.