Data AnalyticsPandas & NumPybeginner
Updated:

Pandas GroupBy: Aggregate and Summarize

4 min read

GroupBy turns detailed rows into category summaries. Learn split-apply-combine, common aggregations, agg with multiple functions, and grouping by many columns.

TL;DR – Quick Answer

df.groupby('column') splits a DataFrame into groups that share a value, then you apply an aggregation such as sum, mean or count to each group and pandas combines the results into one summary table. This split-apply-combine pattern answers questions like average salary per city or total sales per region. It is the pandas equivalent of SQL's GROUP BY.

On This Page

GroupBy is where pandas stops listing rows and starts answering business questions. "What is the average order value per region?" "How many customers do we have in each city?" "Which product category earns the most?" Every one of those is a groupby followed by an aggregation. If filtering is the most common operation, groupby is the most valuable one for producing insight.

The engine behind it is a three-step pattern known as split-apply-combine: split the data into groups, apply a function to each group, and combine the results into one table. Understanding those three steps makes every groupby you will ever write predictable.

Split-apply-combine in action

Start with a small illustrative sample of employee data:

import pandas as pd

df = pd.DataFrame({
    "name": ["Aarti", "Bhaskar", "Chitra", "Devan", "Esha", "Farhan"],
    "city": ["Hyderabad", "Hyderabad", "Bengaluru", "Bengaluru", "Hyderabad", "Pune"],
    "salary": [48000, 65000, 52000, 58000, 47000, 90000],
})

To get the average salary per city, group by city, select the salary column, and apply mean:

print(df.groupby("city")["salary"].mean())
city
Bengaluru    55000.0
Hyderabad    53333.333333
Pune         90000.0
Name: salary, dtype: float64

Pandas split the six rows into three city groups, computed the mean of each, and combined them into one Series indexed by city. Swap mean for sum, count, min or max and you ask a different question with the same shape.

Counting rows per group

Counting group sizes is so common it has a dedicated method, size, which counts rows including any missing values:

print(df.groupby("city").size())
city
Bengaluru    2
Hyderabad    3
Pune         1
dtype: int64

This instantly gives you the distribution across categories, one of the first things you check on any dataset.

Multiple aggregations with agg

Often you want several statistics per group at once. The agg method takes a list of function names and returns a column for each:

print(df.groupby("city")["salary"].agg(["mean", "max", "count"]))
                   mean    max  count
city
Bengaluru  55000.000000  58000      2
Hyderabad  53333.333333  65000      3
Pune       90000.000000  90000      1

To apply different functions to different columns, pass a dictionary. You can also rename outputs using named aggregation, which produces clean column names in one step:

result = df.groupby("city").agg(
    avg_salary=("salary", "mean"),
    headcount=("salary", "count"),
)
print(result)
              avg_salary  headcount
city
Bengaluru   55000.000000          2
Hyderabad   53333.333333          3
Pune        90000.000000          1

Named aggregation, using new_name=("column", "function"), is the modern, readable way to build summary tables and is worth adopting as your default.

Grouping by multiple columns

Pass a list of columns to group by their combinations. Add a department column and group by both:

df["dept"] = ["Sales", "Sales", "Tech", "Tech", "Tech", "Sales"]
print(df.groupby(["city", "dept"])["salary"].mean())
city       dept
Bengaluru  Tech     55000.0
Hyderabad  Sales    56500.0
           Tech     47000.0
Pune       Sales    90000.0
Name: salary, dtype: float64

Now each group is a unique city-and-department pair. The result carries a MultiIndex, a two-level row label. When you want those levels back as ordinary columns for charting or export, call reset_index().

Resetting the index

By default the grouping column becomes the index, which is handy for lookups but awkward for further work. reset_index restores flat columns:

summary = df.groupby("city")["salary"].mean().reset_index()
print(summary)
        city       salary
