¿Cómo leo un archivo csv grande con pandas?

194

Estoy tratando de leer un archivo csv grande (aprox. 6 GB) en pandas y recibo un error de memoria:

MemoryError                               Traceback (most recent call last)
<ipython-input-58-67a72687871b> in <module>()
----> 1 data=pd.read_csv('aphro.csv',sep=';')

...

MemoryError: 

¿Alguna ayuda en esto?

Rajkumar Kumawat
fuente
3
Curiosamente, se hizo una pregunta muy similar casi un año antes de esta ...
DarkCygnus
¿Responde esto a tu pregunta? El trabajo de "datos grandes" fluye usando pandas
AMC

Respuestas:

261

El error muestra que la máquina no tiene suficiente memoria para leer todo el CSV en un DataFrame al mismo tiempo. Suponiendo que no necesita todo el conjunto de datos en la memoria al mismo tiempo, una forma de evitar el problema sería procesar el CSV en fragmentos (especificando el chunksizeparámetro):

chunksize = 10 ** 6
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)

El chunksizeparámetro especifica el número de filas por fragmento. (El último fragmento puede contener menos que chunksizefilas, por supuesto).

unutbu
fuente
17
generalmente necesita 2 veces la memoria final para leer algo (de csv, aunque otros formatos son mejores para tener requisitos de memoria más bajos). FYI esto es cierto para tratar de hacer casi cualquier cosa a la vez. Mucho mejor fragmentarlo (que tiene un uso constante de memoria).
Jeff
24
@altabq: El problema aquí es que no tenemos suficiente memoria para construir un solo DataFrame que contenga todos los datos. La solución anterior intenta hacer frente a esta situación reduciendo los fragmentos (por ejemplo, agregando o extrayendo solo la información deseada) un fragmento a la vez, ahorrando así memoria. Hagas lo que hagas, NO llames DF.append(chunk)dentro del bucle. Eso usará O(N^2)operaciones de copia. Es mejor para anexar los datos agregados a una lista , y luego construir la trama de datos de la lista con una llamada a pd.DataFrameo pd.concat(dependiendo del tipo de datos agregados).
unutbu
12
@altabq: la llamada DF.append(chunk)en bucle requiere O(N^2)operaciones de copia donde Nestá el tamaño de los fragmentos, porque cada llamada a DF.appenddevuelve un nuevo DataFrame. Llamar pd.DataFrameo pd.concat una vez fuera del bucle reduce la cantidad de copias O(N).
unutbu
55
@Pyderman: Sí, el chunksizeparámetro se refiere al número de filas por trozo. El último fragmento puede contener menos que chunksizefilas, por supuesto.
unutbu
77
@Pyderman: Sí; llamar pd.concat([list_of_dfs]) una vez después del ciclo es mucho más rápido que llamar pd.concato df.appendmuchas veces dentro del ciclo. Por supuesto, necesitará una cantidad considerable de memoria para mantener todo el csv de 6GB como un DataFrame.
unutbu
85

La fragmentación no siempre debe ser el primer puerto de escala para este problema.

  1. ¿El archivo es grande debido a datos no numéricos repetidos o columnas no deseadas?

    Si es así, a veces puede ver grandes ahorros de memoria al leer las columnas como categorías y seleccionar las columnas requeridas a través del parámetro pd.read_csv usecols .

  2. ¿Su flujo de trabajo requiere cortar, manipular, exportar?

    Si es así, puede usar dask.dataframe para cortar, realizar sus cálculos y exportar iterativamente. El fragmentación se realiza en silencio por dask, que también admite un subconjunto de pandas API.

  3. Si todo lo demás falla, lea línea por línea a través de fragmentos.

    Trozo a través de pandas o a través de la biblioteca csv como último recurso.

jpp
fuente
3
No estaba al tanto de Dask. +100 por eso!
noamtm
34

Procedí así:

chunks=pd.read_table('aphro.csv',chunksize=1000000,sep=';',\
       names=['lat','long','rf','date','slno'],index_col='slno',\
       header=None,parse_dates=['date'])

df=pd.DataFrame()
%time df=pd.concat(chunk.groupby(['lat','long',chunk['date'].map(lambda x: x.year)])['rf'].agg(['sum']) for chunk in chunks)
Rajkumar Kumawat
fuente
22
¿Hay alguna razón por la que cambiaste read_csva read_table?
Pyderman
33

Para datos grandes, le recomiendo que use la biblioteca "dask",
por ejemplo:

# Dataframes implement the Pandas API
import dask.dataframe as dd
df = dd.read_csv('s3://.../2018-*-*.csv')

Puede leer más de la documentación aquí .

Otra gran alternativa sería usar modin porque toda la funcionalidad es idéntica a la de los pandas, pero aprovecha las bibliotecas de marcos de datos distribuidos como dask.

