Коротко
categoryхранит набор допустимых значений (categories) и для каждой строки — номер значения (codes).- Для столбцов вроде города, размера, статуса это в разы уменьшает память и ускоряет группировку.
ordered=Trueзадаёт порядок: сортировка и сравнения идут по нему, а не по алфавиту.- Записать можно только значение из набора категорий; новое сначала добавляют через
cat.add_categories. - Категории без строк видны в
value_counts, но не вgroupby: там с pandas 3.0 по умолчаниюobserved=True.
Синтаксис
s.astype("category")
pd.CategoricalDtype(["S", "M", "L"], ordered=True)
s.cat.categories, s.cat.codes
s.cat.add_categories([...]), s.cat.remove_unused_categories()
s.cat.rename_categories({...})Параметры
ПараметрЧто делаетПо умолчанию
categoriesдопустимые значения и их порядокуникальные значения по алфавитуorderedесть ли порядок: сравнения < > и сортировка по немуFalseПримеры
sizes = pd.Series(["M", "S", "L", "M", "M", "S"], name="size")cat = sizes.astype("category")
print(cat.cat.categories) # набор допустимых значений
cat.cat.codes.tolist() # внутри хранятся номераВывод
Index(['L', 'M', 'S'], dtype='str') [1, 2, 0, 1, 1, 2]
order = pd.CategoricalDtype(["S", "M", "L"], ordered=True)
ordered = sizes.astype(order)
print(ordered.sort_values().tolist()) # по заданному порядку, а не по алфавиту
ordered[ordered > "S"].tolist() # сравнения по порядкуВывод
['S', 'S', 'M', 'M', 'M', 'L'] ['M', 'L', 'M', 'M']
big = pd.Series(["Москва", "Санкт-Петербург", "Казань"] * 100_000)
before = big.memory_usage(deep=True, index=False)
after = big.astype("category").memory_usage(deep=True, index=False)
print(f"{before / 2**20:.1f} МБ → {after / 2**20:.1f} МБ")
before // after # во сколько раз меньшеВывод
7.3 МБ → 0.3 МБ 25
cat = sizes.astype(pd.CategoricalDtype(["S", "M", "L", "XL"]))
print(cat.value_counts()) # value_counts показывает и пустую категорию
pd.DataFrame({"size": cat, "n": 1}).groupby("size").size() # groupby — нет (observed=True)Вывод
size M 3 S 2 L 1 XL 0 Name: count, dtype: int64 size S 2 M 3 L 1 dtype: int64
Подробнее — в подводных камнях статьи groupby.
Подводные камни
Новое значение не из набора
cat = sizes.astype("category")
cat[0] = "XXL" # значения нет среди категорийОшибка
TypeError: Cannot setitem on a Categorical with a new category (XXL), set the categories first
cat = sizes.astype("category").cat.add_categories(["XXL"])
cat[0] = "XXL"
cat.tolist()Вывод
['XXL', 'S', 'L', 'M', 'M', 'S']
Когда category не помогает
Если почти все значения разные (имена, адреса, id), категории хранят те же строки плюс номера — памяти уходит даже больше. Выигрыш заметен, когда уникальных значений намного меньше, чем строк.
ids = pd.Series([f"id{i}" for i in range(10_000)]) # 10 000 разных строк
before = ids.memory_usage(deep=True, index=False)
after = ids.astype("category").memory_usage(deep=True, index=False)
after > beforeВывод
True