value_counts() в pandas — частоты, доли и распределения

Проверь себя · 1/3разбор после ответа
Что вернёт выражение "b" in ["a", "b", "c"]?

Зачем аналитику value_counts

Когда вы впервые открываете незнакомый датасет, первый вопрос почти всегда один: «а что вообще лежит в этом столбце?». Сколько уникальных городов, какие сегменты пользователей преобладают, нет ли подозрительного значения вроде пустой строки или unknown, которое съедает половину выборки. Руками это считать бессмысленно, а строить полноценную гистограмму ради быстрого взгляда — долго. Именно для этого в pandas есть value_counts() — метод, который за одну строку выдаёт частоту каждого уникального значения и сразу сортирует по убыванию.

На собеседованиях на аналитика value_counts() всплывает постоянно в задачах на EDA (exploratory data analysis): «как быстро посмотреть распределение», «найди топ-5 категорий», «посчитай долю каждого сегмента». Метод выглядит простым, но за normalize, bins и обработкой NaN прячется несколько нюансов, на которых легко споткнуться. Разберём их по порядку — с реальным кодом и выводом, который вы получите в pandas 2.x.

Что делает value_counts

value_counts() — это метод Series. Он берёт столбец, считает, сколько раз встречается каждое уникальное значение, и возвращает новый Series: в индексе — сами значения, в значениях — их количество. По умолчанию результат отсортирован по убыванию частоты, поэтому самое частое значение всегда сверху.

import pandas as pd

df = pd.DataFrame({
    'city': ['Москва', 'Питер', 'Москва', 'Казань', 'Москва', 'Питер', 'Казань', 'Москва'],
    'segment': ['premium', 'free', 'premium', 'free', 'free', 'premium', 'free', 'premium'],
})

df['city'].value_counts()
# city
# Москва    4
# Питер     2
# Казань    2
# Name: count, dtype: int64

Обратите внимание на подпись Name: count — начиная с pandas 2.0 результирующий Series называется count (а с normalize=Trueproportion). В старых статьях и ответах на Stack Overflow вы встретите Name: city, dtype: int64 — это устаревший формат, на свежей версии он выглядит уже иначе.

Параметры value_counts

У метода пять параметров, которые покрывают почти все задачи частотного анализа: normalize, sort, ascending, dropna и bins. Ниже — каждый с реальным выводом.

normalize — доли вместо количества

normalize=True возвращает не абсолютные числа, а доли от общего количества (сумма всех значений равна единице). Это удобно, когда важна не штука, а процент.

df['city'].value_counts(normalize=True)
# city
# Москва    0.50
# Питер     0.25
# Казань    0.25
# Name: proportion, dtype: float64

# В процентах
(df['city'].value_counts(normalize=True) * 100).round(1)
# city
# Москва    50.0
# Питер     25.0
# Казань    25.0
# Name: proportion, dtype: float64

sort и ascending — порядок строк

По умолчанию sort=True — pandas сортирует по частоте по убыванию. Параметр ascending=True переворачивает порядок: реже встречающиеся значения окажутся сверху. А если нужна сортировка по самому значению (по алфавиту или по датам), частотную сортировку отключают через .sort_index().

# По возрастанию частоты
df['city'].value_counts(ascending=True)
# city
# Питер     2
# Казань    2
# Москва    4
# Name: count, dtype: int64

# По индексу (алфавит), а не по частоте
df['city'].value_counts().sort_index()
# city
# Казань    2
# Москва    4
# Питер     2
# Name: count, dtype: int64

dropna — учёт пропусков

По умолчанию value_counts() молча выбрасывает пропуски: NaN в результат не попадает. Это удобно для анализа категорий, но опасно, если вы оцениваете качество данных — пропуски просто исчезнут из поля зрения. Параметр dropna=False включает NaN в подсчёт отдельной строкой.

import numpy as np
s = pd.Series(['A', 'B', 'A', np.nan, 'B', np.nan, 'A'])

s.value_counts()              # NaN не считается (по умолчанию)
# A    3
# B    2
# Name: count, dtype: int64

s.value_counts(dropna=False)  # NaN учитывается
# A      3
# B      2
# NaN    2
# Name: count, dtype: int64