Simbarashe Timothy Motsi
fuente
11
Cualquier beneficio sobre los pandas, podría apreciar agregar algunos consejos más
PirateApp
2
No he usado Dask durante mucho tiempo, pero las principales ventajas en mis casos de uso fueron que Dask puede ejecutarse en paralelo en varias máquinas, también puede caber datos como segmentos en la memoria.
Simbarashe Timothy Motsi
2
¡Gracias! es un reemplazo para los pandas o funciona sobre los pandas como una capa
PirateApp
3
Bienvenido, funciona como un contenedor para Numpy, Pandas y Scikit-Learn.
Simbarashe Timothy Motsi
1
He tratado de enfrentar varios problemas con Dask y siempre arroja un error para todo. Incluso con trozos también arroja errores de memoria. Ver stackoverflow.com/questions/59865572/…
Genarito
10

La respuesta anterior ya está satisfaciendo el tema. De todos modos, si necesita todos los datos en la memoria, eche un vistazo a bcolz . Está comprimiendo los datos en la memoria. He tenido muy buena experiencia con eso. Pero le faltan muchas características de pandas

Editar: Obtuve tasas de compresión de aproximadamente 1/10 o el tamaño original, creo, por supuesto, dependiendo del tipo de datos. Las características importantes que faltaban eran los agregados.

Etiqueta
fuente
2
Mejore esta respuesta diciéndonos a) qué relaciones de compresión obtiene y b) ¿qué características principales de los pandas falta? ¿Puede manejar NA? ¿instrumentos de cuerda? categóricos? ¿fechas?
smci
¿Eh? ¿Puede manejar NA? ¿instrumentos de cuerda? categóricos? ¿fechas? Estas son las cosas que hacen que la lectura de pandas csv sea lenta y flácida. Los NA y los objetos como cuerdas (incluso los cortos) son un asesino. Por cierto, el .ipynb al que hace referencia su blog está inactivo.
smci
1
@smci estaba leyendo tu nota. pero te sugiero que eches un vistazo a los documentos. Necesitaría leerlos yo mismo.
PlagTag
2
Ok, entonces no puede manejar NAs, cadenas o fechas. Dudo que pueda manejar flotadores tampoco.
smci
1
Supongo que podría preprocesar con pandas usando el chunksmétodo mencionado, luego usar bcolz si necesita todos los datos en la memoria para hacer un análisis. Solo un pensamiento.
JakeCowton
6

Puede leer los datos como fragmentos y guardar cada fragmento como pepinillo.

import pandas as pd 
import pickle

in_path = "" #Path where the large file is
out_path = "" #Path to save the pickle files to
chunk_size = 400000 #size of chunks relies on your available memory
separator = "~"

reader = pd.read_csv(in_path,sep=separator,chunksize=chunk_size, 
                    low_memory=False)    


for i, chunk in enumerate(reader):
    out_file = out_path + "/data_{}.pkl".format(i+1)
    with open(out_file, "wb") as f:
        pickle.dump(chunk,f,pickle.HIGHEST_PROTOCOL)

En el siguiente paso, lea los encurtidos y agregue cada encurtido a su marco de datos deseado.

import glob
pickle_path = "" #Same Path as out_path i.e. where the pickle files are

data_p_files=[]
for name in glob.glob(pickle_path + "/data_*.pkl"):
   data_p_files.append(name)


df = pd.DataFrame([])
for i in range(len(data_p_files)):
    df = df.append(pd.read_pickle(data_p_files[i]),ignore_index=True)
Lukas Humpe
fuente
3
Si su dfajuste final se ajusta completamente en la memoria (como está implícito) y contiene la misma cantidad de datos que su entrada, ¿seguramente no necesitará trocear en absoluto?
jpp
Tendría que fragmentar en este caso si, por ejemplo, su archivo es muy ancho (como más de 100 columnas con muchas columnas de cadena). Esto aumenta la memoria necesaria para mantener el df en la memoria. Incluso un archivo de 4GB como este podría terminar usando entre 20 y 30 GB de RAM en una caja con 64 GB de RAM.
cdabel
4

La función read_csv y read_table es casi la misma. Pero debe asignar el delimitador "," cuando utiliza la función read_table en su programa.

def get_from_action_data(fname, chunk_size=100000):
    reader = pd.read_csv(fname, header=0, iterator=True)
    chunks = []
    loop = True
    while loop:
        try:
            chunk = reader.get_chunk(chunk_size)[["user_id", "type"]]
            chunks.append(chunk)
        except StopIteration:
            loop = False
            print("Iteration is stopped")

    df_ac = pd.concat(chunks, ignore_index=True)
