The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Sum by grouptotals = df.groupby('region')['revenue'].sum()View examples
Keep group columnstotals = df.groupby('region', as_index=False)['revenue'].sum()View examples
Group by several keystotals = df.groupby(['region', 'product'], as_index=False)['revenue'].sum()View examples
Name aggregate columnssummary = df.groupby('region').agg(total=('revenue', 'sum'), average=('revenue', 'mean'))View examples
Count group rowscounts = df.groupby('region').size()View examples
Count present valuescounts = df.groupby('region')['revenue'].count()View examples
Broadcast group totalsdf['region_total'] = df.groupby('region')['revenue'].transform('sum')View examples
Calculate group sharedf['share'] = df['revenue'] / df.groupby('region')['revenue'].transform('sum')View examples
Calculate grouped running totalsdf['running'] = df.groupby('account')['amount'].cumsum()View examples
Keep qualifying groupsresult = df.groupby('region').filter(lambda group: len(group) >= 2)View examples
Pivot unique coordinateswide = df.pivot(index='date', columns='metric', values='value')View examples
Aggregate a pivot tablewide = df.pivot_table(index='region', columns='product', values='revenue', aggfunc='sum', fill_value=0)View examples
Unpivot wide columnslong = df.melt(id_vars='id', var_name='metric', value_name='value')View examples
Explode list valueslong = df.explode('tags', ignore_index=True)View examples
Build a cross-tabulationcounts = pd.crosstab(df['region'], df['status'], margins=True)View examples

GroupBy splits records by keys and then aggregates, transforms, or filters them. Reshaping changes how dimensions are represented; choose pivot for unique coordinates, pivot_table when duplicate coordinates require aggregation, and melt when columns should become observations.

Step by step

Detailed examples

01

Split records using explicit group keys

Grouping by one or more columns forms a group for each distinct key combination. Select the value columns before reducing to keep the output narrow. By default group keys become index levels; as_index=False is convenient when the result will continue through column-oriented operations. Missing group keys are excluded unless dropna=False is requested deliberately.

Summarize revenue by region and product
import pandas as pd

sales = pd.DataFrame({
    'region': ['north', 'north', 'south', 'south'],
    'product': ['book', 'pen', 'book', 'book'],
    'revenue': [120, 30, 90, 60],
})

by_region = sales.groupby('region')['revenue'].sum()
region_columns = sales.groupby('region', as_index=False)['revenue'].sum()
by_product = sales.groupby(['region', 'product'], as_index=False)['revenue'].sum()

print(by_region.to_dict())
print(region_columns.to_dict('records'))
print(by_product.to_dict('records'))
Output
{'north': 150, 'south': 150}
[{'region': 'north', 'revenue': 150}, {'region': 'south', 'revenue': 150}]
[{'region': 'north', 'product': 'book', 'revenue': 120}, {'region': 'north', 'product': 'pen', 'revenue': 30}, {'region': 'south', 'product': 'book', 'revenue': 150}]
Back to quick reference ↑
02

Produce stable aggregate schemas

Named aggregation defines the input column, function, and output label together, avoiding ambiguous MultiIndex columns. size counts rows, while count excludes missing values in the selected column. Prefer pandas' built-in reduction names over Python callbacks where possible because built-ins communicate intent and usually use optimized implementations.

Compare row counts and observed-value counts
import pandas as pd

sales = pd.DataFrame({
    'region': ['north', 'north', 'south'],
    'revenue': [120.0, None, 90.0],
})

summary = sales.groupby('region').agg(
    total=('revenue', 'sum'),
    average=('revenue', 'mean'),
)
row_counts = sales.groupby('region').size()
value_counts = sales.groupby('region')['revenue'].count()

print(summary.to_dict('index'))
print(row_counts.to_dict())
print(value_counts.to_dict())
Output
{'north': {'total': 120.0, 'average': 120.0}, 'south': {'total': 90.0, 'average': 90.0}}
{'north': 2, 'south': 1}
{'north': 1, 'south': 1}
Back to quick reference ↑
03

Transform rows or filter whole groups

transform returns a result aligned to the original index, making it suitable for group totals, normalization, and filling. Cumulative methods respect current row order, so sort by the domain sequence first. GroupBy.filter keeps or removes complete groups based on a scalar decision; built-in transformations and boolean masks are generally preferable to custom callbacks for performance-sensitive workloads.

