Использование типа данных категории в pandas

Open in Colab


telegram

Введение

В предыдущей статьетут перевод на русский язык) я писал о типах данных в pandas; что это такое и как преобразовать данные в соответствующий тип. В этой статье основное внимание будет уделено категориальному типу данных, а также некоторым преимуществам и недостаткам его использования.

Оригинал статьи Криса тут.

Тип данных Category в pandas

В этой статье речь пойдет о категориальных данных. Напоминаю, что категориальные данные - это данные, которые принимают конечное число возможных значений. Например, если мы говорим о таком товаре как футболка, у него могут быть следующие категориальные значения:

Такие атрибуты, как стоимость, цена, количество, обычно являются целыми числами или числами с плавающей точкой.

Является ли переменная категориальной, зависит от ее применения. Поскольку у нас всего 3 цвета рубашек, то это хорошая категориальная переменная. Однако в другом случае "цвет" может представлять тысячи значений.

Не существует жесткого правила, определяющего, сколько значений должна иметь категориальная переменная. Вы должны использовать собственные знания о предметной области, чтобы сделать выбор. В этой статье мы рассмотрим один из подходов к определению категориальных значений.

Тип данных категории (category data type) в pandas - это гибридный тип. Во многих случаях он выглядит и ведет себя как строка, но внутренне представлен массивом целых чисел. Это позволяет сортировать данные в произвольном порядке и более эффективно их хранить.

В конце концов, почему мы так беспокоимся об использовании категориальных значений? Есть 3 основные причины:

Подготовка данных

Одно из основных преимуществ категориальных типов данных - более эффективное использование памяти. Для демонстрации этого будем использовать большой набор данных из Центров услуг Медикэр и Медикэйд в США. Этот набор данных включает csv файл размером 500 МБ+, содержащий информацию о платежах за исследования врачам и больницам в 2017 финансовом году (прямая ссылка на скачивание архива).

Сначала настройте импорт и прочтите все данные:

Я установил параметр low_memory=False, как указано в предупрждении:

interactiveshell.py:2728: DtypeWarning: Columns (..) have mixed types. Specify dtype option on import or set low_memory=False.
interactivity=interactivity, compiler=compiler, result=result)

Не стесняйтесь прочитать об этом параметре в документации по read_csv.

В этом наборе данных есть одна интересная особенность: в нем более 176 столбцов, но многие из них пусты. Я нашел решение на stack overflow, позволяющее быстро удалить столбцы, в которых не менее 90% данных отсутствуют.

Думаю, что это решение может быть полезно и для других:

Давайте посмотрим на размер различных кадров данных. Вот исходный набор данных:

CSV-файл размером 560 МБ занимает в памяти около 904 МБ. Кажется, что это много, но даже в слабом ноутбуке есть несколько гигабайт оперативной памяти, поэтому нам не понадобятся специализированные инструменты обработки.

Вот набор данных, который мы будем использовать в оставшейся части Блокнота:

Теперь, когда у нас есть 33 столбца, занимающих 174,6 МБ памяти, давайте посмотрим, какие столбцы могут стать хорошими кандидатами для категориального типа данных.

Чтобы упростить задачу, я создал небольшую вспомогательную функцию для формирования кадра данных, показывающего все уникальные значения в столбце.

Эта таблица указывает на несколько моментов, которые помогают определить категориальные значения. Во-первых, когда мы превышаем 649 уникальных значений, то происходит резкий скачок. Это может стать полезным пределом для данного набора.

Кроме того, поля с датами не следует преобразовывать в категориальные.

Самый простой способ преобразовать столбец в категориальный тип - использовать astype('category').

Мы можем использовать цикл для преобразования всех столбцов, которые нам нужны, используя astype('category'):

Если мы вызовем df.info() для просмотра используемой памяти, то увидим уменьшение кадра данных с 175 МБ до 92 МБ:

Это впечатляет! Мы сократили использование памяти почти вдвое, просто преобразовав большинство столбцов в категориальные значения.

Есть еще одна функция, которую можно использовать с категориальными данными - определение пользовательского порядка.

Чтобы проиллюстрировать это, давайте сделаем краткую сводку общей суммы платежей, произведенных с использованием одного из способов оплаты:

