Нашли ошибку?
среднийpandas 3.0.6

Интервалы: cut и qcut

Разложить числа по интервалам — возрастные группы, ценовые сегменты, квартили.

Нужно знать:Категориальный тип

Содержание статьи

Коротко

  • pd.cut(s, bins=[…]) — по заданным границам; bins=3 — на 3 интервала одинаковой ширины.
  • Интервалы по умолчанию (a, b]: правая граница входит, левая нет. right=False — [a, b).
  • labels=[…] — свои названия групп вместо интервалов.
  • pd.qcut(s, q=4) — по квантилям: в каждой группе примерно одинаковое число значений.
  • Результат — категориальный Series с упорядоченными категориями; значения вне интервалов — NaN.

Синтаксис

pd.cut(s, bins, right=True, labels=None, include_lowest=False)
pd.qcut(s, q, labels=None, duplicates="raise")
pd.qcut(s, q, retbins=True)        # вернуть и границы

Параметры

ПараметрЧто делаетПо умолчанию
binsв cut: список границ или число интервалов равной шириныобязательный
qв qcut: число групп или список квантилей [0, 0.25, …, 1]обязательный
rightв cut: правая граница входит в интервалTrue
labelsназвания групп; False — номера группNone
include_lowestв cut: включить левую границу первого интервалаFalse
duplicatesв qcut: "drop" — убрать совпадающие границы вместо ошибки"raise"

Примеры

Данные для примеровиспользуются во всех примерах ниже
ages = pd.Series([17, 23, 35, 41, 18, 52, 67, 29, 30, 45], name="age")
Свои границы
pd.cut(ages, bins=[0, 18, 30, 50, 100])          # интервалы (a, b]: правая граница входит
Вывод
0      (0, 18]
1     (18, 30]
2     (30, 50]
3     (30, 50]
4      (0, 18]
5    (50, 100]
6    (50, 100]
7     (18, 30]
8     (18, 30]
9     (30, 50]
Name: age, dtype: category
Categories (4, interval[int64, right]): [(0, 18] < (18, 30] < (30, 50] < (50, 100]]
Названия групп
groups = pd.cut(ages, bins=[0, 18, 30, 50, 100], labels=["до 18", "18–30", "30–50", "50+"])
groups.value_counts(sort=False)
Вывод
age
до 18    2
18–30    3
30–50    3
50+      2
Name: count, dtype: int64
Левая граница входит
pd.cut(ages, bins=[0, 18, 30, 50, 100], right=False).value_counts(sort=False)   # [a, b)
Вывод
age
[0, 18)      1
[18, 30)     3
[30, 50)     4
[50, 100)    2
Name: count, dtype: int64
Интервалы равной ширины
pd.cut(ages, bins=3)                              # 3 интервала одинаковой ширины
Вывод
0     (16.95, 33.667]
1     (16.95, 33.667]
2    (33.667, 50.333]
3    (33.667, 50.333]
4     (16.95, 33.667]
5      (50.333, 67.0]
6      (50.333, 67.0]
7     (16.95, 33.667]
8     (16.95, 33.667]
9    (33.667, 50.333]
Name: age, dtype: category
Categories (3, interval[float64, right]): [(16.95, 33.667] < (33.667, 50.333] < (50.333, 67.0]]
Квартили
pd.qcut(ages, q=4, labels=["Q1", "Q2", "Q3", "Q4"]).value_counts(sort=False)   # примерно поровну в каждой
Вывод
age
Q1    3
Q2    2
Q3    2
Q4    3
Name: count, dtype: int64
Границы квантилей
edges = pd.qcut(ages, q=4, retbins=True)[1]
edges                                             # границы квартилей
Вывод
array([17. , 24.5, 32.5, 44. , 67. ])

Подводные камни

Значения вне интервалов

Всё, что не попало ни в один интервал, — в том числе значение, равное левой границе первого интервала, — становится NaN. Проверяйте isna().sum() после cut или используйте include_lowest=True.

Значения вне границ
pd.cut(pd.Series([5, 150]), bins=[0, 18, 100])    # значение вне интервалов → NaN
Вывод
0    (0.0, 18.0]
1            NaN
dtype: category
Categories (2, interval[int64, right]): [(0, 18] < (18, 100]]

Много одинаковых значений в qcut

Совпадающие границыошибка: ValueError
pd.qcut(pd.Series([1, 1, 1, 1, 2, 3]), q=4)       # границы квантилей совпадают
Ошибка
ValueError: Bin edges must be unique: Index([1.0, 1.0, 1.0, 1.75, 3.0], dtype='float64').
You can drop duplicate edges by setting the 'duplicates' kwarg

Передайте duplicates="drop" — групп станет меньше — или используйте rank(method="first") перед qcut.