Как группировать данные в pandas

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Базовый синтаксис

df.groupby('category')['amount'].sum()

Аналог SQL:

SELECT category, SUM(amount) FROM df GROUP BY category;

1. Группировка по одной колонке

import pandas as pd

df = pd.DataFrame({
    'category': ['A', 'B', 'A', 'B', 'A'],
    'amount':   [100, 200, 150, 250, 120]
})

# сумма
df.groupby('category')['amount'].sum()
# A    370
# B    450

# среднее
df.groupby('category')['amount'].mean()

# максимум
df.groupby('category')['amount'].max()

2. По нескольким колонкам

df.groupby(['country', 'city'])['revenue'].sum()

3. Несколько агрегаций одновременно

df.groupby('category')['amount'].agg(['sum', 'mean', 'count'])

Или с именованными:

df.groupby('category').agg(
    total=('amount', 'sum'),
    average=('amount', 'mean'),
    transactions=('amount', 'count')
)

4. Разные агрегации на разных колонках

df.groupby('category').agg({
    'amount': 'sum',
    'qty':    'mean',
    'user_id': 'nunique'
})

5. Custom аггрегации

df.groupby('category')['amount'].agg(lambda x: x.quantile(0.95))

# или более сложно
def iqr(x):
    return x.quantile(0.75) - x.quantile(0.25)

df.groupby('category')['amount'].agg(iqr)

6. transform — вернуть тот же размер

Добавить колонку «среднее по группе» в каждую строку:

df['mean_per_category'] = df.groupby('category')['amount'].transform('mean')

Теперь в каждой строке — среднее её категории. Без groupby — 5 строк. С transform — 5 строк.

7. apply — более гибко, но медленно

df.groupby('category').apply(lambda g: g.sort_values('amount').head(3))
# топ-3 самых маленьких в каждой категории

Apply медленнее agg/transform — используйте, только если нужно нечто сложное.

8. reset_index после groupby

# по умолчанию groupby возвращает Series с MultiIndex
g = df.groupby(['a', 'b'])['c'].sum()  # MultiIndex Series

# в плоский DataFrame
g = g.reset_index()

Или сразу as_index=False:

df.groupby(['a', 'b'], as_index=False)['c'].sum()

9. groupby + filter

Оставить только группы, соответствующие условию:

# категории с >100 записей
df.groupby('category').filter(lambda g: len(g) > 100)

10. Топ-N в каждой группе

df.sort_values(['category', 'amount'], ascending=[True, False]) \
  .groupby('category').head(3)

11. Running total в группе

df['running_total'] = df.sort_values('date') \
    .groupby('category')['amount'].cumsum()

12. Pivot-like — unstack

sales = df.groupby(['month', 'category'])['amount'].sum()
sales.unstack('category')
# строки — month, колонки — category

Или через pd.pivot_table:

pd.pivot_table(df, values='amount', index='month', columns='category', aggfunc='sum')

13. group_keys=False для apply

Избежать лишнего индекса:

df.groupby('category', group_keys=False).apply(lambda g: g.nlargest(3, 'amount'))

14. Groupby и NaN

По умолчанию NaN-группы пропускаются:

df.groupby('category', dropna=False)['amount'].sum()
# включит NaN-категорию

Частые ошибки

1. Забыть колонку

df.groupby('category').sum()
# суммирует ВСЕ числовые колонки, не только нужные

Правильно:

df.groupby('category')['amount'].sum()

2. transform vs agg

  • agg возвращает n_groups строк
  • transform возвращает len(df) строк

Ошибка — использовать agg там, где нужен transform.

3. Медленный apply

Для простых агрегаций (sum, mean) используйте встроенные — они vectorized.

4. Забыть reset_index

После groupby индекс — ключ группировки. Для дальнейшей работы может понадобиться reset.

Связанные темы

FAQ

Groupby или pivot_table?

Pivot_table — удобнее для «широкого» представления с несколькими колонками. Groupby — универсальнее.

transform быстрее apply?

Да, transform векторизован. Apply — построчно.

Как сохранить только группы с >N?

.filter(lambda g: len(g) > N).

Можно ли несколько groupby цепочкой?

Да. Часто используют для многомерной агрегации.


Тренируйте pandas — откройте тренажёр с 1500+ вопросами для собесов.