bins — группировка числовых данных

Для числового столбца считать частоту каждого уникального числа обычно бессмысленно: при тысячах разных значений вы получите тысячи строк по единице. Параметр bins=N сначала режет диапазон на N равных интервалов, а потом считает, сколько значений попало в каждый — фактически быстрая гистограмма без matplotlib.

ages = pd.Series([22, 25, 31, 28, 45, 37, 19, 55, 42, 33])

ages.value_counts(bins=4).sort_index()
# (18.963, 28.0]    4
# (28.0, 37.0]      3
# (37.0, 46.0]      2
# (46.0, 55.0]      1
# Name: count, dtype: int64

Левая граница первого интервала чуть ниже минимума (18.963, а не 19) — pandas намеренно расширяет её, чтобы включить крайнее значение. Без .sort_index() строки идут по убыванию частоты, что для интервалов читается хуже, поэтому интервалы почти всегда сортируют по индексу.

value_counts по нескольким столбцам

С pandas 1.1 value_counts() работает не только на Series, но и на DataFrame — тогда он считает частоту комбинаций значений всех переданных столбцов. Это быстрый способ получить кросс-табуляцию «какие пары встречаются и сколько раз».

df[['city', 'segment']].value_counts()
# city    segment
# Москва  premium    3
# Казань  free       2
# Москва  free       1
# Питер   free       1
#         premium    1
# Name: count, dtype: int64

Результат — Series с MultiIndex (город + сегмент), снова отсортированный по убыванию частоты. Если вам важнее не частота пары, а отдельная агрегация, тот же результат даёт df.groupby(['city', 'segment']).size() — разница в том, что groupby по умолчанию сортирует по ключам, а не по частоте. О сравнении этих двух подходов — ниже.

Закрепи Python для аналитика
200+ задач по pandas, numpy и работе с данными — с разборами
Тренировать Python в Telegram

value_counts vs groupby и SQL GROUP BY

value_counts() — это, по сути, синтаксический сахар поверх группировки. Эквивалент через groupbydf.groupby('city').size(), только без автоматической сортировки по частоте и без normalize.

df.groupby('city').size()
# city
# Казань    2
# Москва    4
# Питер     2
# dtype: int64

# Чтобы получить тот же порядок, что у value_counts:
df.groupby('city').size().sort_values(ascending=False)

Правило выбора простое: если нужна частота одного столбца с сортировкой и долями — берите value_counts(), он короче и читается понятнее. Если рядом с подсчётом нужна агрегация других колонок (средний чек, сумма, несколько метрик сразу) — это уже работа для groupby().agg(), потому что value_counts() умеет только считать строки.

Задача value_counts groupby
Частота одного столбца df['col'].value_counts() df.groupby('col').size()
Доли normalize=True вручную .div(len(df))
Сортировка по частоте по умолчанию .sort_values()
Бины для чисел bins=N pd.cut() + groupby
Агрегация других колонок нет .agg(...)

В SQL прямой аналог value_counts() — это GROUP BY с COUNT(*) и сортировкой. Если вы пришли в pandas из SQL (или наоборот), держите в голове эту параллель — на собесе её часто просят проговорить.

-- Аналог df['city'].value_counts()
SELECT city, COUNT(*) AS cnt
FROM users
GROUP BY city
ORDER BY cnt DESC;

-- Аналог value_counts(normalize=True) — доли
SELECT city,
       COUNT(*)::NUMERIC / SUM(COUNT(*)) OVER () AS share
FROM users
GROUP BY city
ORDER BY share DESC;

Заметьте ::NUMERIC во втором запросе: без приведения типа целочисленное деление в PostgreSQL усечёт результат до нуля. Подробнее про группировку в базе — в гайде по GROUP BY.

Частые задачи на собесе

На интервью value_counts() обычно встречается в трёх формулировках. Первая — топ-N категорий. Поскольку метод уже сортирует по убыванию, достаточно дописать .head(N).

df['city'].value_counts().head(2)
# city
# Москва    4
# Питер     2
# Name: count, dtype: int64