Если мы хотим изменить порядок Covered_Recipient_Type, то нам нужно определить настраиваемый CategoricalDtype:

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

Теперь можем увидеть порядок сортировки в groupby:

Можете указать это преобразование при чтении CSV файла, передав словарь имен и типов столбцов через параметр dtype:

Производительность

Мы показали, что размер кадра данных уменьшается за счет преобразования значений в категориальные типы данных. Влияет ли это на другие сферы деятельности? Ответ положительный.

Вот пример операции groupby над категориальными (categorical) типами данных против типа данных object.

Сначала выполните анализ исходного кадра данных:

Далее кадр данных с категориальными типами:

Мы ускорили код в 10 раз с 55,3 мс до 4,17 мс. Вы можете себе представить, что на гораздо больших наборах данных ускорение может быть еще большим!

Осторожно

Категориальные данные кажутся довольно изящными. Это экономит память и ускоряет код, так почему бы не использовать их везде? Что ж, Дональд Кнут прав, когда предупреждает о преждевременной оптимизации (рекомендую сайт на русском языке):

Программисты тратят огромное количество времени, размышляя и беспокоясь о некритичных местах кода, и пытаются оптимизировать их, что исключительно негативно сказывается на последующей отладке и поддержке. Мы должны вообще забыть об оптимизации в, скажем, 97% случаев. Поспешная оптимизация является корнем всех зол. И, напротив, мы должны уделить все внимание оставшимся 3%.

В приведенных выше примерах код работает быстрее, но это не имеет значения, когда он используется для быстрых сводных действий, которые выполняются нечасто. Кроме того, вся работа по вычислению и преобразованию в категориальные данные, вероятно, не стоит затраченных усилий для этого набора данных и простого анализа.

Также категориальные данные могут привести к неожиданным результатам при использовании в реальном мире. Приведенные ниже примеры проиллюстрируют несколько проблем.

Давайте создадим простой кадр данных с одной упорядоченной категориальной переменной, которая представляет статус клиента. Этот тривиальный пример выделит некоторые потенциальные тонкие ошибки при работе с категориальными переменными.

Стоит отметить, что в примере показано, как использовать astype() для преобразования в упорядоченную категорию за один шаг вместо двухэтапного процесса, который использовался ранее.

В результате получается простой кадр данных, который выглядит так:

Можем рассмотреть категориальный столбец более подробно:

Все выглядит хорошо.

Мы видим, что все данные присутствуют и Gold > Silver.

Теперь давайте добавим еще один кадр данных и применим ту же категорию к столбцу статуса:

Хм. Что-то случилось с нашим статусом.

Посмотрим на столбец:

Поскольку мы не определили Bronze как действующий статус, то получаем значение NaN. Pandas делает это по вполне уважительной причине. Предполагается, что вы заранее определили все допустимые категории.

Можно только представить, насколько запутанной могла бы стать эта проблема, если бы вы ее сразу не нашли.

Этот сценарий относительно легко обнаружить, но что бы вы сделали, если бы было 100 значений, а данные не были очищены и нормализованы?

Вот еще один хитрый пример, когда вы можете "потерять" категориальный тип данных:

Все выглядит нормально, но при дополнительном осмотре мы потеряли категоририальный тип данных:

В этом примере все данные на месте, но тип был преобразован в object. Опять же, это попытка pandas объединить данные без ошибок и без предположений. Если вы хотите преобразовать данные в тип категории, то можете использовать astype('category').

Общие рекомендации

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

  1. Не думайте, что вам нужно преобразовать все категориальные данные в тип данных категории (category) pandas.
  2. Если набор данных занимает значительный процент используемой памяти, рассмотрите возможность использования категориальных типов данных.
  3. Если у вас очень серьезные проблемы с производительностью с часто выполняемыми операциями, обратите внимание на использование категориальных данных.
  4. Если вы используете категориальные данные, добавьте несколько проверок, чтобы убедиться, что данные чистые и полные, перед преобразованием в тип категории pandas. Кроме того, проверьте значения NaN после объединения или преобразования кадров данных.

Надеюсь, эта статья была полезной. Категориальные типы данных в pandas могут быть очень полезны. Однако есть несколько проблем, на которые нужно обратить внимание, чтобы не запутаться в последующей обработке.