Маржинальность подразделений в pandas: как распределить общие расходы

Отдел продаж принёс больше выручки, но после распределения аренды оказался убыточным. Руководитель спрашивает, почему аренду поделили пропорционально продажам, если склад занимает вдвое больше площади. Такой спор решается явным расчётом: у каждого вида затрат должны быть основание распределения и проверяемая сумма.

Что именно считаем

У двух подразделений есть выручка и прямые затраты. Разницу называем вкладом до общих расходов. Затем распределяем аренду по площади, а общий сервис — по количеству обработанных обращений. Остаток после этих статей называем результатом после распределения. Название важно: в примере нет налогов, процентов и других статей, необходимых для расчёта чистой прибыли.

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

Готовим три небольшие таблицы

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

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

Рассчитываем долю и сумму

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

import pandas as pd
base = pd.DataFrame([
    ('retail', 200000, 120000), ('online', 300000, 180000)
], columns=['unit','revenue','direct'])
drivers = pd.DataFrame([
    ('retail','area',60), ('online','area',40),
    ('retail','tickets',20), ('online','tickets',80)
], columns=['unit','driver','value'])
costs = pd.DataFrame([
    ('rent','area',50000), ('service','tickets',20000)
], columns=['cost','driver','amount'])
assert not drivers.duplicated(['unit','driver']).any()
assert set(drivers.unit) == set(base.unit)
assert (drivers.value >= 0).all()
x = costs.merge(drivers, on='driver', validate='one_to_many')
den = x.groupby('cost')['value'].transform('sum')
assert (den > 0).all()
x['allocated'] = x.amount * x.value / den
check = x.groupby('cost').allocated.sum()
assert check.to_dict() == costs.set_index('cost').amount.to_dict()
totals = x.groupby('unit',as_index=False).allocated.sum()
result = base.merge(totals,on='unit',validate='one_to_one')
result['contribution'] = result.revenue-result.direct
result['after_shared'] = result.contribution-result.allocated
result['margin_pct'] = 100*result.after_shared/result.revenue
print(result[['unit','allocated','after_shared','margin_pct']].to_string(index=False))
assert result.set_index('unit').after_shared.to_dict() == {'retail':46000.0,'online':84000.0}
assert result.allocated.sum() == 70000

Почему итог отличается от распределения по выручке

Розница получает 30 тысяч аренды и 4 тысячи сервиса: после прямых и общих затрат остаётся 46 тысяч рублей, или 23% выручки. Интернет получает 20 тысяч аренды и 16 тысяч сервиса: остаётся 84 тысячи, или 28%. Из общего пула 70 тысяч не потеряна и не создана ни одна денежная единица.

Если распределять все 70 тысяч по выручке, розница получила бы 28 тысяч, интернет — 42 тысячи. Общий результат компании остался бы тем же, а результат подразделений изменился. Значит, спор о распределении относится к оценке внутренних центров ответственности. Он не меняет факт расхода компании и сам по себе не обосновывает закрытие подразделения.

Что проверить перед управленческим решением

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

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

Как представить результат

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

Отдельно проверяем изменение базы

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

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

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

Чтобы превратить такой расчёт в повторяемый управленческий отчёт, нужно проверять входы и объяснять смысл показателя. В платной «Практической аналитике данных» вы пройдёте кейсы продаж, клиентов и рекламы, соберёте дашборд в DataLens и оформите аналитическую записку. Откройте программу курса.

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

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

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