Взвешенная цена с пропусками: числитель и знаменатель на одних строках
Средняя цена продажи оказалась ниже любой известной цены. В числитель попали только строки с ценой, а знаменатель включил количество товара с неизвестной ценой. Пропуск был пропущен в одном месте и сохранён в другом.
Определить, какие сделки участвуют
Взвешенная цена равна стоимости известных продаж, делённой на их количество. Если цена неизвестна, эту строку нельзя включить только в знаменатель. Но исключение строки тоже имеет смысл: результат описывает известную часть, поэтому рядом нужно показать покрытие.
В примере у A проданы две единицы по 100 и восемь единиц с неизвестной ценой. Цена известных продаж — 100, а не 20. Чтобы получить среднюю цену всех десяти единиц, надо восстановить отсутствующую стоимость из источника.
Создать общую маску
Строка участвует в расчёте, когда известны цена и вес, а вес положителен. Для возвратов и отрицательных количеств нужно отдельное определение показателя: обычная взвешенная средняя с отрицательными весами ведёт себя иначе.
По одной маске создаём вклад стоимости и учитываемый вес. Группу без пригодных сделок оставляем с неизвестным результатом. Отдельно считаем всё положительное количество, чтобы оценить покрытие.
import pandas as pd
x = pd.DataFrame({"sku": ["A", "A", "B", "A"], "price": [100.0, None, None, 100.0], "qty": [2.0, 8.0, 3.0, -1.0]})
valid = x["price"].notna() & x["qty"].notna() & x["qty"].gt(0)
x["value"] = (x["price"] * x["qty"]).where(valid)
x["used_qty"] = x["qty"].where(valid)
x["positive_qty"] = x["qty"].where(x["qty"].gt(0))
g = x.groupby("sku")
r = pd.DataFrame({"value": g["value"].sum(min_count=1),
"used_qty": g["used_qty"].sum(min_count=1), "all_qty": g["positive_qty"].sum()})
r["weighted_price"] = r["value"] / r["used_qty"]
r["coverage"] = r["used_qty"].fillna(0) / r["all_qty"]
print(r)
assert r.loc["A", "weighted_price"] == 100
assert r.loc["A", "coverage"] == 0.2
assert pd.isna(r.loc["B", "weighted_price"])У A цена 100 при покрытии 20% положительного количества, у B цена неизвестна. Отрицательная строка исключена и из средней, и из базы покрытия: возвраты требуют отдельного показателя. Покрытие показывает, насколько рискованно распространять вывод на все продажи. Большое количество неизвестных цен нельзя исправить формулой среднего.
Проверить единицы веса
Для средней цены единицы товара весом служит количество, для среднего чека — число заказов, для средней ставки — длительность или объём по договору. Случайный столбец «вес» может быть неподходящим, даже если расчёт математически корректен.
Если товары имеют разные единицы измерения, общая средняя цена килограмма и штуки бессмысленна. Сначала разделите сопоставимые единицы либо переведите их по подтверждённому коэффициенту. Аналогично нельзя смешивать валюты без согласованного курса и момента пересчёта.
При сравнении периодов держите одну политику пропусков и покажите покрытие обоих месяцев. Изменение средней при падении покрытия может описывать состав известных строк. Для адресной проверки восстановите несколько строк вручную и убедитесь, что числитель и знаменатель включают одни и те же сделки.
Закрепить на практике
В бесплатном уроке groupby и agg вы научитесь собирать именованные показатели. Затем объедините несколько агрегатов в среднюю по примеру статьи, сохранив единую маску и покрытие. Откройте бесплатный урок и задачи. Полная последовательность тем — в бесплатном курсе.
Продолжить на практике
- NumPy и pandas: практический тренажёр ↗
Бесплатный тренажёр NumPy и pandas для тех, кто знает основы Python. Научитесь очищать таблицы, объединять данные и собирать отчёты: 31 урок и 124 задачи с кодом и автоматической проверкой.
