Раздел 42 · Аналитика данных с нуля

Факторный разбор: сколько рублей дали объём, цена и микс

middle~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Мира · маркетолог магазина настолок «Кубик»

В ноябре заказов стало в полтора раза больше, а продажи выросли только на четверть. Финдиректор говорит, что MEGA30 раздал всё скидками. Так сколько мы потеряли на скидке?

В прошлом уроке мы разложили выручку на дерево и смотрели, какие ветки выросли, а какие просели. Мира спрашивает строже: не «что двигалось», а «сколько рублей изменения принёс каждый множитель». Это факторный разбор. Работаем с базой boardshop: MEGA30 мы уже встречали у второго босса, там он дал худшую маржу. Сегодня считаем, что он сделал с продажами.

Продажи месяца здесь это сумма qty * price по позициям заказов, кроме отменённых; возвраты оставим отдельной веткой. С октября на ноябрь 2025 продажи выросли с 1 356 166 до 1 699 001 рубля, на 342 835.

Прямоугольник

Продажи это заказы × средний чек. Нарисуй прямоугольник: ширина это заказы, высота это чек, площадь это продажи. В октябре 380 заказов по 3569 рублей, в ноябре 590 заказов по 2880.

 3569 ₽ ┌────────────────────┬ ─ ─ ─ ─ ─ ┐
        │ срезал чек         │   угол      689 ₽
 2880 ₽ ├────────────────────┼───────────┤
        │                    │ добавил   │
        │ общая часть        │ объём     │
        │                    │           │
        └────────────────────┴───────────┘
             380 заказов         +210

Октябрь это прямоугольник 380 × 3569, ноябрь это 590 × 2880. Справа добавилась полоса 210 × 2880: это объём. Сверху срезалась полоса 380 × 689: это чек. А пунктирный угол 210 × 689 не принадлежит ни одному месяцу, но любой расчёт «по одному фактору» его задевает: объём по старому чеку считает его своим, чек по новым заказам его вычитает. Кому его отдать?

Цепные подстановки

Классический способ называется цепными подстановками: меняешь факторы по одному. Сначала заказы при старом чеке, потом чек при новых заказах. Или наоборот.

WITH monthly AS (
  SELECT date_trunc('month', o.created_at)::date AS month,
         count(DISTINCT o.id) AS orders,
         sum(oi.qty * oi.price) AS revenue
  FROM orders o
  JOIN order_items oi ON oi.order_id = o.id
  WHERE o.status <> 'cancelled'
    AND o.created_at >= '2025-10-01' AND o.created_at < '2025-12-01'
  GROUP BY month
),
pair AS (
  SELECT max(orders) FILTER (WHERE month = '2025-10-01') AS n0,
         max(orders) FILTER (WHERE month = '2025-11-01') AS n1,
         max(revenue) FILTER (WHERE month = '2025-10-01')
           / max(orders) FILTER (WHERE month = '2025-10-01') AS a0,
         max(revenue) FILTER (WHERE month = '2025-11-01')
           / max(orders) FILTER (WHERE month = '2025-11-01') AS a1
  FROM monthly
)
SELECT round((n1 - n0) * a0) AS volume_first,
       round(n1 * (a1 - a0)) AS check_second,
       round(n0 * (a1 - a0)) AS check_first,
       round((n1 - n0) * a1) AS volume_second
FROM pair;
 volume_first | check_second | check_first | volume_second
--------------+--------------+-------------+---------------
       749460 |      -406625 |     -261894 |        604729

Оба порядка сходятся с итогом: 749 460 − 406 625 и 604 729 − 261 894 дают те же 342 835. Но ответы разные. Если заказы идут первыми, объём принёс 749 тысяч, а чек отнял 407. Если первым чек, объём принёс 605 тысяч, а чек отнял 262. Разница в 145 тысяч это тот самый угол: (n1 - n0) * (a1 - a0) = 210 × (−689) ≈ −144 731. Кто идёт вторым, тот и забирает угол.

Как не выбирать порядок

Первый способ: посчитать оба порядка и взять среднюю. Для двух факторов это делит угол пополам:

volume_effect = (n1 - n0) * (a0 + a1) / 2
check_effect  = (a1 - a0) * (n0 + n1) / 2

Объём даёт +677 095, чек −334 260, сумма снова 342 835. Для двух множителей это то же, что вектор Шепли: средняя по всем порядкам. Но при четырёх множителях порядков уже 24, и считать их все утомительно.

Второй способ: логарифмическое разложение. Логарифм превращает произведение в сумму: ln(R1/R0) = ln(n1/n0) + ln(a1/a0). Каждый множитель получает такую долю изменения, какую его логарифм составляет в сумме:

effect = (R1 - R0) * ln(v1 / v0) / ln(R1 / R0)

Объём даёт +669 232, чек −326 397. Почти как средняя, но этот способ работает для любого числа множителей одной формулой. В PostgreSQL это функция ln.

Оба способа отвечают на вопрос Миры одинаково: продажи выросли за счёт заказов, а чек отнял около трети этого роста. Осталось понять, почему упал чек.

урок из курса

Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.