The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Sum by group | totals = df.groupby('region')['revenue'].sum() | View examples |
| Keep group columns | totals = df.groupby('region', as_index=False)['revenue'].sum() | View examples |
| Group by several keys | totals = df.groupby(['region', 'product'], as_index=False)['revenue'].sum() | View examples |
| Name aggregate columns | summary = df.groupby('region').agg(total=('revenue', 'sum'), average=('revenue', 'mean')) | View examples |
| Count group rows | counts = df.groupby('region').size() | View examples |
| Count present values | counts = df.groupby('region')['revenue'].count() | View examples |
| Broadcast group totals | df['region_total'] = df.groupby('region')['revenue'].transform('sum') | View examples |
| Calculate group share | df['share'] = df['revenue'] / df.groupby('region')['revenue'].transform('sum') | View examples |
| Calculate grouped running totals | df['running'] = df.groupby('account')['amount'].cumsum() | View examples |
| Keep qualifying groups | result = df.groupby('region').filter(lambda group: len(group) >= 2) | View examples |
| Pivot unique coordinates | wide = df.pivot(index='date', columns='metric', values='value') | View examples |
| Aggregate a pivot table | wide = df.pivot_table(index='region', columns='product', values='revenue', aggfunc='sum', fill_value=0) | View examples |
| Unpivot wide columns | long = df.melt(id_vars='id', var_name='metric', value_name='value') | View examples |
| Explode list values | long = df.explode('tags', ignore_index=True) | View examples |
| Build a cross-tabulation | counts = 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
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.
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')) {'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}]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.
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()) {'north': {'total': 120.0, 'average': 120.0}, 'south': {'total': 90.0, 'average': 90.0}}
{'north': 2, 'south': 1}
{'north': 1, 'south': 1}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.
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)) [{'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}]
4Choose 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.
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')) {'2026-08-01': {'sales': 4, 'views': 100}, '2026-08-02': {'sales': 5, 'views': 120}}
{'north': {'book': 100, 'pen': 0}, 'south': {'book': 0, 'pen': 30}}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.
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()) [{'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}Local code tester
Summarize and reshape sales
Change the records and compare a named aggregation with a pivot table.
Press Run to load Python locally.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



