Lesson 18
Grouping with groupby
Split a table into groups on one or more columns, then count, total, and average each group with the split, apply, combine pattern.
groupby splits a table into one group per distinct value, applies a summary to each group, and combines the answers into a new table. Split, apply, combine is the pattern behind most reporting work.
One number per group
Name the column to group by, pick the column to summarize, and call the summary. The distinct values become the index of the result, in sorted order.
import pandas as pd
learners = pd.DataFrame({
"name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
"track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
"city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
"hours": [42, 31, 55, 24, 47, 38],
"score": [88, 74, 91, 65, 82, 79],
})
print(learners.groupby("track")["score"].mean())
print(learners.groupby("track")["hours"].sum())
print(learners.groupby("city")["name"].count())Output:
track
NumPy 78.0
Python 89.5
pandas 72.0
Name: score, dtype: float64
track
NumPy 78
Python 97
pandas 62
Name: hours, dtype: int64
city
Eastport 2
Northgate 2
Westfield 2
Name: name, dtype: int64How big is each group
size() counts the rows in each group, gaps included. count() counts only the real values, so the two differ exactly where data is missing. value_counts() is the shortcut when the size is all you want.
import pandas as pd
learners = pd.DataFrame({
"name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
"track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
"city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
"hours": [42, 31, 55, 24, 47, 38],
"score": [88, 74, 91, 65, 82, 79],
})
print(learners.groupby("track").size())
print(learners["track"].value_counts())
print(learners.groupby("track")["score"].count())Output:
track
NumPy 2
Python 2
pandas 2
dtype: int64
track
Python 2
NumPy 2
pandas 2
Name: count, dtype: int64
track
NumPy 2
Python 2
pandas 2
Name: score, dtype: int64Several columns at a time
A list of columns after the group gives a summary table rather than one column. Ask for the numeric columns by name: a mean over a text column has no meaning and pandas will not invent one.
import pandas as pd
learners = pd.DataFrame({
"name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
"track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
"city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
"hours": [42, 31, 55, 24, 47, 38],
"score": [88, 74, 91, 65, 82, 79],
})
print(learners.groupby("track")[["hours", "score"]].mean().round(2))
print(learners.groupby("track", as_index=False)[["hours"]].sum())Output:
hours score
track
NumPy 39.0 78.0
Python 48.5 89.5
pandas 31.0 72.0
track hours
0 NumPy 78
1 Python 97
2 pandas 62as_index=False keeps the grouping column as a normal column, which is what you want when the result feeds into another step.
Group on two columns
A list of grouping columns makes one group per combination, and the result gets a two-level index. reset_index() flattens it back into ordinary columns.
import pandas as pd
learners = pd.DataFrame({
"name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
"track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
"city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
"hours": [42, 31, 55, 24, 47, 38],
"score": [88, 74, 91, 65, 82, 79],
})
by_pair = learners.groupby(["city", "track"])["score"].mean()
print(by_pair)
print(by_pair.reset_index())Output:
city track
Eastport NumPy 78.0
Northgate Python 89.5
Westfield pandas 72.0
Name: score, dtype: float64
city track score
0 Eastport NumPy 78.0
1 Northgate Python 89.5
2 Westfield pandas 72.0Look inside the groups
A group object is iterable: each turn gives the group name and the rows that belong to it. get_group fetches one by name, and first takes the leading row of every group.
import pandas as pd
learners = pd.DataFrame({
"name": ["Avery", "Jordan", "Casey", "Riley", "Morgan", "Quinn"],
"track": ["Python", "NumPy", "Python", "pandas", "NumPy", "pandas"],
"city": ["Northgate", "Eastport", "Northgate", "Westfield", "Eastport", "Westfield"],
"hours": [42, 31, 55, 24, 47, 38],
"score": [88, 74, 91, 65, 82, 79],
})
grouped = learners.groupby("track")
for track, rows in grouped:
print(track, rows["name"].tolist())
print(grouped.get_group("pandas")[["name", "score"]])
print(grouped.first()[["name", "score"]])Output:
NumPy ['Jordan', 'Morgan']
Python ['Avery', 'Casey']
pandas ['Riley', 'Quinn']
name score
3 Riley 65
5 Quinn 79
name score
track
NumPy Jordan 74
Python Avery 88
pandas Riley 65Key takeaways
-
groupby splits, applies a summary, and combines the answers
-
The grouping values become the index unless you pass as_index=False
-
size counts rows and count counts only the values that are present
-
A list of grouping columns gives one group per combination
-
Previous: Iterating Rows and Columns
Loading playground…
Check your understanding
Grouping with groupby Quiz
What are the three steps behind groupby?
1 / 10