Как оптимизировать размер памяти в Python при обработке крупных файлов
Нередко мы сталкиваемся с большими объемами данных, которые требуют дополнительной обработки с помощью известной всем библиотеки Pandas. Однако, загружая или сохраняя огромные датасеты, неприятно столкнуться с ошибкой Memory error. В таких ситуациях применение таких методов, как .drop_duplicates() (удаление дубликатов) или .dropna() (удаление пустых строк) слабо влияет на сокращение занимаемого объема памяти.
Существует несколько способов эффективного решения проблем с памятью.
Изменение типов данных
float64 → float16 или int64 → int16
Как показывает практика, далеко не для всех задач требуется высокая точность при работе c числовыми значениями. Таким образом, достаточно воспользоваться типом float16 (float32) вместо float64. Аналогичную замену можно сделать и для целочисленных значений.
object → datetime64
Часто при считывании данных, оказывается, что они хранятся в строковом формате. Перевод данных в формат даты datetime64 резервирует существенно меньше памяти в отличие от типа object
object → category
Обратите внимание, что категории на самом деле являются формой динамического перечисления, они наиболее полезны, когда диапазон возможных значений фиксирован, конечен и много меньше общего количества значений в массиве с учетом их повторений.
Разберём, замену типов данных на примере загрузки csv файла объемом (833 МБ, 8.1 млн строк), объем не большой, однако, на нем наглядно можно показать значительное сокращение резервируемой памяти.
Загружаем датасет со следующим набором полей:
'ID' – уникальный номер события
'EVENT_DATE_TIME' – дата и время события
'EVENT_CODE' – код операции (значение из ограниченного набора)
'USER_LOGIN' – логин пользователя
'EMPLOYEE_LOGIN' – логин сотрудника, подтверждающего операцию
'OPERATION_NAME' – наименование операции (значение из ограниченного набора)
Видим, что он занимает 2.9 Гб.
Поскольку, поле 'ID' – это уникальный идентификационный номер события и имеет неповторяющиеся значения высокого порядка, целесообразно оставить эти значения в с типом int64. Поле 'EVENT_DATE_TIME' содержит дату и время в виде «гггг-мм-дд чч:мм:сс», разумно конвертировать в тип данных datetime64. В рамках рассматриваемого набора данных можно считать оставшиеся поля категориальными, производя замену object → category для всех полей кроме 'ID' и 'EVENT_DATE_TIME' получаем итоговую экономию памяти c 2,9 Гб до 0,4 Гб.
Другой способ позволяет выбрать только необходимые для работы столбцы и задать типы данных при чтении файла. Удобно применять этот прием в случае многочисленного набора столбцов, когда для анализа требуется только несколько из них.
Обработка файла фрагментами (in chanks)
В тех случаях, когда объем данных слишком велик и не удается загрузить весь файл целиком, следует прочитать его по частям, разбивая его параметром chunksize. Загрузим ранее описанный датасет, при этом мы будем заменять тип данных при чтении, как в предыдущем примере и методом append() будем добавлять преобразованные в итоговый Dataframe. Однако, поля с категориальными данными не корректно считывать сразу с типом категории, так как такой формат подразумевает хранение ссылок на уникальные значения. Будет лучше преобразовывать датафрейм после применения метода append().
В результате выполнения занимаемый объем памяти составляет 664 Мб.
Если вы знаете, почему в следствии преобразования типов данных различными способами изменяется итоговый объем занимаемой памяти, напишите об этом в комментариях, возможно в понимании этих процессов можно будет добиться наилучших результатов!
Сжатие данных при сохранении результата
Работая над несколькими проектами в области Data Analysis и Data Science, мы часто сталкиваемся с проблемой ограничения места на локальном диске.
Самым очевидным решением оказывается архивирование результата с помощью метода .to_csv() с параметром compression='gzip'. К примеру, csv файл размером 560 Мб можно сохранить через следующую команду
В результате формируется архив, который занимает уже 156 Мб. Архивированный файл можно считать, как обычный файл, с помощью метода .read_csv(), не теряя при этом функциональности.
Перечисленные приемы и методы будут особенно полезны при работе с несколькими датафреймами в рамках одного проекта. В частности, сокращение объёма используемой памяти будет наиболее эффективным с применением таких методов, как .concat(), .append(), merge(), join() и т.п.