Split rows into groups, apply a function to each group, and combine the results back together.
groupby is the method you reach for whenever a question contains the words "for each" — total sales for each region, average temperature for each month, number of plants for each country. If you find yourself writing a loop over the unique values of a column, groupby is almost certainly the answer you actually want.
Official documentation: DataFrame.groupby
The shape of a groupby
Every groupby has three parts: the column you group by, the column(s) you want to summarize, and the aggregation method that does the summarizing.
(
df
.groupby('Country') # split: one group per country
['Capacity (MW)'] # the column we want to summarize
.sum() # apply + combine
)
That returns a series, indexed by country. The four forms worth knowing:
# One value column, one aggregation
df.groupby('Country')['Capacity (MW)'].sum()
# Several value columns at once
df.groupby('Country')[['Capacity (MW)', 'Annual CO2 (million tonnes / annum)']].sum()
# Several grouping keys -- gives a MultiIndex
df.groupby(['Region', 'Status'])['Capacity (MW)'].mean()
# Different aggregations per column, with names you choose
df.groupby('Country').agg(
total_capacity=('Capacity (MW)', 'sum'),
plant_count=('Capacity (MW)', 'size'),
biggest=('Capacity (MW)', 'max'),
)
A worked example, on real data
Here is a question from Bamboo Weekly #64, using the Global Coal Plant Tracker — a real spreadsheet of every coal-fired generating unit on earth.
We load it, keeping only the columns we need:
import pandas as pd
url = ('https://www.bambooweekly.com/content/files/wp-content/uploads/2024/02/'
'global-coal-plant-tracker-january-2024.xlsx')
df = pd.read_excel(url, sheet_name='Units',
usecols=['Country', 'Capacity (MW)', 'Status',
'Start year', 'Combustion technology',
'Coal type', 'Region',
'Annual CO2 (million tonnes / annum)'])
The question: which countries emit the most CO2 from coal?
The data has one row per generating unit, and many units per country. So we group by country and sum the CO2 column:
(
df
.groupby('Country')
['Annual CO2 (million tonnes / annum)']
.sum()
)
That works, but the result comes back sorted alphabetically by country, which tells us nothing. We want the worst offenders first, so we chain on sort_values:
(
df
.groupby('Country')
['Annual CO2 (million tonnes / annum)']
.sum()
.sort_values(ascending=False)
)
And since we only care about the top of that list, head:
(
df
.groupby('Country')
['Annual CO2 (million tonnes / annum)']
.sum()
.sort_values(ascending=False)
.head(15)
)
Which gives:
Country
China 10091.0
India 3941.4
United States 1999.9
Notice that nothing was assigned to a variable along the way. Each step returns a new object, and the next method acts on it — that is the whole idea of method chaining, and it keeps the pipeline readable top to bottom.
Four mistakes people make
I cover these and more on video in Five Pandas "groupby" mistakes to avoid.
The group key becomes the index, not a column. After .groupby('Country')['x'].sum() you have a series indexed by country, so result['Country'] raises a KeyError. If you want a regular data frame with Country as an ordinary column, either pass as_index=False or chain .reset_index():
(
df
.groupby('Country', as_index=False)
['Annual CO2 (million tonnes / annum)']
.sum()
)
Rows with a missing group key vanish silently. groupby defaults to dropna=True, so any row whose grouping column is NaN is excluded from every group — and from your totals. If missing keys are meaningful, say dropna=False and they will be grouped under NaN.
Grouping by a categorical column can produce empty groups. In Pandas 3.0 observed defaults to True, so only categories actually present in the data appear. On older versions the default was False, which returns a row for every defined category — including ones with no rows, giving you zeros or NaN you did not expect. Older Bamboo Weekly issues pass observed=True explicitly for exactly this reason. I walk through that change in Pandas 3's groupby defaults to observed=True? What does that mean?.
Reaching for apply when agg will do. .apply hands your function an entire sub-frame and is markedly slower. If you are computing a standard reduction — sum, mean, count, max — use .agg, which Pandas can run in optimized code:
(
df
.groupby('Region')
.agg(total_mw=('Capacity (MW)', 'sum'),
units=('Capacity (MW)', 'size'))
)
Practice it
Work through a groupby exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/groupby/
Go deeper
Bamboo Weekly is the practice. If you want the structured version — full courses with downloadable Jupyter notebooks, plus live Pandas office hours when you get stuck — that is LernerPython+Data. A paid Bamboo Weekly subscription is included with it.
See it on real data
Below are the 103 Bamboo Weekly exercises that use groupby on real-world data — try each one, then study the worked solution.
- Bamboo Weekly #182: Surveillance technology
- Bamboo Weekly #181: Housing costs
- Bamboo Weekly #180: Movies
- Bamboo Weekly #179: Krakow tourism
- Bamboo Weekly #178: Harmful algal bloom
- Bamboo Weekly #176: Religious restrictions
- Bamboo Weekly #175: Inflation
- Bamboo Weekly #173: IPOs
- Bamboo Weekly #172: World Cup
- Bamboo Weekly #166: Income tax
- Bamboo Weekly #164: Fertilizer
- Bamboo Weekly #163: Daylight saving time
- Bamboo Weekly #162: Spotify and car accidents
- Bamboo Weekly #159: State of the Union
- Bamboo Weekly #158: University endowments
- Bamboo Weekly #157: Government corruption
- Bamboo Weekly #156: Winter Olympics
- Bamboo Weekly #155: Gold
- Bamboo Weekly #154: University rankings
- Bamboo Weekly #152: Congestion pricing
- Bamboo Weekly #151: PyPI in 2025
- Bamboo Weekly #150: Kalshi
- Bamboo Weekly #147: Presidential pardons
- Bamboo Weekly #146: Thanksgiving travel
- Bamboo Weekly #144: Museum Heists
- Bamboo Weekly #142: Hurricanes
- Bamboo Weekly #140: Stack Overflow survey
- Bamboo Weekly #138: Federal workers
- Bamboo Weekly #137: UN Security Council
- Bamboo Weekly #136: Indian vehicles
- Bamboo Weekly #135: Airline seats
- Bamboo Weekly #134: Taiwan weather
- Bamboo Weekly #129: Tom Lehrer
- Bamboo Weekly #125: Shrinking dollars
- Bamboo Weekly #123: Missiles
- Bamboo Weekly #121: Research funding
- Bamboo Weekly #119: Python conferences
- Bamboo Weekly #118: Flight delays
- Bamboo Weekly #117: Electricity
- Bamboo Weekly #113: US airport traffic
- Bamboo Weekly #112: Programming jobs
- Bamboo Weekly #111: State taxes
- Bamboo Weekly #106: Flu season
- Bamboo Weekly #103: CDC data
- Bamboo Weekly #102: WordPress
- Bamboo Weekly #100: Sports betting
- Bamboo Weekly #99: Literacy and numeracy
- Bamboo Weekly #97: Drones
- Bamboo Weekly #95: Tariffs
- Bamboo Weekly #92: Climate disaster costs
- Bamboo Weekly #90: Voter participation
- Bamboo Weekly #88: Hot summers
- Bamboo Weekly #87: Nuclear power
- Bamboo Weekly #86: FEMA
- Bamboo Weekly #81: School
- Bamboo Weekly #79: Cyber attacks
- Bamboo Weekly #77: Paris Olympics
- Bamboo Weekly #73: Avocado hand
- Bamboo Weekly #72: City travel
- Bamboo Weekly #71: Holidays
- Bamboo Weekly #70: Moon missions
- Bamboo Weekly #69: Election participation
- Bamboo Weekly #67: Electric cars
- Bamboo Weekly #65: Microplastics
- Bamboo Weekly #64: Coal power
- Bamboo Weekly #63: Ukraine aid
- Bamboo Weekly #62: Economic report card
- Bamboo Weekly #61: Solar eclipse
- Bamboo Weekly #59: Long covid
- Bamboo Weekly #57: International arms trade
- Bamboo Weekly #56: Rent increases
- Bamboo Weekly #55: IVF
- Bamboo Weekly #54: Household debt
- Bamboo Weekly #52: Border encounters
- Bamboo Weekly #51: Academy Awards
- Bamboo Weekly #49: Campaign finance
- Bamboo Weekly #48: Aviation accidents
- Bamboo Weekly #46: Pedestrians
- Bamboo Weekly #45: Netflix
- Bamboo Weekly #41: Wine production
- Bamboo Weekly #40: Sovereign Bonds
- Bamboo Weekly #36: Nobel Prize
- Bamboo Weekly #35: Terrorism
- Bamboo Weekly #34: House of Representatives
- Bamboo Weekly #33: Fracking
- Bamboo Weekly #31: Poverty
- Bamboo Weekly #30: Uncertainty
- Bamboo Weekly #29: Auto accidents
- Bamboo Weekly #26: Hot weather
- Bamboo Weekly #25: Entrepreneurship
- Bamboo Weekly #24: Wildfire smoke
- Bamboo Weekly #16: Consumer oil prices
- Bamboo Weekly #15: Eurovision
- Bamboo Weekly #14: JOLTS
- Bamboo Weekly #12: Tourism
- Bamboo Weekly #10: Oil prices
- Bamboo Weekly #9: US house prices
- Bamboo Weekly #8: Happiness
- Bamboo Weekly #7: Bank failures
- Bamboo Weekly #5: Ukrainian exports
- Bamboo Weekly #4: Eating well
- Bamboo Weekly #2: Egg prices
- Bamboo Weekly #1: Government corruption
Part of the Pandas Methods Index. See also practice by skill.