HOWTO · Pandas
How to Count Unique Values Per Group(s) in Pandas
count values by grouping column in DataFrame using df.groupby().nunique(), df.groupby().agg(), and df.groupby().unique() methods in pandas library
On this page
When you analyze a DataFrame, you often need to know how many distinct values appear inside each group rather than across the whole column. For example, a data set might record which code values each entry in Countries uses, and you want one count per country. The pandas groupby() operation offers three practical tools for this task:
df.groupby().nunique()returns the number of distinct values in each group.df.groupby().agg()applies the same count through the general aggregation interface.df.groupby().unique()lists the distinct values themselves for each group.
All the examples below use the same DataFrame, built from nine rows of country and code pairs.
Run this example:
import pandas as pd
data = [
[999, "Switzerland"],
[113, "Switzerland"],
[112, "Japan"],
[112, "Switzerland"],
[113, "Canada"],
[114, "Japan"],
[100, "Germany"],
[114, "Japan"],
[115, "Germany"],
]
df = pd.DataFrame(data, columns=["code", "Countries"])
print(df)
code Countries
0 999 Switzerland
1 113 Switzerland
2 112 Japan
3 112 Switzerland
4 113 Canada
5 114 Japan
6 100 Germany
7 114 Japan
8 115 Germany
Count unique values with GroupBy.nunique()
The most direct way to count distinct values per group is nunique(). Selecting the code column after groupby() turns the grouped object into a grouped Series, so nunique() counts the different codes within each country’s rows.
import pandas as pd
data = [
[999, "Switzerland"],
[113, "Switzerland"],
[112, "Japan"],
[112, "Switzerland"],
[113, "Canada"],
[114, "Japan"],
[100, "Germany"],
[114, "Japan"],
[115, "Germany"],
]
df = pd.DataFrame(data, columns=["code", "Countries"])
result = df.groupby("Countries")["code"].nunique()
print(result)
Countries
Canada 1
Germany 2
Japan 2
Switzerland 3
Name: code, dtype: int64
The result is a Series whose index holds the group names and whose values hold the counts: Switzerland uses three distinct codes, Germany and Japan use two each, and Canada uses one. Because the group keys became the index, call reset_index() if you want a flat two-column DataFrame, or use the agg() form in the next section. By default nunique() does not count missing (NaN) entries; pass dropna=False if missing values should count as a distinct value.
Count unique values with GroupBy.agg()
agg() is the general aggregation method of a grouped object. Passing nunique() as the aggregation function produces the same counts through the DataFrame.aggregate() interface, with more control over the shape of the result.
import pandas as pd
data = [
[999, "Switzerland"],
[113, "Switzerland"],
[112, "Japan"],
[112, "Switzerland"],
[113, "Canada"],
[114, "Japan"],
[100, "Germany"],
[114, "Japan"],
[115, "Germany"],
]
df = pd.DataFrame(data, columns=["code", "Countries"])
result = df.groupby(by="Countries", as_index=False).agg({"code": pd.Series.nunique})
print(result)
Countries code
0 Canada 1
1 Germany 2
2 Japan 2
3 Switzerland 3
The dictionary {"code": pd.Series.nunique} tells agg() which column to aggregate and which function to apply; pd.Series.nunique is the same operation called directly in the previous section, and passing its name as a string — agg({"code": "nunique"}) — works identically. The as_index=False argument keeps the country names as a regular column instead of moving them into the index, which is why result is a flat DataFrame rather than an indexed Series. Reach for this form when you want a DataFrame result or when the distinct count is one of several aggregations computed in a single pass.
List unique values with GroupBy["code"].unique()
A count answers “how many”, but sometimes you need to know which codes each country uses. Applying unique() to a grouped Series returns the distinct values themselves.
import pandas as pd
data = [
[999, "Switzerland"],
[113, "Switzerland"],
[112, "Japan"],
[112, "Switzerland"],
[113, "Canada"],
[114, "Japan"],
[100, "Germany"],
[114, "Japan"],
[115, "Germany"],
]
df = pd.DataFrame(data, columns=["code", "Countries"])
result = df.groupby("Countries")["code"].unique()
print(result)
Countries
Canada [113]
Germany [100, 115]
Japan [112, 114]
Switzerland [999, 113, 112]
Name: code, dtype: object
Each entry in the result is an array of the distinct code values seen for that country, in order of appearance — Switzerland’s array confirms the three codes counted earlier: 999, 113, and 112. Because every element is an array rather than a single number, the Series has dtype object. One behavioral difference worth noting: unique() includes a NaN in the returned array, while nunique() skips missing values by default.
Choose between counting and listing values
Pick nunique() when the question is quantitative — “how many distinct codes does each country use?” It returns tidy numeric counts that you can sort, filter, or join back onto the original rows. To attach each row’s group count as a new column, use df.groupby("Countries")["code"].transform("nunique"), which broadcasts the per-group count to every row.
Pick unique() when the identity of the values matters — for example, auditing which codes are shared between countries or feeding the value lists into a later processing step. If you already hold the unique() result and need its lengths, result.apply(len) reproduces the same counts nunique() would give.
Finally, use agg() when a distinct count is only part of a wider aggregation, such as agg({"code": "nunique", "price": "sum"}), which computes several per-group statistics in one pass. In short: nunique() for the count, unique() for the values, and agg() when you need a DataFrame result or want to combine the count with other aggregations.