Como faço para liberar a memória usada por um dataframe do pandas?


111

Eu tenho um arquivo csv muito grande que abri no pandas da seguinte maneira ....

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

Depois de fazer isso, meu uso de memória aumenta em 2 GB, o que é esperado porque este arquivo contém milhões de linhas. Meu problema surge quando preciso liberar essa memória. Eu corri ....

del df

No entanto, meu uso de memória não diminuiu. É esta a abordagem errada para liberar memória usada por um quadro de dados do pandas? Se for, qual é a maneira adequada?


3
está correto, o coletor de lixo pode não liberar a memória imediatamente, você também pode importar o gcmódulo e chamar, gc.collect()mas ele pode não recuperar a memória
— EdChum

del dfnão é chamado diretamente após a criação do df certo? Acho que há referências ao df no momento em que você exclui o df. Portanto, ele não será excluído em vez disso, ele exclui o nome.
— Marlon Abeykoon

4
Se a memória recuperada pelo coletor de lixo é ou não devolvida ao sistema operacional depende da implementação; a única garantia que o coletor de lixo oferece é que a memória recuperada pode ser usada pelo processo Python atual para outras coisas, em vez de solicitar ou até mais memória do sistema operacional.
— chepner

Estou ligando para del df logo após a criação. Não adicionei nenhuma outra referência ao df. Tudo o que fiz foi abrir o ipython e executar essas três linhas de código. Se eu executar o mesmo código em algum outro objeto que consome muita memória, digamos, uma matriz numpy. del nparray funciona perfeitamente
— b10hazard

@ b10hazard: Que tal algo como df = ''no final do seu código? Parece limpar a RAM usada pelo dataframe.
— jibounet

Respostas:


119

Reduzir o uso de memória em Python é difícil, porque Python não libera memória de volta para o sistema operacional . Se você excluir objetos, a memória estará disponível para novos objetos Python, mas não será free()devolvida ao sistema ( veja esta questão ).

Se você se limitar a matrizes numpy numéricas, elas serão liberadas, mas os objetos em caixas não.

>>> 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

Reduzindo o número de Dataframes

Python mantém nossa memória com marca d'água alta, mas podemos reduzir o número total de dataframes que criamos. Ao modificar seu dataframe, prefira inplace=True, para não criar cópias.

Outra pegadinha comum é manter cópias de dataframes criados anteriormente em 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}

Você pode corrigir isso digitando %reset Outpara limpar seu histórico. Alternativamente, você pode ajustar quanto histórico o ipython mantém ipython --cache-size=5(o padrão é 1000).

Reduzindo o tamanho do dataframe

Sempre que possível, evite usar tipos de objetos.

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

Os valores com um objeto dtype são encaixotados, o que significa que o array numpy contém apenas um ponteiro e você tem um objeto Python completo no heap para cada valor em seu dataframe. Isso inclui strings.

Enquanto o numpy suporta strings de tamanho fixo em arrays, o pandas não ( isso causa confusão ao usuário ). Isso pode fazer uma diferença significativa:

>>> 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

Você pode evitar o uso de colunas de string ou encontrar uma maneira de representar dados de string como números.

Se você tiver um dataframe que contém muitos valores repetidos (NaN é muito comum), você pode usar uma estrutura de dados esparsa para reduzir o uso de memória:

>>> 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

Visualizando o uso da memória

Você pode ver o uso de memória ( documentos ):

>>> 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

A partir do pandas 0.17.1, você também pode fazer df.info(memory_usage='deep')para ver o uso de memória, incluindo objetos.


2
Isso deve ser marcado como 'Resposta aceita'. Ele explica de maneira breve, mas clara, como o python mantém a memória mesmo quando realmente não precisa dela. As dicas para economizar memória são todas sensatas e úteis. Como outra dica, eu adicionaria o uso de 'multiprocessamento' (conforme explicado na resposta de
— @Ami

46

Conforme observado nos comentários, existem algumas coisas para tentar: gc.collect(@EdChum) pode limpar coisas, por exemplo. Pelo menos pela minha experiência, essas coisas às vezes funcionam e muitas vezes não.

No entanto, há uma coisa que sempre funciona porque é feito no nível do sistema operacional, não no idioma.

Suponha que você tenha uma função que cria um grande DataFrame intermediário e retorna um resultado menor (que também pode ser um DataFrame):

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

Então, se você fizer algo como

import multiprocessing

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

Em seguida, a função é executada em um processo diferente . Quando esse processo é concluído, o SO retoma todos os recursos que usou. Não há realmente nada que Python, pandas, o coletor de lixo possam fazer para impedir isso.


1
@ b10hazard Mesmo sem os pandas, nunca entendi completamente como a memória Python funciona na prática. Essa técnica tosca é a única coisa em que confio.
— Ami Tavory

9
Funciona muito bem. No entanto, em um ambiente ipython (como o notebook jupyter), descobri que você precisa .close () e .join () ou .terminate () o pool para se livrar do processo gerado. A maneira mais fácil de fazer isso, desde o Python 3.3, é usar o protocolo de gerenciamento de contexto: o with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something])que leva a fechar o pool uma vez feito.
— Zertrin,

2
Isso funciona bem, mas não se esqueça de encerrar e ingressar no pool após a conclusão da tarefa.
— Andrey Nikishaev

1
Depois de ler várias vezes sobre como reivindicar de volta a memória de um objeto Python, essa parece ser a melhor maneira de fazer isso. Crie um processo e, quando esse processo for encerrado, o sistema operacional libera a memória.
— muammar

1
Talvez ajude alguém, ao criar o Pool tente usar maxtasksperchild = 1 para liberar o processo e gerar um novo após o trabalho ser concluído.
— giwiro

22

Isso resolve o problema de liberar a memória pra mim !!!

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

o quadro de dados será explicitamente definido como nulo


1
Por que dataframes adicionados à sublista [[df_1, df_2]]? Algum motivo específico? Por favor explique.
— goks de

5
Por que você simplesmente não usa as duas últimas afirmações? Não acho que você precise das duas primeiras declarações.
— spacedustpi

3

del dfnão será excluído se houver qualquer referência a dfno momento da exclusão. Portanto, você precisa excluir todas as referências a ele del dfpara liberar a memória.

Portanto, todas as instâncias vinculadas a df devem ser excluídas para acionar a coleta de lixo.

Use objgragh para verificar qual está segurando os objetos.


o link aponta para objgraph ( mg.pov.lt/objgraph ), é um erro de digitação em sua resposta, a menos que haja um objgragh
— SatZ

1

Parece que há um problema com o glibc que afeta a alocação de memória no Pandas: https://github.com/pandas-dev/pandas/issues/2659

O patch monkey detalhado neste problema resolveu o problema para mim:

# monkeypatches.py

# Solving memory leak problem in pandas
# https://github.com/pandas-dev/pandas/issues/2659#issuecomment-12021083
import pandas as pd
from ctypes import cdll, CDLL
try:
    cdll.LoadLibrary("libc.so.6")
    libc = CDLL("libc.so.6")
    libc.malloc_trim(0)
except (OSError, AttributeError):
    libc = None

__old_del = getattr(pd.DataFrame, '__del__', None)

def __new_del(self):
    if __old_del:
        __old_del(self)
    libc.malloc_trim(0)

if libc:
    print('Applying monkeypatch for pd.DataFrame.__del__', file=sys.stderr)
    pd.DataFrame.__del__ = __new_del
else:
    print('Skipping monkeypatch for pd.DataFrame.__del__: libc or malloc_trim() not found', file=sys.stderr)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.