Add shares and running account balances
import pandas as pd

df = pd.DataFrame({
    'account': ['a', 'b', 'a', 'b'],
    'region': ['north', 'south', 'north', 'south'],
    'sequence': [1, 1, 2, 2],
    'revenue': [40, 30, 60, 70],
    'amount': [40, 30, -10, 20],
}).sort_values(['account', 'sequence'])

df['region_total'] = df.groupby('region')['revenue'].transform('sum')
df['share'] = df['revenue'] / df['region_total']
df['running'] = df.groupby('account')['amount'].cumsum()
qualified = df.groupby('region').filter(lambda group: len(group) >= 2)

print(df[['account', 'share', 'running']].to_dict('records'))
print(len(qualified))
Output
[{'account': 'a', 'share': 0.4, 'running': 40}, {'account': 'a', 'share': 0.6, 'running': 30}, {'account': 'b', 'share': 0.3, 'running': 30}, {'account': 'b', 'share': 0.7, 'running': 50}]
4
Back to quick reference ↑
04

Choose pivot or pivot_table based on uniqueness

pivot only reshapes and therefore requires one value for each index-column coordinate; duplicates raise an error instead of being silently combined. pivot_table explicitly aggregates duplicates and can fill empty cells. Select an aggregation function that matches the measure—sum for additive quantities, for example—and avoid filling missing cells with zero unless absence truly means zero.

Reshape unique metrics and aggregate sales
import pandas as pd

metrics = pd.DataFrame({
    'date': ['2026-08-01', '2026-08-01', '2026-08-02', '2026-08-02'],
    'metric': ['views', 'sales', 'views', 'sales'],
    'value': [100, 4, 120, 5],
})
wide_metrics = metrics.pivot(index='date', columns='metric', values='value')

sales = pd.DataFrame({
    'region': ['north', 'north', 'south'],
    'product': ['book', 'book', 'pen'],
    'revenue': [40, 60, 30],
})
wide_sales = sales.pivot_table(
    index='region', columns='product', values='revenue', aggfunc='sum', fill_value=0,
)

print(wide_metrics.to_dict('index'))
print(wide_sales.to_dict('index'))
Output
{'2026-08-01': {'sales': 4, 'views': 100}, '2026-08-02': {'sales': 5, 'views': 120}}
{'north': {'book': 100, 'pen': 0}, 'south': {'book': 0, 'pen': 30}}
Back to quick reference ↑
05

Normalize repeated dimensions into rows

melt converts selected wide columns into a variable-value pair while preserving identifier columns. explode expands list-like values and repeats other fields; empty list-likes produce a missing value, and sets do not guarantee output order. crosstab is a concise frequency table for categorical combinations, with optional margins for totals.

Melt measurements, explode tags, and count states
import pandas as pd

wide = pd.DataFrame({'id': [1, 2], 'height': [170, 165], 'weight': [65, 58]})
long = wide.melt(id_vars='id', var_name='metric', value_name='value')

items = pd.DataFrame({
    'id': [1, 2],
    'tags': [['new', 'sale'], ['sale']],
    'region': ['north', 'south'],
    'status': ['open', 'closed'],
})
tags = items.explode('tags', ignore_index=True)
counts = pd.crosstab(items['region'], items['status'], margins=True)

print(long.to_dict('records'))
print(tags[['id', 'tags']].to_dict('records'))
print(counts.loc['All'].to_dict())
Output
[{'id': 1, 'metric': 'height', 'value': 170}, {'id': 2, 'metric': 'height', 'value': 165}, {'id': 1, 'metric': 'weight', 'value': 65}, {'id': 2, 'metric': 'weight', 'value': 58}]
[{'id': 1, 'tags': 'new'}, {'id': 1, 'tags': 'sale'}, {'id': 2, 'tags': 'sale'}]
{'closed': 1, 'open': 1, 'All': 2}
Back to quick reference ↑

Local code tester

Summarize and reshape sales

Change the records and compare a named aggregation with a pivot table.

Runs in your browser
Output
Press Run to load Python locally.

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. pandas development teamGroup by: split-apply-combinepandas.pydata.org
  2. pandas development teamReshaping and pivot tablespandas.pydata.org
  3. pandas development teamGroupBypandas.pydata.org
  4. pandas development teampandas.DataFrame.pivot_tablepandas.pydata.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback