GroupBy & Aggregation
pandas groupby implements split-apply-combine: partition rows by keys, run a function per group, and stitch results into a summary or transformed frame.
Search across all documentation pages
pandas groupby implements split-apply-combine: partition rows by keys, run a function per group, and stitch results into a summary or transformed frame.
Quick-reference recipe card - copy-paste ready.
import pandas as pd
summary = (
df.groupby("region", observed=True)["revenue"]
.agg(total="sum", avg="mean", n="count")
.reset_index()
)
pivot = pd.pivot_table(df, index="month", columns="region", values="revenue", aggfunc="sum")When to reach for this:
import pandas as pd
sales = pd.DataFrame(
{
"region": pd.Categorical(["East", "West", "East", "West", "East"]),
"month": ["2025-01", "2025-01", "2025-02", "2025-02", "2025-02"],
"revenue": [120.0, 340.0, 150.0, 280.0, 130.0],
"units": [10, 25, 12, 20, 11],
}
)
# Multi-metric aggregation
by_region = (
sales.groupby("region", observed=True)
.agg(revenue_total=("revenue", "sum"), units_avg=("units", "mean"))
.reset_index()
)
# Per-row share of regional total (transform keeps row count)
sales = sales.copy()
sales["share"] = sales.groupby("region", observed=True)["revenue"].transform(
lambda s: s / s.sum()
)
# Pivot for reporting
pivot = pd.pivot_table(
sales,
index="month",
columns="region",
values="revenue",
aggfunc="sum",
fill_value=0,
observed=True,
)
print(by_region)
print(sales[["region", "revenue", "share"]])
print(pivot)What this demonstrates:
.agg(col=(field, func))transform for row-level group fractionspivot_table with fill_value for sparse monthsobserved=True on categorical keyssum, mean) or transform (rank, fillna).as_index=False (pandas 2.x default in many APIs) returns keys as columns.| Method | Output rows | Use |
|---|---|---|
agg | One per group | Summaries |
transform | Same as input | Per-row group stats |
apply | Varies | Custom Python logic (slower) |
import pandas as pd
# Multiple keys
df.groupby(["region", "month"], observed=True)["revenue"].sum()
# Filter groups by aggregate condition
df.groupby("region").filter(lambda g: g["revenue"].sum() > 500)dropna=False keeps NaN groups; often accidental. Fix: dropna=True default or clean keys first.observed=True. Fix: pass observed=True in pandas 2.x groupby/pivot.agg/transform; vectorize custom logic.aggfunc. Fix: choose sum, mean, or first explicitly..sort_values("revenue_total", ascending=False).| Alternative | Use When | Don't Use When |
|---|---|---|
Polars group_by | Faster lazy aggregations on big data | Small frames where pandas is already loaded |
| DuckDB SQL | Complex joins + aggregates in one query | Simple one-table rollups |
pd.crosstab | Frequency counts of two factors | Numeric revenue sums |
NumPy bincount | Integer bucket sums on 1-D data | Multiple columns and NA handling |
df.groupby("region")["customer_id"].nunique()df["rank"] = df.groupby("region")["revenue"].rank(ascending=False)df.groupby("region").agg({"revenue": "sum", "units": "max"})apply per group is the usual culprit.agg, Polars, or DuckDB for millions of rows.out = df.groupby("region").sum().reset_index().agg(total=("revenue", "sum")) names output columns directly..agg(["sum", "mean"]).df.groupby(pd.Grouper(key="date", freq="ME")) for month-end buckets.df.groupby("region")["revenue"].sum().plot(kind="bar")Grouper and resampleStack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 19, 2026