Counting unique values in a column with pandas is a common data analysis task that helps you understand the variety, distribution, and quality of your data. Whether you are working with customer names, product categories, status codes, or categorical variables, knowing how many distinct values exist in a pandas column is one of the fastest ways to explore a dataset.
Introduction to Counting Unique Values in pandas
In pandas, a column is usually represented as a Series, and each row contains a value from that column. Counting unique values means identifying how many different values appear in that column The details matter here. Took long enough..
As an example, if a column contains these values:
["apple", "banana", "apple", "orange", "banana"]
The unique values are:
["apple", "banana", "orange"]
So, the count of unique values is 3.
Pandas provides several useful methods for counting unique values, including:
.nunique().value_counts()groupby().nunique().drop_duplicates()
Each method is useful in slightly different situations Not complicated — just consistent..
Count Unique Values in a pandas Column with .nunique()
The most direct way to count unique values in a pandas column is using the .nunique() method That's the part that actually makes a difference..
import pandas as pd
df = pd.DataFrame({
"fruit": ["apple", "banana", "apple", "orange", "banana", "apple"]
})
unique_count = df["fruit"].nunique()
print(unique_count)
Output:
3
The .nunique() method returns the number of distinct values in the column.
Basic Syntax
df["column_name"].nunique()
Where:
dfis your pandas DataFrame"column_name"is the column you want to analyze
Example with Multiple Columns
You can also count unique values across multiple columns:
unique_counts = df["fruit"].nunique()
Or for multiple columns:
unique_counts = df[["fruit", "color"]].nunique()
Output may look like this:
fruit 3
color 4
Name: nunique, dtype: int64
Count Unique Values Including or Excluding Missing Values
By default, .Practically speaking, nunique() counts missing values such as NaN as one unique value. This behavior is important to understand because missing data can affect your results.
df = pd.DataFrame({
"status": ["active", "inactive", "active", None, "pending", None]
})
print(df["status"].nunique())
Output:
4
The result is 4 because pandas counts:
["active", "inactive", "pending", NaN]
If you want to exclude missing values, use the dropna parameter:
print(df["status"].nunique(dropna=True))
Output:
3
Common options are:
df["column"].nunique(dropna=False)
df["column"].nunique(dropna=True)
Use:
dropna=Falsewhen you want missing values counted as a categorydropna=Truewhen you only want non-missing values
Count Unique Values and Their Frequencies with .value_counts()
While .nunique() only returns the number of unique values, .value_counts() shows how often each unique value appears Still holds up..
df = pd.DataFrame({
"category": ["red", "blue", "red", "green", "blue", "red"]
})
print(df["category"].value_counts())
Output:
red 3
blue 2
green 1
Name: category, dtype: int64
This is useful when you want to know both:
- How many unique values exist
- How frequently each value appears
To get only the number of unique values from .value_counts(), use len():
unique_count = len(df["category"].value_counts())
print(unique_count)
Output:
3
You can also count unique values while ignoring missing values:
unique_count = len(df["category"].value_counts(dropna=True))
Difference Between .nunique() and .value_counts()
It is helpful to understand the difference between these two methods Not complicated — just consistent. Took long enough..
| Method | Purpose | Example Output |
|---|---|---|
.nunique() |
Counts how many unique values exist | 5 |
.value_counts() |
Counts how often each unique value appears | A: 10, B: 5, C: 2 |
Example:
df = pd.DataFrame({
"city": ["London", "Paris", "London", "Berlin", "Paris"]
})
print(df["city"].nunique())
Output:
3
print(df["city"].value_counts())
Output:
London 2
Paris 2
Berlin 1
Name: city, dtype: int64
Use .nunique() when you only need the count. Use .value_counts() when you need a frequency distribution Which is the point..
Count Unique Values in a DataFrame Column
To count unique values in a single column:
unique_count = df["column_name"].nunique()
print(unique_count)
To count unique values for all object or categorical columns:
object_columns = df.select_dtypes(include=["object", "category"]).columns
for col in object_columns:
print(col, df[col].nunique())
You can also create a summary table:
unique_summary = df[object_columns].nunique()
print(unique_summary)
Example output:
name 500
city 12
status 4
product 120
dtype: int64
This is a useful first step during data exploration.
Count Unique Values in Multiple Columns
If you want to count unique values for several columns at once, select the columns and apply .nunique().
columns_to_check = ["city", "status", "product"]
unique_counts = df[columns_to_check].nunique()
print(unique_counts)
Output:
city 12
status 4
product 120
Name: nunique, dtype: int64
To convert the result into a more readable table:
unique_summary = pd.DataFrame({
"column_name": columns_to_check,
"unique_count": df[columns_to_check].nunique().values
})
print(unique_summary)
Output:
column_name unique_count
0 city 12
1 status 4
2 product 120
Count Unique Values Across the Entire DataFrame
To get a quick overview of cardinality for every column in your DataFrame, call .nunique() directly on the DataFrame object. By default, this operates column-wise (axis=0) Nothing fancy..
unique_counts = df.nunique()
print(unique_counts)
Output:
id 1000
name 950
city 12
status 4
product 120
signup_date 365
dtype: int64
This instantly highlights high-cardinality columns (like id or name) versus low-cardinality categorical columns (like status), which is critical for deciding encoding strategies or identifying potential primary keys.
Controlling NaN Handling Globally
The dropna parameter behaves identically on the DataFrame level. Setting dropna=False counts NaN as a distinct category, which is useful for auditing data completeness And that's really what it comes down to. Simple as that..
# Count NaN as a unique value
unique_with_nan = df.nunique(dropna=False)
print(unique_with_nan)
Count Unique Combinations Across Multiple Columns
Often, you need to know how many unique records exist based on a subset of columns—for example, unique users per city, or unique product-status pairs. groupby().Use .Because of that, drop_duplicates() on the column subset followed by len(), or use . ngroups And that's really what it comes down to. Surprisingly effective..
Method 1: drop_duplicates (Intuitive & Flexible)
# Unique combinations of city and status
unique_pairs = df[["city", "status"]].drop_duplicates()
count = len(unique_pairs)
print(f"Unique city/status combinations: {count}")
# Inspect the actual combinations
print(unique_pairs)
Output:
Unique city/status combinations: 15
city status
0 London active
1 Paris active
3 Berlin active
4 London pending
...
Method 2: groupby().ngroups (Performant for Large Data)
For very large DataFrames, groupby avoids the overhead of creating a deduplicated copy of the data Worth knowing..
count = df.groupby(["city", "status"]).ngroups
print(count)
Output:
15
Method 3: value_counts() on Multiple Columns (Frequency of Combinations)
If you need the frequency of each combination rather than just the count of combinations, pass a list of columns to .value_counts().
combo_counts = df[["city", "status"]].value_counts()
print(combo_counts)
Output:
city status
London active 45
Paris active 38
Berlin active 22
London pending 12
Paris pending 8
...
dtype: int64
This returns a Series with a MultiIndex, making it easy to filter for the most (or least) common segments That's the part that actually makes a difference..
Performance Considerations
.nunique()vslen(.unique()): Always prefer.nunique(). It is implemented in Cython and returns a scalar integer immediately.df[col].unique()materializes the entire array of unique values in memory beforelen()counts them, which is significantly slower and memory-intensive for high-cardinality columns.- Approximate Counts: For massive datasets (millions of rows) where exact precision is not required, consider probabilistic algorithms like HyperLogLog (available via libraries like
datasketchorpyspark.sql.functions.approx_count_distinct). Pandas does not have a built-in approximate distinct count.
Summary Cheat Sheet
| Task | Recommended Syntax | Returns |
|---|---|---|
| Unique count (Series) | s.nunique() |
int |
| Unique count + NaN | s.nunique(dropna=False) |
int |
| Frequency table | s.value_counts() |
Series |
| Unique count (DataFrame) | df.nunique() |
Series (per column) |
| Unique row combos (count) | `df[['A', 'B']].drop_duplicates(). |
Beyond the elementary patterns displayed earlier, there are several nuanced scenarios that often arise when counting distinct elements in a pandas structure Most people skip this — try not to. Still holds up..
Managing missing values
When NaN entries are present, the default behavior of nunique() excludes them unless the dropna flag is set to False. This allows you to decide whether a missing entry should contribute to the distinct count. For example:
s = pd.Series([1, 2, 2, None, 3, None])
print(s.nunique()) # 3 (NaNs are ignored)
print(s.nunique(dropna=False)) # 4 (NaNs are counted as a separate level)
The same argument applies to value_counts(), which can be instructed to treat NaN as a regular category by passing dropna=False Less friction, more output..
Aggregating across multiple columns
If you need the distinct count for a combination of columns, you can either rely on drop_duplicates() on a sliced view of the frame or employ groupby with size(). The latter is often more memory‑efficient because it works directly on the original index without materialising a new DataFrame And that's really what it comes down to. But it adds up..
# Count distinct (city, status) pairs using groupby
pair_counts = df.groupby(["city", "status"]).size()
print(pair_counts)
pair_counts yields a Series whose index is a MultiIndex of the unique pairs and whose values represent how many times each pair occurs. This approach scales well to millions of rows, as the grouping operation is performed in‑place Surprisingly effective..
Row‑wise distinctness
Sometimes the interest lies in how many unique values appear across an entire row rather than within a single column. The nunique(axis=1) signature lets you compute this directly:
row_uniques = df.nunique(axis=1)
print(row_uniques)
Each entry in row_uniques tells you the number of different non‑null values present in the corresponding row.
Memory‑friendly tricks
High‑cardinality columns can inflate memory usage. Converting object‑type columns that have a limited set of values to the category dtype often reduces the footprint dramatically and can speed up nunique() because the underlying codes are smaller integers And it works..
df["city"] = df["city"].astype("category")
df["status"] = df["status"].astype("category")
After conversion, calling nunique() will still return the correct count, but the operation will be faster and use less RAM.
Choosing the right tool
- Exact counts for modest‑size data –
nunique()(orlen(df[cols].drop_duplicates())) is straightforward and fast enough for most analytics workloads. - Exact counts on very large tables –
groupby([...]).ngroupsavoids the extra copy thatdrop_duplicates()would create. - Frequency distribution –
value_counts()(with or withoutdropna=False) provides a full breakdown of how often each distinct combination appears. - Row‑level distinctness –
nunique(axis=1)delivers per‑row uniqueness without any reshaping.
Practical checklist
- Decide whether missing values should be counted.
- Choose a method that matches the required granularity (column vs. row vs. combination).
- For massive datasets, prefer the grouping‑based approach to keep memory overhead low.
- Convert high‑cardinality columns to
categorywhen feasible.
Conclusion
Counting distinct elements in pandas can be accomplished through a handful of idiomatic patterns, each meant for a specific need. size() become indispensable. But ). ngroups offers a scalable alternative for huge frames. ).The nunique() method remains the most direct way to obtain a simple count, while groupby(...Practically speaking, when you need more than a raw total—such as the distribution of each unique combination—value_counts()orgroupby(... By understanding the trade‑offs in memory usage, speed, and the handling of missing data, you can select the optimal technique for any analytical scenario, ensuring both correctness and efficiency.