Tyrion W
fuente
Sería útil si se indica cuál es su pregunta en esta publicación. Como "¿Cuál es la diferencia entre read_csv y read_table?" o "¿Por qué la tabla de lectura necesita un delimitador?"
nate_weldon
1
Depende de cómo se vea tu archivo. Algunos archivos tienen delimitadores comunes como "," o "|" o "\ t" pero es posible que vea otros archivos con delimitadores como 0x01, 0x02 (componiendo este), etc. Así que read_table es más adecuado para delimitadores poco comunes, pero read_csv puede hacer el mismo trabajo igual de bien.
Naufal
3

Solución 1:

Usar pandas con datos grandes

Solución 2:

TextFileReader = pd.read_csv(path, chunksize=1000)  # the number of rows per chunk

dfList = []
for df in TextFileReader:
    dfList.append(df)

df = pd.concat(dfList,sort=False)
oveja negra
fuente
3
Una vez más estamos cargando el archivo de 6 GB totalmente a la memoria, ¿Hay alguna opción, podemos procesar el trozo actual y después lee el siguiente fragmento
debaonline4u
66
simplemente no lo hagas dfList.append, solo procesa cada fragmento ( df) por separado
gokul_uf
3

Aquí sigue un ejemplo:

chunkTemp = []
queryTemp = []
query = pd.DataFrame()

for chunk in pd.read_csv(file, header=0, chunksize=<your_chunksize>, iterator=True, low_memory=False):

    #REPLACING BLANK SPACES AT COLUMNS' NAMES FOR SQL OPTIMIZATION
    chunk = chunk.rename(columns = {c: c.replace(' ', '') for c in chunk.columns})

    #YOU CAN EITHER: 
    #1)BUFFER THE CHUNKS IN ORDER TO LOAD YOUR WHOLE DATASET 
    chunkTemp.append(chunk)

    #2)DO YOUR PROCESSING OVER A CHUNK AND STORE THE RESULT OF IT
    query = chunk[chunk[<column_name>].str.startswith(<some_pattern>)]   
    #BUFFERING PROCESSED DATA
    queryTemp.append(query)

#!  NEVER DO pd.concat OR pd.DataFrame() INSIDE A LOOP
print("Database: CONCATENATING CHUNKS INTO A SINGLE DATAFRAME")
chunk = pd.concat(chunkTemp)
print("Database: LOADED")

#CONCATENATING PROCESSED DATA
query = pd.concat(queryTemp)
print(query)
jonathask
fuente
2

Puede probar sframe, que tiene la misma sintaxis que los pandas pero le permite manipular archivos que son más grandes que su RAM.

nunodsousa
fuente
Enlace a documentos de SFrame: turi.com/products/create/docs/generated/graphlab.SFrame.html
ankostis el
"Los datos en SFrame se almacenan en columnas en el lado del servidor GraphLab" ¿es un servicio o un paquete?
Danny Wang el
2

Si usa pandas, lea un archivo grande en trozos y luego ceda fila por fila, esto es lo que he hecho

import pandas as pd

def chunck_generator(filename, header=False,chunk_size = 10 ** 5):
   for chunk in pd.read_csv(filename,delimiter=',', iterator=True, chunksize=chunk_size, parse_dates=[1] ): 
        yield (chunk)

def _generator( filename, header=False,chunk_size = 10 ** 5):
    chunk = chunck_generator(filename, header=False,chunk_size = 10 ** 5)
    for row in chunk:
        yield row

if __name__ == "__main__":
filename = r'file.csv'
        generator = generator(filename=filename)
        while True:
           print(next(generator))
paulg
fuente
1

Quiero dar una respuesta más completa basada en la mayoría de las posibles soluciones que ya se proporcionan. También quiero señalar una ayuda potencial más que puede ayudar al proceso de lectura.

Opción 1: dtypes

"dtypes" es un parámetro bastante poderoso que puede usar para reducir la presión de memoria de los readmétodos. Mira esto y esto respuesta. Los pandas, por defecto, intentan inferir tipos de datos.

En referencia a las estructuras de datos, cada dato almacenado tiene lugar una asignación de memoria. En un nivel básico, consulte los valores a continuación (la tabla siguiente ilustra los valores para el lenguaje de programación C):

The maximum value of UNSIGNED CHAR = 255                                    
The minimum value of SHORT INT = -32768                                     
The maximum value of SHORT INT = 32767                                      
The minimum value of INT = -2147483648                                      
The maximum value of INT = 2147483647                                       
The minimum value of CHAR = -128                                            
The maximum value of CHAR = 127                                             
The minimum value of LONG = -9223372036854775808                            
The maximum value of LONG = 9223372036854775807

Consulte esta página para ver la coincidencia entre los tipos NumPy y C.

