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

Sponsored

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:

  1. Split — rows are partitioned into groups based on column values.
  2. Apply — a function (sum, mean, count, custom) runs on each group independently.
  3. 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: groupby does 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 cryptic DataFrameGroupBy object 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 — if True (default), the group keys become the index of the result; if False, they become regular columns.
  • dropna — if True (default), NaN group keys are excluded.
  • sort — if True (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() or as_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: mean and sum skip NaNs by default, but count counts non-null, and size counts 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: size includes rows with NaN in aggregation columns, count does not.
  • Not using as_index=False and 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

  1. Use pivot_table when you need the groups as separate columns (wide format) rather than rows.
  2. Use pd.cut() to group numeric columns into bins before applying groupby.
  3. Use groupby with as_index=False to 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=False to 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.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.