← Все курсы · Курс Python · Все руководства
pandas.groupby: как суммировать фрейм данных, не теряя строк
К концу этой страницы вы можете взять DataFrame, суммировать его по любому столбцу или комбинации столбцов, получить сразу несколько статистических данных с разумными именами и учесть каждую вошедшую строку, включая те, которые pandas в противном случае удалили бы, не сообщая вам. Это около двадцати пяти минут, и каждый показанный результат был получен путем фактического запуска кода из таблицы, напечатанной ниже.
Вот чем на самом деле заняться сегодня. В вашем следующем groupby, добавьте количество строк рядом со статистикой, которую вы вычисляете, используя sizeи запустите его один раз с помощью dropna=False. Эти два дополнения выявляют две наиболее распространенные молчаливые проблемы в сгруппированных результатах: средние значения, построенные почти на пустом месте, и строки, которые исчезли из-за отсутствия ключа.
Краткая версия: groupby разбивает таблицу на одну мини-таблицу для каждого значения ключа, применяет вашу функцию к каждой и объединяет ответы в новую таблицу с одной строкой на группу. count пропускает пропущенные значения, size нет, и строки с отсутствующим ключом полностью удаляются, если вы не попросите иного.
Форма «разделить-применить-объединить» — это единственная идея, на которой основано все остальное на этой странице, поэтому она понятна.
- Что на самом деле делает группировка: разделить, применить, объединить
- Основной ход: один столбец, одна статистика
- Сразу несколько статистических данных: агрегирование и именованная агрегация
- размер по количеству, и какой из них отвечает на ваш вопрос
- Два группирующих столбца, Мультииндекс и возвращение плоской таблицы
- Проблема малых групп, перенесенная из SQL
- преобразование: групповая статистика в каждой строке
- Строки, которые исчезают: водянка
- Полное до и после
- Пограничные случаи, которые нарушают сгруппированные результаты в реальных данных
- Почему это работает
- Использование этого в вашем собственном проекте
- Все на одном экране
Вот весь набор данных. Четырнадцать заказов, регион, категория и сумма. Две вещи намеренно несовершенны, потому что реальные данные всегда таковы: в заказе 1008 отсутствует сумма, а в заказах 1013 и 1014 отсутствует регион.
import pandas as pd
import numpy as np
df = pd.DataFrame({
'order_id': [1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014],
'region': ['East','East','East','East','East','West','West','West','West',
'South','South','South',np.nan,np.nan],
'category': ['Chairs','Chairs','Desks','Desks','Lamps','Chairs','Desks','Desks','Lamps',
'Chairs','Chairs','Lamps','Desks','Lamps'],
'amount': [120.0, 80.0, 300.0, 250.0, 40.0, 100.0, 400.0, np.nan, 60.0,
90.0, 110.0, 50.0, 260.0, 45.0],
})
| идентификатор заказа | Регион | категория | Количество |
|---|---|---|---|
| 1001 | Восток | Стулья | 120 |
| 1002 | Восток | Стулья | 80 |
| 1003 | Восток | Столы | 300 |
| 1004 | Восток | Столы | 250 |
| 1005 | Восток | Лампы | 40 |
| 1006 | Запад | Стулья | 100 |
| 1007 | Запад | Столы | 400 |
| 1008 | Запад | Столы | НЭН |
| 1009 | Запад | Лампы | 60 |
| 1010 | Юг | Стулья | 90 |
| 1011 | Юг | Стулья | 110 |
| 1012 | Юг | Лампы | 50 |
| 1013 | НЭН | Столы | 260 |
| 1014 | НЭН | Лампы | 45 |
1. Что на самом деле делает группировка: разделить, применить, объединить
Перед пояснением: в таблице 14 строк. Вы группируете по регионам и получаете обратно 3 строки. Где же в вашей голове остальные 11 строк и что теперь означает одна строка результата?
Название механизма разделить-применить-объединить. Расколоть: pandas сортирует строки в одну мини-таблицу для каждого отдельного значения ключа, каждую восточную строку вместе, каждую западную строку вместе, каждую южную строку вместе. Применять: он запускает вашу функцию, скажем, среднее значение, для каждой мини-таблицы отдельно. Объединить: ответы складываются в новую таблицу, по одной строке на группу. Ничто не усредняется по группам случайно, поскольку каждый вызов функции видел только строки одной группы.
Часть, которую стоит произнести вслух, — это то, что означает одна строка после нее. До группировки одна строка представляла собой один заказ. После группировки по регионам одна строка — это один регион. Такое изменение значения называется grain таблицы, и это именно та дисциплина, которую SQL заставляет GROUP BY: как только детализация составит одну строку на регион, сумма отдельного заказа больше не будет присутствовать в результате, и любой столбец, который вы хотите видеть, должен быть групповым ключом или сводкой. pandas не будет выдавать ошибку, как это делает SQL, если вы перепутаете зерна; это просто даст вам то, что вы не имели в виду. Назвать новое уровень детализации перед вводом — дешевая защита.
2. Основной ход: один столбец, одна статистика.
Строка «рабочей лошадки» читается почти как предложение, которое вы бы сказали: сгруппируйте по регионам, возьмите столбец суммы, возьмите среднее значение.
df.groupby('region')['amount'].mean()
region
East 158.000000
South 83.333333
West 186.666667
Name: amount, dtype: float64
Проверьте Восток вручную: (120 + 80 + 300 + 250 + 40) ÷ 5 = 790 ÷ 5 = 158. Юг: (90 + 110 + 50) ÷ 3 = 250 ÷ 3 = 83,33. У Запада четыре строки, но одна сумма отсутствует, и mean пропускает пропущенные значения: (100 + 400 + 60) ÷ 3 = 560 ÷ 3 = 186,67. Делится на три, а не на четыре. Имейте это в виду; вскоре это станет целым разделом.
Прежде чем двигаться дальше, две вещи поменьше. Менять mean для sum, median, min, max, или std и форма идентична. В результате получается серия, индекс которой является групповым ключом, что удобно для быстрого просмотра и неудобно для чего-либо последующего; в пятом разделе показана версия с плоским столом.
3. Сразу несколько статистик: агрегация и именованная агрегация
Единственное среднее редко является результатом. agg берет список функций и вычисляет их для каждой группы за один проход.
df.groupby('region')['amount'].agg(['mean', 'sum', 'size', 'count'])
mean sum size count
region
East 158.000000 790.0 5 5
South 83.333333 250.0 3 3
West 186.666667 560.0 4 3
Версия, которую я на самом деле отправляю, именованная агрегация, где вы сами называете каждый выходной столбец и указываете, какой входной столбец и какая функция его подают. Шаблон new_name=('column', 'function').
df.groupby('region').agg(
avg_amount=('amount', 'mean'),
orders=('amount', 'size'),
)
avg_amount orders
region
East 158.000000 5
South 83.333333 3
West 186.666667 4
Названная форма стоит на несколько символов больше и платит дважды. Выходные столбцы называются avg_amount и orders вместо mean и size, поэтому результат можно прочитать без кода рядом с ним. И каждая статистика явно указывает свой входной столбец, поэтому добавление второго столбца значений в таблицу позже не может незаметно изменить то, что агрегируется.
4. размер по количеству, и какой из них отвечает на ваш вопрос
Перед пояснением: в таблице двумя секциями назад Уэст показывает размер 4 и количество 3. Оба утверждают, что считают. Решите, что каждый из них посчитал, прежде чем читать ответ.
size подсчитывает строки в группе, отсутствуют они или нет. У Запада четыре ряда порядка, поэтому размер равен 4. count подсчитывает непропущенные значения в выбранном столбце. Четыре строки Уэста включают заказ 1008 с пропущенной суммой, поэтому счетчик показывает 3. Одно пропущенное значение — это весь разрыв между двумя ответами.
Это точное разделение SQL между COUNT(*) и COUNT(col): size является COUNT(*), строки в ведре и count является COUNT(col), значения представлены в одном столбце. Ни один из них не является правильным в целом; они отвечают на разные вопросы. «Сколько заказов разместил Уэст?» - это вопрос размера: 4. «Сколько западных заказов есть сумма, которую я могу подсчитать?» это вопрос на счет: 3. Ошибка не столько в том, чтобы выбрать неправильную функцию, сколько в том, чтобы не заметить, что они могут отличаться, потому что на чистых столбцах они сходятся и формируется привычка, что так будет всегда.
Прежде чем читать дальше, скажите в одном предложении, почему среднее значение Уэста делится на 3, а не на 4. Это тот же факт, но в другой шляпе: агрегации пропускают пропущенные значения, поэтому знаменателем среднего является количество, а не размер. Среднее значение по столбцу, в котором отсутствует одна четверть, представляет собой утверждение о трех четвертях, которые ответили.
5. Два группирующих столбца, Мультииндекс и возвращение плоской таблицы.
Передайте список столбцов, и уровень детализации станет более мелким: одна строка на регион на категорию.
df.groupby(['region', 'category'])['amount'].mean()
region category
East Chairs 100.0
Desks 275.0
Lamps 40.0
South Chairs 100.0
Lamps 50.0
West Chairs 100.0
Desks 400.0
Lamps 60.0
Name: amount, dtype: float64
Восток Стулья вручную: (120 + 80) ÷ 2 = 100. Восточные столы: (300 + 250) ÷ 2 = 275. Схема лестницы слева представляет собой Мультииндекс: групповые ключи стали двухуровневым индексом строки, а не обычными столбцами. Он хорошо печатает, а затем начинает ссориться в тот момент, когда вы пытаетесь объединить, отфильтровать, вывести на печать или экспортировать, потому что region больше не является столбцом, на который можно просто сослаться.
Два способа получить простой плоский стол, и заканчиваются они в одном месте. Попросите об этом заранее с as_index=False, который сохраняет ключи как обычные столбцы, или впоследствии восстановите его с помощью .reset_index(), который перемещает уровни индекса обратно в столбцы.
df.groupby(['region', 'category'], as_index=False)['amount'].mean()
region category amount
East Chairs 100.0
East Desks 275.0
East Lamps 40.0
South Chairs 100.0
South Lamps 50.0
West Chairs 100.0
West Desks 400.0
West Lamps 60.0
Также стоит отметить: на юге вообще нет строки «Парты», нет нуля. Комбинации без строк просто никогда не образуют группу, точно так же, как отфильтрованная группа теряется в SQL. Если известная вам категория отсутствует в сгруппированном результате, строки, стоящие за ней, никогда не попали в разделение.
6. Проблема малых групп, перенесенная из SQL
Рейтинги групповых средних показателей имеют структурную предвзятость: самые маленькие группы плавают в крайностях, потому что среднее значение более трех значений колеблется гораздо свободнее, чем среднее значение более трех тысяч. Исправление из руководства по SQL остается в силе: всегда вычисляйте размер группы рядом со статистикой и устанавливайте нижний предел, прежде чем читать рейтинг.
summary = df.groupby('region').agg(
avg_amount=('amount', 'mean'),
orders=('order_id', 'size'),
)
summary[summary['orders'] >= 4]
avg_amount orders
region
East 158.000000 5
West 186.666667 4
Юг, с его тремя порядками со средним значением 83,33, исключен из сравнения, а не может выглядеть как стабильный результат. Строка логического фильтра принадлежит pandas. HAVING: он выполняется на основе объединенного результата после существования групп, и это единственный раз, когда можно проверить условие для каждой группы. В четырнадцати рядах четвертый этаж явно демонстрационный; на реальных данных нижний предел — это суждение, которое вы должны выбрать из данных и записать, и Выбор порогов из данных рассказывает о том, как.
Теперь представьте, как вы запускаете строку именованного агрегирования в своей таблице, что бы это ни было: билеты по правопреемникам, доходы по клиентам, баллы по школам. Как вы уже подозреваете, в какой группе за уверенным средним показателем стоят однозначные ряды? Это подозрение является причиной orders= входит в каждый сгруппированный результат, который я отправляю.
7. Преобразование: групповая статистика в каждой строке.
Все до сих пор рухнуло на стол. Иногда вам нужно обратное: сохранить все исходные строки и прикрепить к каждой номер уровня группы, чтобы можно было вычислить долю каждой строки в ее группе. transform применяет функцию для каждой группы, а затем передает ответ обратно в каждую строку этой группы, поэтому результат имеет ту же длину, что и входные данные.
d2 = df.dropna(subset=['region', 'amount']).copy()
d2['region_total'] = d2.groupby('region')['amount'].transform('sum')
d2['share'] = d2['amount'] / d2['region_total']
order_id region category amount region_total share
1001 East Chairs 120.0 790.0 0.152
1002 East Chairs 80.0 790.0 0.101
1003 East Desks 300.0 790.0 0.380
1004 East Desks 250.0 790.0 0.316
1005 East Lamps 40.0 790.0 0.051
1006 West Chairs 100.0 560.0 0.179
1007 West Desks 400.0 560.0 0.714
1009 West Lamps 60.0 560.0 0.107
1010 South Chairs 90.0 250.0 0.360
1011 South Chairs 110.0 250.0 0.440
1012 South Lamps 50.0 250.0 0.200
В каждом восточном ряду общее количество заказов Востока составляет 790, в каждом западном ряду — 560, в каждом южном ряду — 250. Доля заказа 1007 проверяется вручную: 400 ÷ 560 = 0,714, поэтому один кассовый заказ составляет 71% расходов Запада. Эмпирическое правило: agg когда результат составляет одну строку на группу, transform когда результатом являются исходные строки с прикрепленным групповым фактом. В SQL то же самое перемещение строк является оконной функцией.
В первой строке специально и вслух перед разделением пропадали строки с пропущенным регионом или суммой. Это идеальный переход.
8. Исчезающие строки: водянка
Перед пояснением: в нашей таблице 14 строк, а размеры регионов в третьем разделе были 5 + 3 + 4 = 12. До сих пор в каждом сгруппированном результате на этой странице отсутствовали две строки. Какие два?
Заказы 1013 и 1014, те, у которых отсутствует регион. По умолчанию groupby бежит с dropna=True, что означает, что строки, у которых отсутствует групповой ключ, исключаются еще до того, как произойдет разделение. Это не группа, которая отображается пустой; их просто нет, и никаких предупреждений не выводится. На этой странице это 2 строки по 14, содержащие 260 + 45 = 305 продаж. В реальной таблице это может составлять пятую часть вашего дохода, отсутствующую в каждом региональном отчете, при этом каждое напечатанное число остается верным в отдельности.
Один аргумент представляет их как отдельную группу.
df.groupby('region', dropna=False)['amount'].mean()
region
East 158.000000
South 83.333333
West 186.666667
NaN 152.500000
Name: amount, dtype: float64
Среднее число проверок группы НЭН вручную: (260 + 45) ÷ 2 = 305 ÷ 2 = 152,5. Эта строка не является ошибкой, которую нужно скрывать. Это измерение недостающих данных, и честным шагом будет сообщить об этом: «Два заказа, 305 продаж, не имеют зарегистрированного региона». Негласные исключения — это именно то, что следует писать рядом с результатом, и Документирование ограничений данных это руководство о том, как это сказать. Моя привычка — запускать dropna=False версию один раз для каждого ключа, по которому я группирую, прочитайте строку НЭН, а затем решите, что с этим делать, вместо того, чтобы позволять значению по умолчанию решать за меня.
Полное до и после
Та же таблица из 14 строк, тот же вопрос: как регионы сравниваются по стоимости заказа?
До
df.groupby('region')['amount'].mean()
Три проблемы, ни одна из которых не приводит к ошибке. Размер группы отсутствует, поэтому показатель Юга 83,33 по трем порядкам читается так же уверенно, как 158 у Востока по пяти. Целых две строки отсутствуют, потому что их область пуста, и об этом ничего не говорится. В результате получается серия с индексным ключом, о которой следующий шаг будет спотыкаться.
после
# One row per region, sizes shown, missing keys surfaced, flat table, sorted.
result = (
df.groupby('region', dropna=False, as_index=False)
.agg(avg_amount=('amount', 'mean'),
orders=('amount', 'size'))
.sort_values('avg_amount', ascending=False)
)
region avg_amount orders
West 186.666667 4
East 158.000000 5
South 83.333333 3
NaN 152.500000 2
(Строка НЭН появляется из-за dropna=False; pandas сохраняет его в конце сортировки.) Теперь учитывается каждая из 14 входных строк: 4 + 5 + 3 + 2 = 14. Читатель может видеть, что среднее значение Юга составляет 3 порядка, а 2 порядка вообще не имеют региона. sort_values('avg_amount', ascending=False) результат упорядочивается по статистике, начиная с самой высокой, поскольку таблица сравнения должна быть отсортирована по сравниваемому объекту, а не по алфавиту.
Пограничные случаи, которые нарушают сгруппированные результаты в реальных данных
Пять, каждая из которых стоила кому-то дня.
Ключевой столбец содержит почти повторяющиеся значения. 'East' и 'East ' с пробелом в конце — это две разные группы, и в вашем региональном отчете незаметно увеличивается четвертый регион. Нормализовать ключи перед группировкой, df['region'].str.strip() как минимум. Когда дубликаты более беспорядочны, чем пробелы, то есть Разрешение объекта.
Группа, все значения которой отсутствуют, возвращает НЭН, а не ноль. Если каждая сумма в группе отсутствует, sum возвращает 0, но mean возвращает НЭН, а НЭН в столбце отчета имеет тенденцию быть «фиксированным» до нуля тем, кто его форматирует. Группа без измеримых значений и группа со средним значением 0 — это разные результаты.
Группировка после слияния учитывает дубликаты. Если восходящее соединение объединяет несколько строк, size теперь считает пары, а не заказы. На этой странице постоянно используется проверка: добавляются ли размеры групп к ожидаемому количеству строк?
Мультииндекс сохраняется при экспорте. Запишите результат, сгруппированный двумя ключами, прямо в CSV, и индекс лестницы станет пустыми ячейками под region, которые пользователи Excel будут считать отсутствующими данными. Сгладить с помощью as_index=False или reset_index() прежде чем что-либо покинет Питон.
Сортировка по индексу, когда вы имели в виду значения. sort_index() упорядочивает по названию группы в алфавитном порядке. sort_values('avg_amount') заказы по номеру. Рейтинг, отсортированный по алфавиту, по-прежнему выглядит отсортированным, поэтому его легко пропустить.
Почему это работает
Разделить-применить-объединить — это не просто то, как создаются pandas; это именованная общая стратегия анализа данных. В описании Уикхэма, написанном для мира R, утверждается, что огромная часть практического анализа заключается именно в этом шаблоне: разбить проблему на части по ключу, работать с каждой частью независимо, снова собрать части вместе, и что присвоение имени шаблону позволяет распознавать его в разных инструментах (Уикхэм, 2011, Журнал статистического программного обеспечения, 40(1), 1–29). Вот почему эта страница продолжала указывать на SQL: groupby, GROUP BY, сводные таблицы Excel и оконные функции — это одна стратегия, использующая четыре синтаксиса, и вместе с ней передается зернистость. Теория, лежащая в ее основе, еще старше: группировка — это разделение строк на непересекающиеся наборы, а агрегат — это функция от набора к одному значению, что представляет собой реляционную основу SQL, формализованную с самого начала (Кодд, 1970, Коммуникации ACM, 13(6), 377–387). Как только вы увидите сгруппированный результат в виде раздела, странности страницы перестанут быть причудами: отброшенный ключ НЭН — это строка, не назначенная ни одному набору, а размер по сравнению со счетчиком — это размер набора по сравнению со значениями, присутствующими в одном из его атрибутов.
И есть причина, по которой эта страница продолжала просить вас дать ответ, где находятся недостающие строки, какой размер учитывался, прежде чем дать его. Попытка дать ответ до того, как вы увидите правильный, достоверно повышает, насколько хорошо сохраняется правильный ответ, даже если попытка неверна (Бисра, Лю, Несбит, Салими и Винн, 2018, Обзор педагогической психологии, 30(3), 703–725). 5 + 3 + 4 = 12 арифметических палочек, потому что вас попросили найти пробел до того, как он был назван.
Использование этого в вашем собственном проекте
Переделывать каждую сгруппированную строку в старом блокноте – занятие унылое, и вы остановитесь на третьей ячейке. Вместо этого сделайте это по порядку.
- Скажи вслух новое уровень детализации прежде чем ввести: «одна строка на регион в месяц». Он определяет список ключей и предупреждает вас, что перестает существовать.
- Использовать именованную агрегацию по умолчанию, даже для одной статистики. Будущее-ты получает имена столбцов, которые объясняют сами себя.
- Поставьте
sizeрядом с каждой статистикойи проверьте, что размеры в сумме соответствуют количеству строк, с которого вы начали. Проверка 5 + 3 + 4 = 12 на этой странице — это весь метод. - Запустите каждую клавишу один раз с помощью
dropna=Falseи прочитайте строку НЭН, прежде чем решить, следует ли ее исключить, заполнить или сообщить о ней. - Сгладить, прежде чем что-либо покинет Python:
as_index=Falseилиreset_index(), затемsort_valuesпо статистике.
Если у вас под рукой есть бумага, один дополнительный рисунок стоит пяти минут: перерисуйте по памяти картинку «разделить-применить-объединить» для своей таблицы, семь рядов, три штриховки, один пунктирный ряд с недостающим ключом и отметить, куда идет этот пунктирный ряд. Правильное размещение пропущенной строки, до разделения, а не после, — это весь урок дропны одним росчерком пера.
Все на одном экране
Это лист поиска. Закройте правую колонку, проработайте левую и произнесите каждый ответ вслух, прежде чем проверять его.
| концепция | Что он делает |
|---|---|
| разделить-применить-объединить | Одна мини-таблица для каждой клавиши, один вызов функции для каждой мини-таблицы, ответы складываются в одну строку на группу. |
| Уровень детализации после группировки | Одна строка на группу, а не одна строка на запись. Скажите это, прежде чем печатать. |
df.groupby('k')['v'].mean() | Основной ход: ключ, столбец значений, статистика. |
agg(['mean','sum']) | Несколько статистических данных на группу за один проход. |
| именованная агрегация | agg(avg=('v','mean'), n=('v','size')). Ваши имена, явные данные. |
size | Строки в группе, включая отсутствующие. SQL COUNT(*). |
count | Непропущенные значения в одном столбце. SQL COUNT(col). Может быть меньше. |
| Агрегации и НЭН | среднее, сумма и друзья пропускают пропущенные значения. Знаменатель – это количество, а не размер. |
| Несколько ключей | groupby(['a','b']): более мелкое уровень детализации, результат содержит Мультииндекс. |
| Плоская спинка стола | as_index=False вперед, или .reset_index() после. |
| Маленькие группы | Отправьте столбец размера, фильтр summary[summary['n'] >= floor]. pandas имеют. |
transform | Групповая статистика транслируется обратно в каждую исходную строку. Для акций и сравнений с группой. |
agg против transform | агг сворачивается до одной строки на группу; трансформировать сохраняет каждую строку. |
| Отсутствующие ключи | По умолчанию выпадает перед разделением. dropna=False сохраняет их как группу НаН. |
| Отсутствует комбинация | Отсутствие строк означает отсутствие группы вообще, а не нулевую строку. |
| Проверка сверки | Размеры групп должны добавляться к количеству входных строк. Здесь: 5+3+4+2=14. |
sort_values | Упорядочивайте результат по статистике, а не по алфавиту. |
Еще одна мысль, и мне бы искренне хотелось услышать ответы других людей. Когда я впервые запускал региональный отчет, в нем просто не было строк без региона, и я обнаружил, что они сверяются с несовпадающей общей суммой финансов только несколько недель спустя. Что вы обнаружили в своей группе НЭН и как долго она там существует?
Ссылки
- Уикхэм, Х. (2011). Стратегия «разделить-применить-объединить» для анализа данных. Журнал статистического программного обеспечения, 40(1), 1–29.
- Кодд, EF (1970). Реляционная модель данных для крупных общих банков данных. Коммуникации ACM, 13(6), 377–387.
- Бисра К., Лю К., Несбит Дж. К., Салими Ф. и Винн П. Х. (2018). Побуждение к самообъяснению: метаанализ. Обзор педагогической психологии, 30(3), 703–725.
Python для аналитиков составляет 203 страницы, группировка, агг и сброс_индекс читаются построчно, с выводом на каждом этапе.
Python для аналитиков, 19 $ →тот Курс Python дает вам повторы: проработанные примеры pandas, флэш-карточки с точно такими же различиями по размеру, количеству и количеству капель с этой страницы, а также пробный экзамен, который просит вас спрогнозировать результаты перед их запуском. Теперь вы владеете методом; комплект - это практика. Ничего устанавливать, учетная запись не требуется.
Откройте комплект Python →