0  Bengaluru  55000.000000
1  Hyderabad  53333.333333
2       Pune  90000.000000

Now city is a normal column again, ready to feed a chart or merge with another table. A common next step is to sort the summary so the largest or smallest group leads, which is how you build "top N" rankings:

ranked = df.groupby("city")["salary"].sum().reset_index()
print(ranked.sort_values("salary", ascending=False))
        city  salary
1  Hyderabad  160000
0  Bengaluru  110000
2       Pune   90000

Chaining groupby, reset_index and sort_values in this order, summarize then rank, is one of the most reused patterns in reporting, because stakeholders almost always want the leaders at the top.

Practical usage

GroupBy is the backbone of reporting. A weekly dashboard is usually a handful of grouped aggregations: revenue by region, orders by channel, active users by plan. The analyst pattern is to filter to the relevant rows, group by the dimension a stakeholder cares about, aggregate the metric they asked for, sort, and present. Because the result is a DataFrame, you can immediately chart it or export it. Combining groupby with sort_values to rank groups, such as top cities by revenue, is one of the most common real tasks.

Common mistakes

  • Forgetting to select a column before aggregating. df.groupby("city").mean() tries to average every numeric column, sometimes including ones you did not intend. Select the column you want: df.groupby("city")["salary"].mean().
  • Confusing size and count. size counts all rows per group; count counts non-missing values per column. They differ when data has NaN.
  • Expecting flat columns after multi-column grouping. Grouping by several columns yields a MultiIndex; call reset_index() if downstream code expects plain columns.
  • Losing the grouping column. After aggregation the group key lives in the index, not the columns, which surprises people trying to reference it by name. Reset the index or use as_index=False.

In interviews

GroupBy questions are staples of analyst interviews, often phrased as "average sales per category" or "count of orders per customer." Interviewers watch for the correct split-apply-combine shape, the right aggregation, and clean handling of the index afterward. A frequent follow-up compares pandas groupby to SQL's GROUP BY, so be ready to say the concept is identical and mention that pandas returns a reusable DataFrame. Showing named aggregation and a reset_index for tidy output signals polish.

Where this fits in your learning path

GroupBy is a high-value skill in the data analytics path and pairs naturally with the filtering rows you learned earlier. To reshape grouped results into cross-tabs, continue to pivot tables, and to combine data from several tables before grouping, learn merge and join. Summarizing data by category is a core daily task in the data analyst role.

Frequently Asked Questions

What does groupby do in pandas?
groupby splits a DataFrame into groups based on the values in one or more columns, then lets you apply an aggregation to each group. For example df.groupby('city')['salary'].mean() gives the average salary for each city. Pandas combines the per-group results into a single output indexed by the grouping column.
How do I apply multiple aggregations at once in pandas?
Use agg with a list or dictionary. df.groupby('city')['salary'].agg(['mean', 'max', 'count']) returns three columns of statistics per city. A dictionary like agg({'salary': 'mean', 'age': 'max'}) applies different functions to different columns.
How do I group by more than one column?
Pass a list of column names: df.groupby(['city', 'department']). This creates one group per unique combination of the two columns, producing a result with a MultiIndex. Call reset_index afterwards if you want the grouping columns back as regular columns.
What is the difference between groupby in pandas and SQL?
The concept is identical: split rows into groups and aggregate each. The main difference is that pandas returns a DataFrame you can keep transforming, and by default it puts the grouping column into the index. SQL returns a result set and requires every selected column to be grouped or aggregated.
Why does my groupby result have the grouping column as the index?
By default pandas moves the grouping column into the index of the result. This is convenient for lookups but awkward for further processing. Call reset_index to turn it back into a normal column, or pass as_index=False to groupby to skip the behaviour entirely.

Want to Build Your Career in Data Analytics with AI?

Join CodeBegun and train with working industry engineers — See the Data Analytics course in Hyderabad

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 16 July 2026 LinkedIn
Chat with us