
Как проверить итог groupby в pandas: пропущенные группы и правильный знаменатель доли


Если после groupby сумма стала меньше исходной, причина часто не в сложении, а в том, что часть строк выпала на этапе формирования групп. Для ключей с пропусками это обычная ситуация: pandas по умолчанию не включает NA в группы. Вторая ошибка появляется следом: доли по регионам считают от разных оснований, но в таблице это не подписывают.
Чтобы проверить итог без гаданий, удобно идти в таком порядке: сначала зафиксировать состав строк, потом сравнить две группировки с разным dropna, затем сверить суммы и только после этого считать доли. Тогда видно, что именно произошло: строки исчезли из группировки, были исключены по правилу отчета или просто использован другой знаменатель.
Сначала определите, какие строки вообще входят в отчет
До любой группировки полезно зафиксировать базу отчета отдельной переменной. Даже если в примере участвуют все заказы, это дисциплинирует код: вы отделяете отбор строк от расчета показателей. В нашей задаче все строки остаются в базе, а различие возникает только из-за пропущенного region.
На этом шаге нужны всего три числа: общий итог по сумме заказов, итог только по строкам с заполненным регионом и количество строк без региона. Если их не выписать заранее, итог 520 после группировки может выглядеть как ошибка, хотя это просто сумма по известным регионам. Для категориального ключа полезно еще заранее решить, хотите ли вы видеть только наблюдавшиеся категории или весь список категорий: такое поведение лучше задавать явно, а не оставлять на значения по умолчанию.
Что меняет dropna при группировке по региону
Учебный пример. Ниже условный набор из 8 заказов в среде Python с pandas. Суммы подобраны так, чтобы можно было быстро проверить арифметику вручную.
import pandas as pd
orders = pd.DataFrame({
"order_id": [1, 2, 3, 4, 5, 6, 7, 8],
"region": ["North", "South", None, "North", "West", None, "South", "West"],
"amount": [120, 80, 50, 100, 70, 30, 60, 90],
})
base = orders.copy()
total_amount = base["amount"].sum()
known_amount = base.loc[base["region"].notna(), "amount"].sum()
missing_region_rows = base["region"].isna().sum()
by_region_drop = (
base.groupby("region", dropna=True, sort=False)["amount"]
.sum()
.rename("amount")
)
by_region_keep = (
base.groupby("region", dropna=False, sort=False)["amount"]
.sum()
.rename("amount")
)
В этих данных общий итог равен 600. Если исключить строки без региона, останется 520. Это легко проверить вручную: по известным регионам получаются суммы North = 220, South = 140, West = 160, а по пропущенному региону — еще 80. Поэтому groupby(..., dropna=True) вернет только три группы и сумму 520, а groupby(..., dropna=False) добавит группу с пропуском и вернет полный итог 600.
Здесь важно не считать первый вариант «неправильным». Он правильный для задачи «сводка только по заказам с известным регионом». Ошибка начинается тогда, когда аналитик не замечает, что смысл итога поменялся. Поэтому параметр dropna лучше писать явно даже там, где вы знаете значение по умолчанию: так код не заставляет читателя угадывать вашу договоренность.
Как сверить суммы с исходником, чтобы не пропустить потерянные строки
После группировки нужна не только таблица, но и проверка того, к какому исходному итогу она должна сходиться. В нашей задаче есть два корректных эталона: либо полный итог по всем заказам, либо итог только по строкам с непустым регионом. Если вы не выбрали эталон заранее, число само по себе ничего не доказывает.
| Проверка | Ожидаемое значение | Что это означает |
|---|---|---|
| Количество строк без региона | 2 | Именно они исчезнут из ключей при dropna=True |
| Общий итог по всем заказам | 600 | Эталон для варианта с dropna=False |
| Итог только по заполненным регионам | 520 | Эталон для варианта с dropna=True |
Сумма групп при dropna=True | 520 | Показывает только известные регионы |
Сумма групп при dropna=False | 600 | Возвращает полный итог, включая пропуск как отдельную группу |
Практически это удобно закрепить простыми проверками в коде. Для учебного примера подойдут даже обычные assert:
assert missing_region_rows == 2
assert total_amount == 600
assert known_amount == 520
assert by_region_drop.sum() == known_amount
assert by_region_keep.sum() == total_amount
Если хотя бы одна из этих проверок не проходит, проблема уже не в одном dropna. Тогда стоит искать другое: лишний фильтр до группировки, дубли после объединения таблиц, неожиданное преобразование типов или пропуски уже в агрегируемом столбце, а не только в ключе группировки.
Как не перепутать знаменатель при расчете доли
После сверки сумм начинается более тонкий момент: доля региона зависит не только от числителя, но и от того, что вы считаете полным объемом. В учебном примере возможны как минимум два осмысленных варианта. Первый — доля от всех заказов, включая строки без региона. Второй — доля только среди заказов, у которых регион известен.
shares_all_orders = (by_region_keep / total_amount).round(4)
shares_known_regions = (by_region_drop / known_amount).round(4)
shares_displayed_from_all = (by_region_drop / total_amount).round(4)
Числа будут разными. Для доли от всех заказов получаем: North = 36.67%, South = 23.33%, West = 26.67%, пропуск по региону — 13.33%. Для доли только среди известных регионов получаем: North = 42.31%, South = 26.92%, West = 30.77%. Оба результата могут быть полезны, но они отвечают на разные вопросы.
Частая путаница возникает, когда в отчет выводят только известные регионы, а знаменатель оставляют от всех заказов. Тогда показанные доли суммируются не до 100%, а до 86.67%, потому что еще 13.33% приходится на строки без региона. Это не обязательно ошибка, но в заголовке или имени столбца должно быть ясно, что это доля от всех заказов, а не структура по известным регионам.
Как закрепить правило в коде и в постановке задачи
Лучший способ не возвращаться к этой проблеме каждый месяц — вынести договоренность в код и в описание отчета. Не оставляйте поведение на значениях по умолчанию, если оно влияет на смысл результата. Для такой задачи обычно достаточно явно указать dropna и sort, а если ключ категориальный — еще и observed, чтобы состав групп не зависел от неявных настроек.
Перед тем как считать итоговую таблицу, задайте себе несколько вопросов. Должны ли заказы без региона появляться отдельной строкой? Если нет, нужно ли все равно учитывать их в знаменателе доли? Должен ли читатель отчета видеть, сколько строк было исключено и на какую сумму? Если эти ответы есть только «в голове у аналитика», следующая правка почти наверняка сломает сопоставимость отчетов.
Полезный шаблон такой: отдельно сформировать base, отдельно посчитать контрольные итоги, затем построить одну таблицу для сумм и вторую — для долей с явным названием знаменателя. Например, столбцы можно назвать share_all_orders и share_known_regions. Тогда читатель не гадает, почему доли складываются не в 100%, а вы можете быстро доказать, что расчет согласован с исходными данными.
Практический результат проверки в нашем учебном кейсе такой: сумма groupby на 520 не является ошибкой, если отчет сознательно исключает пустой region; полный контрольный итог 600 возвращается только при dropna=False или при отдельном учете строк без региона. А корректный выбор доли зависит от того, показываете ли вы структуру всех заказов или только заказов с заполненным регионом.
Где это обычно отрабатывают на практике
В сохраненном описании каталога курса по pandas есть темы очистки и предобработки данных и фильтрации, группировки и агрегации. Для этой задачи полезна именно связка этих блоков: сначала проверить пропуски и состав базы, потом уже строить агрегаты. Официальная страница курса недоступна, поэтому ориентироваться можно только на сохраненное описание каталога.





