Вторая — доля каждой категории. Здесь спасает normalize=True; если просят именно проценты, умножаем на 100. Удобно сразу прогнать пару таких задач в тренажёре Карьерника, чтобы синтаксис не вылетал из головы на собесе.

segment = pd.Series(['free']*12 + ['premium']*5 + ['trial']*3)

(segment.value_counts(normalize=True) * 100).round(1)
# free       60.0
# premium    25.0
# trial      15.0
# Name: proportion, dtype: float64

Третья — фильтрация редких категорий. Частоты сами по себе можно использовать как маску: оставить только популярные значения или, наоборот, отсечь хвост из единичных категорий перед обучением модели.

cat = pd.Series(['x','x','x','y','z','x','y','x','x','y','q'])
counts = cat.value_counts()

# Категории, которые встречаются реже 2 раз
rare = counts[counts < 2].index
filtered = cat[~cat.isin(rare)]
# редкие значения 'z' и 'q' выброшены

Ещё одна классика — найти самое частое значение (моду). Поскольку value_counts() сортирует по убыванию, первый элемент индекса и есть мода — то же самое вернёт .mode().

df['city'].value_counts().index[0]   # 'Москва'
df['city'].mode()[0]                 # 'Москва'

Частые ошибки

Путают value_counts() с count(). value_counts() считает частоту каждого значения, а df['col'].count() — просто число непустых ячеек. Это разные вопросы: «сколько каких» против «сколько всего».

Забывают про NaN. По умолчанию пропуски выпадают из подсчёта. Если вы оцениваете долю заполненности столбца через value_counts() без dropna=False, вы получите красивую картину, которая врёт — потому что пропуски просто не показаны. Для аудита данных всегда добавляйте dropna=False.

Считают проценты вручную, когда есть normalize. Конструкция df['col'].value_counts() / len(df) * 100 работает, но value_counts(normalize=True) короче и не сломается, если в столбце есть пропуски (нормализация считается от непустых значений, а не от длины DataFrame).

Вызывают value_counts() на числовом столбце без bins. Для непрерывной величины (доход, возраст, время на сайте) вы получите простыню из тысяч строк по единице. Для таких столбцов нужен bins=N или pd.cut().

Сортируют по индексу, забыв sort_index(). value_counts() всегда сортирует по частоте. Если нужен порядок по самому значению (алфавит, даты, числовые интервалы), дописывайте .sort_index() — иначе строки пойдут хаотично с точки зрения значений.

Связанные темы

FAQ

Чем value_counts отличается от groupby + count?

value_counts() — это шорткат для частоты одного столбца: сразу сортирует по убыванию и умеет normalize. groupby().size() даёт тот же подсчёт, но без сортировки по частоте и без долей, зато позволяет считать агрегации по другим колонкам. Для «сколько каких значений» берите value_counts(), для сложной агрегации — groupby().agg().

Как посчитать процент каждой категории?

Через df['col'].value_counts(normalize=True) * 100. Метод вернёт доли (сумма равна единице), умножение на 100 переводит их в проценты. Для округления допишите .round(1).

Можно ли применять value_counts к числовому столбцу?

Можно, но для непрерывных данных используйте bins=N — иначе каждое уникальное число станет отдельной строкой. С bins pandas сам разобьёт диапазон на интервалы и посчитает частоту попаданий в каждый, как в гистограмме.

Как сделать value_counts сразу по нескольким столбцам?

Передайте DataFrame с нужными колонками: df[['col1', 'col2']].value_counts(). Метод посчитает частоту комбинаций значений и вернёт Series с MultiIndex. Это работает с pandas 1.1 и новее.

Какой аналог value_counts в SQL?

SELECT col, COUNT(*) AS cnt FROM table GROUP BY col ORDER BY cnt DESC — точный аналог df['col'].value_counts(). Для долей добавьте оконную функцию COUNT(*)::NUMERIC / SUM(COUNT(*)) OVER (), не забыв привести тип, чтобы не словить целочисленное деление.

Почему у value_counts в выводе написано Name: count?

С pandas 2.0 результирующий Series называется count (а при normalize=Trueproportion). Раньше он наследовал имя столбца. Если вы видите в старых примерах Name: city, dtype: int64, это просто более ранняя версия pandas — на поведение метода смена имени не влияет.