Pandas value_counts Explained
value_counts() counts how many times each unique value appears in a Series and returns the result sorted by frequency, most common first. It's the fastest way to answer "what's in this column?"
How Do You Get Percentages with normalize=True?
Pass normalize=True to get relative frequencies (proportions that sum to 1) instead of raw counts; multiply by 100 for percentages.
import pandas as pd
s = pd.Series(["chrome", "safari", "chrome", "firefox", "chrome"])
s.value_counts()
# chrome 3
# safari 1
# firefox 1
s.value_counts(normalize=True).round(2)
# chrome 0.6
# safari 0.2
# firefox 0.2
Does value_counts Include NaN?
No โ missing values are silently excluded by default, which can hide a data quality problem. Pass dropna=False to count NaN as its own row.
s = pd.Series(["chrome", None, "chrome", None])
s.value_counts() # chrome 2 (looks fine!)
s.value_counts(dropna=False)
# chrome 2
# NaN 2 <- half your data is missing
How Do You Bin Numeric Values?
For continuous columns, bins=N splits the range into N equal-width intervals and counts values per bin โ a quick text histogram without matplotlib.
ages = pd.Series([22, 25, 31, 35, 41, 58, 62])
ages.value_counts(bins=4, sort=False)
# (21.959, 32.0] 3
# (32.0, 42.0] 2
# (42.0, 52.0] 0
# (52.0, 62.0] 2
Use sort=False so bins stay in numeric order instead of frequency order. For custom edges, use pd.cut then value_counts().
value_counts vs groupby + size โ Which One?
value_counts handles one Series; for counting combinations across columns, call it on a DataFrame subset or use groupby(...).size(). The DataFrame version of value_counts (pandas 1.1+) counts unique rows.
df = pd.DataFrame({
"browser": ["chrome", "chrome", "safari", "chrome"],
"os": ["mac", "windows", "mac", "mac"],
})
df[["browser", "os"]].value_counts()
# browser os
# chrome mac 2
# windows 1
# safari mac 1
df.groupby(["browser", "os"]).size() # same numbers, sorted by key
The practical difference: value_counts sorts by count and drops NaN groups by default, while groupby(...).size() sorts by key and feeds naturally into further aggregation โ more on that in pandas groupby.
Common Pitfalls
- Hidden NaN: default
dropna=Truemeans the counts can silently exclude a large share of rows. Check withdropna=Falsefirst. - Case and whitespace split categories: "Chrome", "chrome", and "chrome " count separately. Normalize with
s.str.strip().str.lower()โ see Python string methods. - It returns a Series: the values become the index. Use
.reset_index()to get a two-column DataFrame for plotting or export. - normalize + dropna interact: with the default
dropna=True, proportions are computed over non-null values only.
Pro Tip: s.value_counts().head(10) plus s.nunique() is a two-line cardinality audit: it shows the dominant categories and tells you whether the column has 5 levels or 50,000 before you one-hot encode it.