Как освободить память, используемую фреймворком pandas?

У меня есть действительно большой файл csv, который я открыл в pandas следующим образом....

import pandas
df = pandas.read_csv('large_txt_file.txt')

Как только я это сделаю, использование памяти увеличивается на 2 ГБ, что ожидается, потому что этот файл содержит миллионы строк. Моя проблема возникает, когда мне нужно освободить эту память. Я побежал....

del df

Однако использование памяти не уменьшилось. Это неправильный подход к выпуску памяти, используемой фреймом данных pandas? Если да, то каков правильный путь?

Ответ 1

Сокращение использования памяти в Python затруднено, потому что Python фактически не выводит память обратно в операционную систему. Если вы удаляете объекты, тогда память доступна для новых объектов Python, но не free() 'd назад в систему (см. Этот вопрос).

Если вы придерживаетесь числовых массивов numpy, они освобождаются, но объекты в коробке не являются.

>>> import os, psutil, numpy as np
>>> def usage():
...     process = psutil.Process(os.getpid())
...     return process.get_memory_info()[0] / float(2 ** 20)
... 
>>> usage() # initial memory usage
27.5 

>>> arr = np.arange(10 ** 8) # create a large array without boxing
>>> usage()
790.46875
>>> del arr
>>> usage()
27.52734375 # numpy just free()'d the array

>>> arr = np.arange(10 ** 8, dtype='O') # create lots of objects
>>> usage()
3135.109375
>>> del arr
>>> usage()
2372.16796875  # numpy frees the array, but python keeps the heap big

Уменьшение количества фреймов данных

Python сохраняет нашу память на высоком водяном знаке, но мы можем уменьшить общее количество создаваемых нами кадровых фреймов. При изменении вашего фрейма данных предпочитайте inplace=True, поэтому вы не создаете копии.

Еще одна распространенная проблема заключается в копировании ранее созданных фреймов данных в ipython:

In [1]: import pandas as pd

In [2]: df = pd.DataFrame({'foo': [1,2,3,4]})

In [3]: df + 1
Out[3]: 
   foo
0    2
1    3
2    4
3    5

In [4]: df + 2
Out[4]: 
   foo
0    3
1    4
2    5
3    6

In [5]: Out # Still has all our temporary DataFrame objects!
Out[5]: 
{3:    foo
 0    2
 1    3
 2    4
 3    5, 4:    foo
 0    3
 1    4
 2    5
 3    6}

Вы можете исправить это, набрав %reset Out, чтобы очистить историю. В качестве альтернативы вы можете настроить, сколько истории ipython поддерживает с ipython --cache-size=5 (по умолчанию 1000).

Уменьшение размера файлового блока

По возможности избегайте использования объектов dtypes.

>>> df.dtypes
foo    float64 # 8 bytes per value
bar      int64 # 8 bytes per value
baz     object # at least 48 bytes per value, often more

Значения с объектом dtype помещаются в бокс, что означает, что массив numpy просто содержит указатель, и у вас есть полный объект Python в куче для каждого значения в вашем фреймворке данных. Сюда входят строки.

В то время как numpy поддерживает строки фиксированного размера в массивах, pandas не (это вызвало путаницу пользователя). Это может иметь существенное значение:

>>> import numpy as np
>>> arr = np.array(['foo', 'bar', 'baz'])
>>> arr.dtype
dtype('S3')
>>> arr.nbytes
9

>>> import sys; import pandas as pd
>>> s = pd.Series(['foo', 'bar', 'baz'])
dtype('O')
>>> sum(sys.getsizeof(x) for x in s)
120

Вы можете избежать использования строковых столбцов или найти способ представления строковых данных в виде чисел.

Если у вас есть фреймворк данных, который содержит много повторяющихся значений (NaN очень распространен), вы можете использовать разреженную структуру данных для уменьшения объема памяти использование:

>>> df1.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo    float64
dtypes: float64(1)
memory usage: 605.5 MB

>>> df1.shape
(39681584, 1)

>>> df1.foo.isnull().sum() * 100. / len(df1)
20.628483479893344 # so 20% of values are NaN

>>> df1.to_sparse().info()
<class 'pandas.sparse.frame.SparseDataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo    float64
dtypes: float64(1)
memory usage: 543.0 MB

Просмотр использования памяти

Вы можете просмотреть использование памяти (docs):

>>> df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 14 columns):
...
dtypes: datetime64[ns](1), float64(8), int64(1), object(4)
memory usage: 4.4+ GB

Как и для pandas 0.17.1, вы также можете сделать df.info(memory_usage='deep'), чтобы увидеть использование памяти, включая объекты.

Ответ 2

Как отмечено в комментариях, есть некоторые вещи, которые можно попробовать: gc.collect (@EdChum) может, например, очистить материал. По крайней мере, по моему опыту, эти вещи иногда работают, а часто нет.

Есть одна вещь, которая всегда работает, однако, потому что она выполняется на ОС, а не на языке, уровне.

Предположим, что у вас есть функция, которая создает промежуточный огромный DataFrame и возвращает меньший результат (который также может быть DataFrame):

def huge_intermediate_calc(something):
    ...
    huge_df = pd.DataFrame(...)
    ...
    return some_aggregate

Тогда, если вы сделаете что-то вроде

import multiprocessing

result = multiprocessing.Pool(1).map(huge_intermediate_calc, [something_])[0]

Затем функция выполняется в другом процессе. Когда этот процесс завершается, ОС возвращает все ресурсы, которые он использовал. Там действительно ничего не может сделать Python, pandas, сборщик мусора, чтобы остановить это.

Ответ 3

Это решает проблему освобождения памяти для меня!

del [[df_1,df_2]]
gc.collect()
df_1=pd.DataFrame()
df_2=pd.DataFrame()

в кадре данных будет явно установлено значение null

Ответ 4

del df не будет удаляться, если на момент удаления есть ссылка на df. Поэтому вам нужно удалить все ссылки на него с помощью del df, чтобы освободить память.

Итак, все экземпляры, привязанные к df, должны быть удалены, чтобы вызвать сбор мусора.

Используйте objgragh, чтобы проверить, что держится за объекты.

Ответ 5

Я не уверен, но вы можете установить df для пустого фрейма данных, поэтому размер df будет уменьшен

import sys
df=pd.DataFrame()
print("Size of dataframe", sys.getsizeof(df))

Пожалуйста, поправьте меня, если я ошибаюсь