Почему заказы растут, а выручка падает: заказы × средний чек
Содержание курса
Как собрать таблицу с итогами и не запутаться в округлении
Чтобы подготовить готовый отчет, объединяют помесячные строки и итоговую строку TOTAL, после чего форматируют вывод.
Дробное значение обычно округляют для отображения до двух знаков после запятой — . Посмотрим, что произойдет, если попытаться восстановить выручку из округленного значения.
Вариант на SQL
SELECT 1.0 * 16200 / 7 AS aov_full,
ROUND(1.0 * 16200 / 7, 2) AS aov_display,
7 * ROUND(1.0 * 16200 / 7, 2) AS revenue_from_display,
7 * (1.0 * 16200 / 7) AS revenue_from_full;
Результат проверки:
| aov_full | aov_display | revenue_from_display | revenue_from_full |
|---|---|---|---|
| 2314.285714285714 | 2314.29 | 16200.03 | 16200.0 |
Вариант на pandas
aov_full = 16200 / 7
aov_display = round(aov_full, 2)
print(aov_full)
print(aov_display)
print(7 * aov_display)
print(7 * aov_full)
Вывод программы:
2314.285714285714 2314.29 16200.029999999999 16200.0
Произведение дает рубля. Разница в 3 копейки вызвана исключительно экранным округлением, а не потерей данных или ошибкой отбора строк. Поэтому в расчетной модели всегда сохраняют точные целые компоненты ( и ) и неокругленное отношение, а функцию ROUND() или метод .round(2) применяют только к финальному представлению.
Итоговая сборка полного отчета выполняется следующим образом:
В SQL через обобщенное табличное выражение (WITH) рассчитываются помесячные компоненты, к которым через UNION ALL прикрепляется строка TOTAL:
WITH monthly AS (
SELECT strftime('%Y-%m', order_date) AS month,
COUNT(*) AS paid_orders,
SUM(gross_revenue) AS gross_revenue
FROM demo_orders
WHERE status = 'paid'
AND order_date >= '2024-03-01'
AND order_date < '2024-05-01'
GROUP BY strftime('%Y-%m', order_date)
), report AS (
SELECT month, paid_orders, gross_revenue,
1.0 * gross_revenue / paid_orders AS aov
FROM monthly
UNION ALL
SELECT 'TOTAL', SUM(paid_orders), SUM(gross_revenue),
1.0 * SUM(gross_revenue) / SUM(paid_orders)
FROM monthly
)
SELECT month, paid_orders, gross_revenue, ROUND(aov, 2) AS aov
FROM report
ORDER BY month;
В pandas формируется отдельный однострочный DataFrame для строки TOTAL и объединяется с помесячной таблицей с помощью pd.concat:
total_orders = int(monthly["paid_orders"].sum())
total_revenue = int(monthly["gross_revenue"].sum())
total = pd.DataFrame([{
"month": "TOTAL",
"paid_orders": total_orders,
"gross_revenue": total_revenue,
"aov": total_revenue / total_orders,
}])
report = pd.concat([monthly, total], ignore_index=True)
shown = report.copy()
shown["aov"] = shown["aov"].round(2)
print(shown.to_string(index=False))
Оба варианта дают одинаковую финальную структуру:
| month | paid_orders | gross_revenue | aov |
|---|---|---|---|
| 2024-03 | 3 | 9000 | 3000.00 |
| 2024-04 | 4 | 7200 | 1800.00 |
| TOTAL | 7 | 16200 | 2314.29 |
При сохранении результата в CSV-файл (например, report.to_csv('monthly_paid_report.csv', index=False)) аргумент index=False обязателен, чтобы не добавлять в файл безымянную колонку со служебными индексами строк.
