Лечим проблемы с памятью у Pandas
Работа с данными в pandas, когда их размер не превышает пары гигабайт, проблем не вызывает. Но когда они достигают десятков гигабайт, то обрабатывать их на обычном компьютере становится сложно. Нужно или уменьшать размер датасета или работать с данными, разбитыми на chunk, что долго и не всегда удобно.
Например, в работе при анализе операций клиентов и брокеров на финансовых рынках или транзакций по картам, для выявления мошеннических действий, когда количество таких операций составляет десятки и сотни миллионов, мы получаем долгие часы расчётов или скверное настроение, когда видим сбой вычислений из-за нехватки памяти после многих часов ожидания.
Есть другие инструменты, например, Spark, которые могут обрабатывать большие наборы данных, но для полного использования их возможностей обычно требуется более дорогое оборудование. И обычно их функционал беднее, чем у pandas.
Чтобы не менять инструмент, но при этом реализовать его использование с большими объёмами информации, мы использовали некоторые приёмы.
На первом шаге нам следует определить текущий объём памяти, который занимает датасет.
Если с числовыми значениями всё более или менее очевидно, то с объектами и строками всё запутанней. Pandas загружает в строковые столбцы как object type по умолчанию.
Чтобы заставить Pandas проверять память для каждого связанного строкового значения и возвращать истинный объем памяти, нам нужно установить параметр memory_usage в значение «deep» при вызове DataFrame.info().
Также мы можем использовать метод memory_usage ()
Для того, чтобы подобрать нужный тип для числовых данных нужно понимать пограничные значения каждого типа и подтипа.
Используем numpy.класс iinfo для проверки минимальных и максимальных значений для каждого целочисленного подтипа:
Например, определим максимальное и минимальное значение типа:
Теперь можно использовать такой алгоритм для поиска типа данных, который потребует наименьших затрат памяти.
Но данный алгоритм не эффективен для типа float.
Чтобы помочь найти наиболее эффективный тип пространства для столбца, мы можем использовать функцию pandas.to_numeric () как для float так и для integer, не забывая указать downcast=.
Некоторые столбцы object type могут являться датами, при этом тип datetime занимает меньше памяти. Мы используем функцию pandas.to_datetime() для преобразования столбца в тип datetime.
Начиная с версии 0,15 в Pandas ввели категориальный тип. Тип категории использует целочисленные значения для представления значений в столбце. Pandas использует отдельный словарь, который сопоставляет целочисленные значения с необработанными.
Если в категориальном столбце менее 50% уникальных значений (в противном случае выигрыша в памяти не будет), то его смело можно перевести в данный тип. Но следует учесть, что с данным типом нельзя выполнять вычисления или использовать такие методы, как min () и max ().
Из всего, что мы описали выше, были случаи, когда преобразовывался уже готовый датасет. При загрузке данных мы также можем указать оптимальные типы столбцов при чтении набора данных. И тогда мы имеем на ПК уже готовый файл нужного размера, с которым сразу начинаем работать.
Pandas.read_csv() имеет несколько различных параметров, которые позволяют это сделать. Параметр dtype принимает словарь, содержащий имена столбцов (string) в качестве ключей и объекты типа NumPy в качестве значений.
Параметр parse_dates принимает список строк, содержащих имена столбцов, которые мы хотим разобрать как значения datetime.
Используя параметр usecols, можно указать, какие столбцы мы хотим включить в исследуемые данные.
Резюмируем сказанное выше.
Для оптимизации размера, исследуемого датасета, вы можете использовать следующий алгоритм, который уже применяли мы.
- Для всех столбцов, имеющих object type, попробуйте присвоить этим столбцам нужный тип. Как уже говорилось, по умолчанию Pandas считывает числовые столбцы как float64. Используйте pd.to_numeric чтобы сменить c float64 на 32 или 16, если это возможно.
- Устраните nan и используйте dtypes.
- Устраните или замените недостающие данные перед использованием pd.read_csv. Сделайте пропущенные значения -1 или 0 или что-то, что Pandas не интерпретирует как «nan». Почему? Потому что, если столбец содержит nan, Pandas автоматически делает переменную типом данных, который занимает больше памяти.
- pd.read_csv часто назначает типы данных, которые занимают больше памяти, чем обычно, например, используя float64, когда float16 достаточно. Чтобы устранить эту проблему, можно явно объявить тип данных (и импортировать только необходимые переменные):
- Наконец, об этом мы ещё не говорили, но будет хорошим тоном сохранить и позже импортировать файл с использованием формата pickle (или hd5), так как pd.read_csv часто не хватает памяти даже тогда, когда сам файл не очень большой.
Надеемся, наш опыт поможет и вам в работе.
Если совсем беда с памятью и даже метод из статьи не помогает, то можно поюзать Vaex или Dask, которые имеют Pandas-like API с привычными датафреймами, но не грузят их полностью в память, а работают по кускам.
Спасибо! 👍