Нашли ошибку?
Скачать чистый
урок 3 из 4≈ 15 минупражнения: 0 из 3

Интервалы

Превратить числа в группы: pd.cut делит по заданным границам и подписывает интервалы, pd.qcut — на равные по числу строк части. Как читать запись (0, 15], что происходит на границах и за ними.

Python запустится при первом выполнении

Точная температура — 17.3 или 18.1 градуса — нужна редко. Обычно хватает «прохладно», «тепло», «жарко». Так же с ценами («дешёвые», «средние», «дорогие»), возрастом, сроками. Превращение чисел в группы по интервалам — частая операция, и для неё есть готовая функция.

pd.cut: свои границы

pd.cut получает столбец и список границ bins. Четыре интервала задают пять границ:

[ ]Дневной максимум — по интервалам
import pandas as pd

weather = pd.read_csv("data/weather.csv", parse_dates=["date"])
moscow = weather[weather["city"] == "Москва"].copy()
pd.cut(moscow["temp_max"], bins=[-50, 0, 15, 25, 50]).head(3)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
0    (-50, 0]
1    (-50, 0]
2    (-50, 0]
Name: temp_max, dtype: category
Categories (4, interval[int64, right]): [(-50, 0] < (0, 15] < (15, 25] < (25, 50]]

Вместо температуры в каждой строке теперь интервал. Запись (-50, 0] читается как в математике: круглая скобка — граница не входит, квадратная — входит. То есть «больше −50 и не больше 0».

Тип результата — category, категория: значение из заранее известного набора. Строка Categories внизу перечисляет набор, а знаки < показывают, что у категорий есть порядок.

[ ]Сколько дней в каждом интервале
pd.cut(moscow["temp_max"], bins=[-50, 0, 15, 25, 50]).value_counts().sort_index()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
temp_max
(-50, 0]    106
(0, 15]     129
(15, 25]    121
(25, 50]      9
Name: count, dtype: int64

Порядок пригодился: sort_index расставил интервалы по шкале — от холодных к тёплым.

Интервалам можно дать названия — параметром labels. Названий должно быть на одно меньше, чем границ:

[ ]Подписи вместо интервалов
names = ["ниже нуля", "прохладно", "тепло", "жарко"]
moscow["kind"] = pd.cut(moscow["temp_max"], bins=[-50, 0, 15, 25, 50], labels=names)
moscow[["date", "temp_max", "kind"]].head(3)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
date temp_max kind
0 2025-01-01 -3.2 ниже нуля
1 2025-01-02 -3.2 ниже нуля
2 2025-01-03 -3.7 ниже нуля
[ ]Дни по типам
moscow["kind"].value_counts().sort_index()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
kind
ниже нуля    106
прохладно    129
тепло        121
жарко          9
Name: count, dtype: int64
[ ]Упражнение 1Климат Сочи решено

Добавьте в таблицу sochi столбец kind — тип дня по temp_max с теми же границами и подписями, что для Москвы: до 0 — «ниже нуля», до 15 — «прохладно», до 25 — «тепло», выше — «жарко». В kinds сохраните число дней каждого типа по порядку шкалы.

sochi = weather[weather["city"] == "Сочи"].copy()
# добавьте в sochi столбец kind
kinds = ...

Дней «ниже нуля» в Сочи нет, но строка с нулём в ответе осталась: категория помнит весь набор значений, даже те, что не встретились.

Границы

Куда попадёт значение, равное границе? По умолчанию — в интервал слева от неё: правая граница входит.

[ ]Правая граница входит
marks = pd.Series([0, 15, 15.1, 25])
print(pd.cut(marks, bins=[0, 15, 25]).tolist())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[nan, Interval(0.0, 15.0, closed='right'), Interval(15.0, 25.0, closed='right'), Interval(15.0, 25.0, closed='right')]

В списке интервалы показаны подробной записью: closed='right' значит «закрыт справа», то есть (0, 15]. Ровно 15 — это ещё (0, 15], а 15.1 — уже (15, 25]. Ноль не попал никуда: левая граница первого интервала не входит, и вместо интервала стоит nan.

