Group Data with GroupBy Basics
Learn the basics of grouping data with pandas GroupBy in Python for data science — from the core concept to hands-on examples and troubleshooting.
Focus: group data with groupby basics
You've spent hours cleaning your data, only to realize that the question you actually need to answer is not "what's the mean of this column?" but "what's the mean per group?" — average sales by region, churn by plan type, conversion rate by channel. Doing this with loops and filters is slow, error-prone, and hard to read. That's the pain: you need a concise, expressive way to group data with groupby basics.
The problem this lesson solves
Pandas groupby is one of the most powerful tools in the data science toolbox, but it's also one of the most misunderstood. The frustration usually hits when you try to compute a statistic that varies across categories. Without groupby, you might write something like:
# Manual, painful, and wrong for many groups
for region in df['region'].unique():
subset = df[df['region'] == region]
print(region, subset['sales'].mean())
This works, but it's verbose, slow, and doesn't scale. If you have 100 categories, you're writing 100 filters. More importantly, the manual approach does not compose well — you can't easily chain operations or pivot the results. The problem is that group data with groupby basics gives you a declarative way to express "split by category, apply a function, and combine the results" in a single, fast, vectorized step.
Core concept / mental model
Think of groupby as the split-apply-combine machinery. It's a three-step process:
- Split — rows are partitioned into groups based on column values.
- Apply — a function (sum, mean, count, custom) runs on each group independently.
- Combine — results are assembled into a new DataFrame (or Series).
It's like a food processor: you load in a whole bag of mixed vegetables (all rows), the blade separates them by type (split), each type gets the same treatment — slicing, dicing (apply) — and the output is a neat bowl of separate piles (combine).
In pandas, the object returned by df.groupby('col') is a GroupBy object — it's lazy until you apply an aggregation. Nothing happens until you call .agg(), .mean(), .sum(), or any reduction. This is a key mental shift: groupby creates the intent, and the aggregation executes it.
Pro tip:
groupbydoes NOT perform the aggregation by itself. You must always chain an aggregation method like.mean()or.sum(). If you forget it, you'll get a crypticDataFrameGroupByobject instead of numbers.
How it works step by step
Let's break the syntax down into its components:
# Basic structure
df.groupby(by, axis=0, level=None, as_index=True, sort=True, dropna=True)
The most important arguments:
by— the column(s) you want to group by. Can be a string (column name), a list of strings, or a callable/array.as_index— ifTrue(default), the group keys become the index of the result; ifFalse, they become regular columns.dropna— ifTrue(default), NaN group keys are excluded.sort— ifTrue(default), groups are sorted by key.
The first step is to pick your aggregation. Common ones:
.count()— number of non-null rows per group.sum()— sum of numeric columns.mean()— average.median()— median (robust to outliers).agg()— apply multiple functions at once
The output of a groupby aggregation is typically a DataFrame with one row per group. If you group by a single column, the index becomes the unique values of that column.
Hands-on walkthrough
Let's work with a realistic sales dataset. We'll use the classic tips dataset from seaborn — it's small, familiar, and perfect for seeing what's going on.
import pandas as pd
import seaborn as sns
# Load the built-in tips dataset (or any CSV you have)
df = sns.load_dataset('tips')
print(df.head())
Expected output:
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
Now, group by 'day' and get the mean of all numeric columns:
# Group by day, compute mean for numeric columns
day_means = df.groupby('day').mean()
print(day_means)
Expected output:
total_bill tip size
day
Thur 17.682742 2.771452 2.483871
Fri 17.151579 2.734737 2.104762
Sat 20.441379 2.993103 2.517241
Sun 21.410000 3.255682 2.863636
Notice how the day column became the index. If you prefer it as a regular column (for merge or export), use as_index=False:
# Keep day as a column
result = df.groupby('day', as_index=False)['tip'].mean()
print(result)
Expected output:
day tip
0 Thur 2.771452
1 Fri 2.734737
2 Sat 2.993103
3 Sun 3.255682
Now let's see the power of multiple aggregations:
# Group by day and smoker, get sum and count for total_bill
result2 = df.groupby(['day', 'smoker'])['total_bill'].agg(['sum', 'count'])
print(result2)
Expected output:
sum count
day smoker
Thur No 1727.80 45
Yes 559.80 17
Fri No 215.00 8
Yes 633.80 11
Sat No 2924.25 47
Yes 1371.50 29
Sun No 3055.62 49
Yes 1213.50 22
This is where group data with groupby basics shines: you get a tidy summary in one line, ready for further analysis or visualization.
Compare options / when to choose what
When grouping, you have several aggregation choices — each fits a different question:
| Aggregation | What it answers | Use case |
|---|---|---|
.sum() |
Total per group | Sales revenue by region |
.mean() |
Average per group | Average order value by customer segment |
.count() |
Rows per group (non-null) | Number of transactions per day |
.median() |
Middle value (robust to outliers) | Typical income per age bracket |
.agg() |
Multiple statistics at once | Summary table with sum, mean, and count |
.size() |
Rows per group (including nulls) | Count of observations per category |
Also, you can use pivot_table for a similar result when you want to reshape the output into a wide table. groupby is best when you just need to summarize; pivot_table is better for cross-tabulations with rows/columns structured.
Pro tip: If you want to apply different aggregation functions to different columns, use
.agg()with a dictionary:df.groupby('col').agg({'revenue': 'sum', 'customers': 'count'}).
Troubleshooting & edge cases
Common pitfalls and how to fix them:
- Forgetting the aggregation method: You get
pandas.core.groupby.generic.DataFrameGroupBy— that's normal, but you need to add.mean(),.sum(), etc. - NaN in group keys: By default, rows with NaN in the group column are dropped. If you want them included, set
dropna=False. - Grouping by multiple columns returns MultiIndex: You can still access the result, but it's a hierarchical index. To flatten, use
.reset_index()oras_index=False. - Performance: Always use vectorized methods like
.mean(), not.apply(lambda x: np.mean(x))for standard aggregations — the former is much faster. - Missing values in the column you're aggregating:
meanandsumskip NaNs by default, butcountcounts non-null, andsizecounts all. Understand the difference.
Example of a typical error:
# This returns a GroupBy object, not a result!
grouped = df.groupby('day')
print(grouped.mean()) # correct: call the aggregation
If you try to access a column before aggregating, you get a warning or error — always aggregate first.
What you learned & what's next
In this lesson, you've learned the core of group data with groupby basics: the split-apply-combine pattern, how to use groupby with different aggregations, how to group by multiple columns, and how to avoid common pitfalls. You now can quickly summarize your data by categories, which is essential for exploratory analysis, reporting, and feature engineering.
Next in the track, you'll learn to reshape and pivot data — combining groupby with pivot_table and melt to transform data between long and wide formats, which is a natural next step after grouping. Check the next lesson: Reshape Data with pivot_table and melt.
Practice recap
Try this: Load the tips dataset (or any dataset with a categorical column). Group by a column of your choice and compute at least three different aggregations (e.g., sum, mean, count). Then group by two columns at once and see how the output changes. Finally, try using .agg() with a dictionary to apply different functions to different columns. This will solidify the split-apply-combine pattern.
Common mistakes
- Forgetting to call an aggregation method after
groupby— you'll get a GroupBy object, not a result. - Using
.size()vs.count()incorrectly:sizeincludes rows with NaN in aggregation columns,countdoes not. - Not using
as_index=Falseand then getting confused when the group key becomes the index. - Using
.apply(lambda x: np.mean(x))instead of.mean()— slow and unnecessarily complex. - Grouping by multiple columns and not resetting the index, making the result hard to use in later steps.
Variations
- Use
pivot_tablewhen you need the groups as separate columns (wide format) rather than rows. - Use
pd.cut()to group numeric columns into bins before applyinggroupby. - Use
groupbywithas_index=Falseto keep group keys as columns for easier merging with other data.
Real-world use cases
- Compute average order value by customer segment to inform email marketing targeting.
- Aggregate daily website traffic by device type for a weekly performance report.
- Group sensor readings by hour and location to detect anomalies in manufacturing quality.
Key takeaways
- GroupBy follows split-apply-combine: split data into groups, apply a function, combine results.
- Always chain an aggregation method like
.mean()or.sum()to get a result. - Group by multiple columns using a list — the output becomes a MultiIndex DataFrame.
- Use
as_index=Falseto keep group keys as columns, not index. - Choose the right aggregation: sum for totals, mean for averages, count for rows, size for raw count.
- Be aware of NaN handling: dropna=False keeps NaN groups, count vs size behavior.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.