python - index - Cómo concatenar múltiples pandas.DataFrames sin ejecutarse en MemoryError
pandas join (9)
Dask podría ser una buena opción para probar el manejo de grandes marcos de datos. Revise Dask Docs.
Tengo tres DataFrames que estoy tratando de concatenar.
concat_df = pd.concat([df1, df2, df3])
Esto resulta en un error de memoria. ¿Cómo puedo resolver esto?
Tenga en cuenta que la mayoría de las preguntas similares existentes están relacionadas con las MemoryErrors que se producen al leer archivos grandes. No tengo ese problema He leído mis archivos en DataFrames. Simplemente no puedo concatenar esos datos.
De manera similar a lo que sugiere @glegoux, también pd.DataFrame.to_csv
puede escribir en modo agregado, por lo que puede hacer algo como:
df1.to_csv(filename)
df2.to_csv(filename, mode=''a'', columns=False)
df3.to_csv(filename, mode=''a'', columns=False)
del df1, df2, df3
df_concat = pd.read_csv(filename)
El problema es, como se ve en las otras respuestas, un problema de memoria. Y una solución es almacenar datos en el disco y luego construir un marco de datos único.
Con datos tan grandes, el rendimiento es un problema.
Las soluciones csv son muy lentas, ya que se produce la conversión en modo de texto. Las soluciones HDF5 son más cortas, más elegantes y más rápidas desde el uso del modo binario. Propongo una tercera vía en modo binario, con pickle , que parece ser incluso más rápido, pero más técnico y que necesita más espacio. Y un cuarto, a mano.
Aquí el código:
import numpy as np
import pandas as pd
# a DataFrame factory:
dfs=[]
for i in range(10):
dfs.append(pd.DataFrame(np.empty((10**5,4)),columns=range(4)))
# a csv solution
def bycsv(dfs):
md,hd=''w'',True
for df in dfs:
df.to_csv(''df_all.csv'',mode=md,header=hd,index=None)
md,hd=''a'',False
#del dfs
df_all=pd.read_csv(''df_all.csv'',index_col=None)
os.remove(''df_all.csv'')
return df_all
Mejores soluciones:
def byHDF(dfs):
store=pd.HDFStore(''df_all.h5'')
for df in dfs:
store.append(''df'',df,data_columns=list(''0123''))
#del dfs
df=store.select(''df'')
store.close()
os.remove(''df_all.h5'')
return df
def bypickle(dfs):
c=[]
with open(''df_all.pkl'',''ab'') as f:
for df in dfs:
pickle.dump(df,f)
c.append(len(df))
#del dfs
with open(''df_all.pkl'',''rb'') as f:
df_all=pickle.load(f)
offset=len(df_all)
df_all=df_all.append(pd.DataFrame(np.empty(sum(c[1:])*4).reshape(-1,4)))
for size in c[1:]:
df=pickle.load(f)
df_all.iloc[offset:offset+size]=df.values
offset+=size
os.remove(''df_all.pkl'')
return df_all
Para los marcos de datos homogéneos, podemos hacerlo aún mejor:
def byhand(dfs):
mtot=0
with open(''df_all.bin'',''wb'') as f:
for df in dfs:
m,n =df.shape
mtot += m
f.write(df.values.tobytes())
typ=df.values.dtype
#del dfs
with open(''df_all.bin'',''rb'') as f:
buffer=f.read()
data=np.frombuffer(buffer,dtype=typ).reshape(mtot,n)
df_all=pd.DataFrame(data=data,columns=list(range(n)))
os.remove(''df_all.bin'')
return df_all
Y algunas pruebas en (pequeños, 32 Mb) datos para comparar el rendimiento. Tienes que multiplicar por aproximadamente 128 para 4 Gb.
In [92]: %time w=bycsv(dfs)
Wall time: 8.06 s
In [93]: %time x=byHDF(dfs)
Wall time: 547 ms
In [94]: %time v=bypickle(dfs)
Wall time: 219 ms
In [95]: %time y=byhand(dfs)
Wall time: 109 ms
Un cheque :
In [195]: (x.values==w.values).all()
Out[195]: True
In [196]: (x.values==v.values).all()
Out[196]: True
In [197]: (x.values==y.values).all()
Out[196]: True
Por supuesto, todo eso debe mejorarse y ajustarse para adaptarse a su problema.
Por ejemplo, df3 se puede dividir en partes del tamaño ''total_memory_size - df_total_size'' para poder ejecutar bypickle
.
Puedo editarlo si me da más información sobre su estructura de datos y tamaño si lo desea. Hermosa pregunta!
Estoy agradecido a la comunidad por sus respuestas. Sin embargo, en mi caso, descubrí que el problema se debía en realidad al hecho de que estaba usando Python de 32 bits.
Hay límites de memoria definidos para el sistema operativo Windows 32 y 64 bit. Para un proceso de 32 bits, es solo 2 GB. Entonces, incluso si su RAM tiene más de 2 GB, e incluso si está ejecutando el sistema operativo de 64 bits, pero está ejecutando un proceso de 32 bits, ese proceso se limitará a solo 2 GB de RAM; en mi caso, ese proceso era Python.
¡Actualicé a Python de 64 bits y no he tenido un error de memoria desde entonces!
Otras preguntas relevantes son: Límites de memoria de Python de 32 bits en ventanas de 64 bits . ¿Debo usar Python de 32 bits o Python de 64 bits? ¿Por qué esta matriz numpy es demasiado grande para cargar?
He tenido problemas de rendimiento similares al tratar de concatenar un gran número de DataFrames a un DataFrame ''en crecimiento''.
Mi solución fue anexar todos los sub marcos de datos a una lista, y luego concatenar la lista de marcos de datos una vez que se haya completado el procesamiento de los subfases de datos. Esto llevará el tiempo de ejecución a casi la mitad.
Le aconsejo que coloque sus marcos de datos en un solo archivo csv por concatenación. A continuación, para leer su archivo csv.
Ejecuta eso
# write df1 content in file.csv
df1.to_csv(''file.csv'', index=False)
# append df2 content to file.csv
df2.to_csv(''file.csv'', mode=''a'', columns=False, index=False)
# append df3 content to file.csv
df3.to_csv(''file.csv'', mode=''a'', columns=False, index=False)
# free memory
del df1, df2, df3
# read all df1, df2, df3 contents
df = pd.read_csv(''file.csv'')
Si esta solución no tiene un rendimiento suficiente, es necesario concatear archivos más grandes de lo habitual. Hacer:
df1.to_csv(''file.csv'', index=False)
df2.to_csv(''file1.csv'', index=False)
df3.to_csv(''file2.csv'', index=False)
del df1, df2, df3
Luego ejecuta el comando bash:
cat file1.csv >> file.csv
cat file2.csv >> file.csv
cat file3.csv >> file.csv
O concat csv en python:
def concat(file1, file2):
with open(file2, ''r'') as filename2:
data = file2.read()
with open(file1, ''a'') as filename1:
file.write(data)
concat(''file.csv'', ''file1.csv'')
concat(''file.csv'', ''file2.csv'')
concat(''file.csv'', ''file3.csv'')
Después de leer:
df = pd.read_csv(''file.csv'')
Otra opción:
1) Escriba df1
en el archivo .csv: df1.to_csv(''Big file.csv'')
2) Abra el archivo .csv, luego agregue df2
:
with open(''Big File.csv'',''a'') as f:
df2.to_csv(f, header=False)
3) Repita el paso 2 con df3
with open(''Big File.csv'',''a'') as f:
df3.to_csv(f, header=False)
Puede almacenar sus marcos de datos individuales en una Store HDF, y luego llamar a la tienda como si fuera un gran marco de datos.
# name of store
fname = ''my_store''
with pd.get_store(fname) as store:
# save individual dfs to store
for df in [df1, df2, df3, df_foo]:
store.append(''df'',df,data_columns=[''FOO'',''BAR'',''ETC'']) # data_columns = identify the column in the dfs you are appending
# access the store as a single df
df = store.select(''df'', where = [''A>2'']) # change where condition as required (see documentation for examples)
# Do other stuff with df #
# close the store when you''re done
os.remove(fname)
Un poco adivinando aquí, pero tal vez:
df1 = pd.concat([df1,df2])
del df2
df1 = pd.concat([df1,df3])
del df3
Obviamente, podrías hacerlo más como un bucle, pero la clave es que deseas eliminar df2, df3, etc. a medida que avanzas. Mientras lo hace en la pregunta, nunca borra los marcos de datos antiguos, por lo que está utilizando aproximadamente el doble de memoria que necesita.
Más en general, si estás leyendo y concatenando, lo haría algo como esto (si tuvieras 3 CSV: foo0, foo1, foo2):
concat_df = pd.DataFrame()
for i in range(3):
temp_df = pd.read_csv(''foo''+str(i)+''.csv'')
concat_df = pd.concat( [concat_df, temp_df] )
En otras palabras, mientras lee en archivos, solo guarda temporalmente los pequeños marcos de datos en la memoria, hasta que los concatene en la df combinada, concat_df. Como lo haces actualmente, mantienes alrededor de todos los marcos de datos más pequeños, incluso después de concatenarlos.