Параметр right=False переворачивает правило: входит левая граница, правая — нет.

[ ]Левая граница входит
print(pd.cut(marks, bins=[0, 15, 25], right=False).tolist())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
[Interval(0.0, 15.0, closed='left'), Interval(15.0, 25.0, closed='left'), Interval(15.0, 25.0, closed='left'), nan]

Значения за пределами границ тоже становятся пропусками — без ошибки и предупреждения:

[ ]60 лет — за последней границей
ages = pd.Series([5, 15, 30, 60])
pd.cut(ages, bins=[0, 15, 30], labels=["дети", "молодые"])
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
0       дети
1       дети
2    молодые
3        NaN
dtype: category
Categories (2, str): ['дети' < 'молодые']
Вопрос

Какую подпись получит значение 10: pd.cut(pd.Series([10]), bins=[0, 10, 20], labels=["низкий", "высокий"])?

[ ]Упражнение 2Скорость доставки решено

Добавьте в delivery столбец speed по сроку days: до 3 дней включительно — «быстро», 4–5 дней — «нормально», 6 дней и больше — «долго». Самый долгий срок в данных — 13 дней. В speed_share сохраните доли каждой группы по порядку шкалы.

delivery = pd.read_csv("data/delivery_h1.csv")
# добавьте в delivery столбец speed
speed_share = ...

Пропуски в days остались пропусками в speed — их 36, и в доли они не вошли.

Пояснение с ответом появится, когда решите упражнение «Скорость доставки».

Равные интервалы и равные группы

Если вместо списка границ передать число, pd.cut сам разделит диапазон на столько интервалов равной ширины:

[ ]Ветер: три интервала одинаковой ширины
pd.cut(moscow["wind_ms"], bins=3).value_counts().sort_index()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
wind_ms
(0.487, 4.867]    272
(4.867, 9.233]     81
(9.233, 13.6]      12
Name: count, dtype: int64

Ширина одинаковая, а строк в интервалах — очень разное число: сильный ветер бывает редко. Левая граница первого интервала (0.487) чуть меньше наименьшего значения (0.5) — pandas сдвигает её, чтобы минимум тоже попал внутрь: ведь левая граница не входит.

Функция pd.qcut делит иначе — по квантилям: так, чтобы в каждой группе оказалось примерно поровну строк. Границы она подбирает сама:

[ ]Выручка строки: четыре группы по четверти строк
orders = pd.read_csv("data/shop_orders.csv")
revenue = orders["price"] * orders["quantity"]
pd.qcut(revenue, 4).value_counts().sort_index()
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
(149.999, 535.0]    612
(535.0, 1080.0]     665
(1080.0, 1580.0]    598
(1580.0, 9030.0]    573
Name: count, dtype: int64

Границы — знакомые квартили: 535, 1080 и 1580 ₽. Группы получились не совсем равными, потому что многие строки имеют одинаковую выручку, а делить одинаковые значения между группами нельзя.

Когда что выбирать: pd.cut — когда границы имеют смысл сами по себе (ноль градусов, 1000 рублей, совершеннолетие); pd.qcut — когда нужны группы «нижняя четверть, верхняя четверть» относительно самих данных.

[ ]Упражнение 3Размер покупки решено

Добавьте в orders столбец size — четверть, в которую попадает выручка строки revenue: «малая», «средняя», «крупная», «очень крупная». В big сохраните строки верхней четверти, а в big_min — наименьшую выручку среди них.

# добавьте в orders столбец size
big = ...
big_min = ...

Итоги

  • pd.cut(столбец, bins=[...], labels=[...]) — группы по своим границам; подписей на одну меньше, чем границ.
  • (a, b] — правая граница входит; right=False — входит левая.
  • Значения за границами становятся NaN: берите границы с запасом.
  • pd.cut(столбец, bins=n) — n интервалов равной ширины; pd.qcut(столбец, n) — n групп с примерно равным числом строк.

Подробнее в справочнике

Нашли ошибку?