Group By to Pivot Table – Converting GroupBy Results to Pivot Tables in Pandas 2026
Many data manipulation tasks start with a groupby() and then need to be reshaped into a pivot table format for reporting or visualization. In 2026, understanding how to smoothly transition from GroupBy aggregations to beautiful pivot tables is a highly valuable skill.
TL;DR — Two Main Approaches
- Do aggregation with
groupby().agg()→ then.unstack() - Use
pivot_table()directly (often cleaner and more flexible)
1. Starting with GroupBy and Converting to Pivot
import pandas as pd
# Create a small demo file so this example runs as-is
# (in your project, sales_data.csv is your real export)
import numpy as np
rng = np.random.default_rng(42)
n = 60
pd.DataFrame({
"order_date": pd.date_range("2025-10-01", periods=n, freq="3D"),
"customer_id": rng.integers(101, 121, n),
"customer_name": rng.choice([" ana", "ben ", "chen", "dana"], n),
"product_id": rng.integers(1, 9, n),
"region": rng.choice(["North", "South", "East", "West"], n),
"category": rng.choice(["Electronics", "Books", "Home"], n),
"payment_method": rng.choice(["card", "cash", "wallet"], n),
"segment": rng.choice(["consumer", "business"], n),
"quantity": rng.integers(1, 6, n),
"amount": rng.uniform(50, 2500, n).round(2),
}).assign(
ship_date=lambda d: d["order_date"] + pd.Timedelta(days=2),
status=lambda d: np.where(d["amount"] > 2000, "priority", "standard"),
).to_csv("sales_data.csv", index=False)
df = pd.read_csv("sales_data.csv", parse_dates=["order_date"])
# Helper columns: pivot_table() works best with real column names
df["month"] = df["order_date"].dt.to_period("M")
df["year"] = df["order_date"].dt.year
# Step 1: GroupBy aggregation
grouped = (
df
.groupby(["region", "month"])
.agg(total_sales=("amount", "sum"))
.reset_index()
)
# Step 2: Convert to Pivot Table format
pivot = grouped.pivot(
index="region",
columns="month",
values="total_sales"
).round(2)
print(pivot)
2. Cleaner Approach: Direct pivot_table() (Recommended)
# Much cleaner and more powerful
pivot = pd.pivot_table(
df,
values="amount",
index="region",
columns="month",
aggfunc="sum",
margins=True,
margins_name="Total"
).round(2)
print(pivot)
3. Advanced Example: Multiple Metrics in Pivot Form
report = pd.pivot_table(
df,
values=["amount", "quantity"],
index=["region", "category"],
columns="month",
aggfunc={
"amount": ["sum", "mean"],
"quantity": "sum"
},
# no margins here: margins=True fails with a dict of function lists in pandas 3.1
).round(2)
print(report)
4. Best Practices in 2026
- Use
pivot_table()directly when possible — it is usually cleaner than groupby + unstack - Use
groupby().agg()+.unstack()only when you need very custom logic before pivoting - Extract date components with
.dt.to_period()for clean month/year columns - Add
margins=Trueto get grand totals automatically - Use named aggregation inside
pivot_table()when you need multiple metrics
GroupBy and Pivot Tables Are Two Views of One Result
df.groupby(["a", "b"])["x"].sum().unstack() and df.pivot_table(index="a", columns="b", values="x", aggfunc="sum") produce the same grid. The groupby version is flexible and reads well in a method chain; the pivot version states the final layout in one call and adds conveniences such as fill_value and margins. Knowing both lets you switch freely: start with groupby while exploring, then move to pivot_table() for the final report.
Worked Example: Proving the Two Are Equal
This example builds the same table both ways and checks that they match.
import pandas as pd
df = pd.DataFrame({
"region": ["East", "East", "West", "West", "West"],
"channel": ["web", "shop", "web", "web", "shop"],
"amount": [10, 20, 30, 40, 50],
})
via_groupby = df.groupby(["region", "channel"])["amount"].sum().unstack(fill_value=0)
via_pivot = df.pivot_table(index="region", columns="channel", values="amount",
aggfunc="sum", fill_value=0)
print(via_pivot)
print(via_groupby.equals(via_pivot)) # True
Both grids show East with 20 in shop and 10 in web, and West with 50 and 70. When the check prints False, compare dtypes first; a missing combination can turn integers into floats in one version but not the other.
Common Mistakes
- Forgetting
unstack(): a two-key groupby returns a long Series, not a grid - Unstacking the wrong level: pass the level name, for example
unstack("channel") - Different fill rules: use the same
fill_valuein both approaches - Expecting a pivot to reshape without aggregating: use
pivot()when each pair appears only once
Quick Summary
- groupby + unstack equals pivot_table
- Explore with groupby, report with pivot_table
- Compare results with
equals()and check dtypes
Conclusion
Converting GroupBy results into pivot table format is a very common workflow in data manipulation. In 2026, the recommended approach is to use pd.pivot_table() directly whenever possible because it is more concise, flexible, and readable than doing a groupby followed by unstack. Mastering both techniques gives you full control over how you reshape and present your data.
Next steps:
- Take a GroupBy summary you currently use and convert it into a clean pivot table using
pivot_table()