Digamos que tiene una serie de enteros de dígitos . Puede asignar, teórica y prácticamente, una matriz de tipo entero de 16 bits, pero luego asignaría más memoria de la que realmente necesita para almacenar esa matriz. Para evitar esto, puede configurar la dtypeopción read_csv. No desea almacenar los elementos de la matriz como un entero largo donde realmente puede ajustarlos con un entero de 8 bits (np.int8 onp.uint8 ).

Observe el siguiente mapa dtype.

Fuente: https://pbpython.com/pandas_dtypes.html

Puede pasar el dtypeparámetro como un parámetro en los métodos pandas como dictar en read{column: type}.

import numpy as np
import pandas as pd

df_dtype = {
        "column_1": int,
        "column_2": str,
        "column_3": np.int16,
        "column_4": np.uint8,
        ...
        "column_n": np.float32
}

df = pd.read_csv('path/to/file', dtype=df_dtype)

Opción 2: Leer por fragmentos

La lectura de los datos en fragmentos le permite acceder a una parte de los datos en memoria, y puede aplicar el preprocesamiento en sus datos y preservar los datos procesados ​​en lugar de los datos sin procesar. Sería mucho mejor si combina esta opción con la primera, dtypes .

Quiero señalar las secciones de libros de cocina de pandas para ese proceso, donde pueden encontrarlo aquí . Tenga en cuenta esas dos secciones allí;

Opción 3: Dask

Dask es un marco que se define en el sitio web de Dask como:

Dask proporciona paralelismo avanzado para análisis, permitiendo un rendimiento a escala para las herramientas que amas

Nació para cubrir las partes necesarias donde los pandas no pueden alcanzar. Dask es un marco poderoso que le permite mucho más acceso a los datos al procesarlos de manera distribuida.

Puede usar dask para preprocesar sus datos en conjunto, Dask se encarga de la parte de fragmentación, por lo que, a diferencia de los pandas, puede definir sus pasos de procesamiento y dejar que Dask haga el trabajo. Dask no aplica los cálculos antes de que sea explícitamente empujado por computey / o persist(vea la respuesta aquí para ver la diferencia).

Otras ayudas (ideas)

  • Flujo ETL diseñado para los datos. Mantener solo lo que se necesita de los datos sin procesar.
    • Primero, aplique ETL a datos completos con marcos como Dask o PySpark, y exporte los datos procesados.
    • Luego vea si los datos procesados ​​pueden caber en la memoria como un todo.
  • Considera aumentar tu RAM.
  • Considere trabajar con esos datos en una plataforma en la nube.
nulo
fuente
0

Además de las respuestas anteriores, para aquellos que desean procesar CSV y luego exportar a csv, parquet o SQL, d6tstack es otra buena opción. Puede cargar varios archivos y se ocupa de los cambios en el esquema de datos (columnas agregadas / eliminadas). Descompuesto del soporte principal ya está integrado.

def apply(dfg):
    # do stuff
    return dfg

c = d6tstack.combine_csv.CombinerCSV([bigfile.csv], apply_after_read=apply, sep=',', chunksize=1e6)

# or
c = d6tstack.combine_csv.CombinerCSV(glob.glob('*.csv'), apply_after_read=apply, chunksize=1e6)

# output to various formats, automatically chunked to reduce memory consumption
c.to_csv_combine(filename='out.csv')
c.to_parquet_combine(filename='out.pq')
c.to_psql_combine('postgresql+psycopg2://usr:pwd@localhost/db', 'tablename') # fast for postgres
c.to_mysql_combine('mysql+mysqlconnector://usr:pwd@localhost/db', 'tablename') # fast for mysql
c.to_sql_combine('postgresql+psycopg2://usr:pwd@localhost/db', 'tablename') # slow but flexible
hombre de la ciudad
fuente
0

En caso de que alguien todavía esté buscando algo como esto, descubrí que esta nueva biblioteca llamada modin puede ayudar. Utiliza computación distribuida que puede ayudar con la lectura. Aquí hay un buen artículo que compara su funcionalidad con los pandas. Básicamente usa las mismas funciones que los pandas.

import modin.pandas as pd
pd.read_csv(CSV_FILE_NAME)
Jaskaran
fuente
¿Puedes comentar cómo este nuevo módulo se modincompara con el bien establecido dask.dataframe? Por ejemplo, vea pasar de pandas a dask para utilizar todos los núcleos de CPU locales .
jpp
0

Antes de usar la opción chunksize, si desea estar seguro sobre la función de proceso que desea escribir dentro del bucle for de chunking como lo menciona @unutbu, simplemente puede usar la opción nrows.

small_df = pd.read_csv(filename, nrows=100)

Una vez que esté seguro de que el bloque de proceso está listo, puede ponerlo en el bucle de fragmentación para todo el marco de datos.

sam
fuente