[英]How to concatenate multiple pandas.DataFrames without running into MemoryError
我有三個要連接的 DataFrame。
concat_df = pd.concat([df1, df2, df3])
這會導致 MemoryError。 我該如何解決這個問題?
請注意,大多數現有的類似問題都是關於讀取大文件時發生的 MemoryErrors。 我沒有那個問題。 我已將我的文件讀入 DataFrames。 我只是無法連接這些數據。
問題是,就像在其他答案中看到的那樣,是記憶問題。 一個解決方案是將數據存儲在磁盤上,然后構建一個唯一的數據幀。
對於如此龐大的數據,性能是一個問題。
csv 解決方案非常慢,因為會在文本模式下進行轉換。 由於使用二進制模式,HDF5 解決方案更短、更優雅、更快。 我在二進制模式中提出了第三種方式,使用pickle ,它似乎更快,但更具技術性並且需要更多空間。 第四個,手動。
這里的代碼:
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
更好的解決方案:
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
對於同類數據幀,我們可以做得更好:
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
並對(小,32 Mb)數據進行一些測試以比較性能。 對於 4 Gb,您必須乘以大約 128。
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
支票:
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
當然,所有這些都必須改進和調整以適應您的問題。
例如,df3 可以分成大小為 'total_memory_size - df_total_size' 的塊,以便能夠運行bypickle
。
如果你願意的話,如果你提供更多關於你的數據結構和大小的信息,我可以編輯它。 漂亮的問題!
我建議您通過串聯將數據幀放入單個 csv 文件中。 然后讀取您的csv文件。
執行:
# 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')
如果此解決方案的性能不夠,則連接比通常更大的文件。 做:
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
然后運行 bash 命令:
cat file1.csv >> file.csv
cat file2.csv >> file.csv
cat file3.csv >> file.csv
或者在 python 中連接 csv 文件:
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')
閱讀后:
df = pd.read_csv('file.csv')
有點猜測這里,但也許:
df1 = pd.concat([df1,df2])
del df2
df1 = pd.concat([df1,df3])
del df3
顯然,您可以循環執行更多操作,但關鍵是您要隨時刪除 df2、df3 等。 當您在問題中這樣做時,您永遠不會清除舊的數據幀,因此您使用的內存大約是您需要的兩倍。
更一般地說,如果您正在閱讀和連接,我會這樣做(如果您有 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] )
換句話說,當您讀取文件時,您只會將小數據幀暫時保留在內存中,直到將它們連接成組合的 df concat_df。 正如您目前所做的那樣,您將保留所有較小的數據幀,即使在連接它們之后也是如此。
類似@glegoux暗示什么,也pd.DataFrame.to_csv
可以追加方式寫的,所以你可以喜歡做一些事情:
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)
Dask 可能是嘗試處理大型數據幀的不錯選擇 - 瀏覽Dask Docs
您可以將您的單個數據幀存儲在 HDF Store 中,然后像調用一個大數據幀一樣調用該存儲。
# 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)
我感謝社區的回答。 但是,就我而言,我發現問題實際上是由於我使用的是 32 位 Python。
為 Windows 32 和 64 位操作系統定義了內存限制。 對於 32 位進程,它只有 2 GB。 因此,即使您的 RAM 超過 2GB,即使您運行的是 64 位操作系統,但您運行的是 32 位進程,那么該進程也將僅限於 2 GB 的 RAM - 在我的情況下,該進程是 Python。
我升級到 64 位 Python,從那時起就沒有出現內存錯誤!
其他相關問題是: Python 32-bit memory limits on 64bit windows , 我應該使用 Python 32bit 還是 Python 64bit , 為什么這個 numpy 數組太大而無法加載?
另一種選擇:
1) 將df1
寫入 .csv 文件: df1.to_csv('Big file.csv')
2)打開 .csv 文件,然后附加df2
:
with open('Big File.csv','a') as f:
df2.to_csv(f, header=False)
3) 用df3
重復步驟 2
with open('Big File.csv','a') as f:
df3.to_csv(f, header=False)
我在嘗試將大量 DataFrame 連接到“不斷增長”的 DataFrame 時遇到了類似的性能問題。
我的解決方法是將所有子數據幀附加到一個列表中,然后在子數據幀的處理完成后連接數據幀列表。 這將使運行時間幾乎減少一半。
寫入硬盤時, df.to_csv
會為columns=False
引發錯誤。
以下解決方案工作正常:
# write df1 to hard disk as file.csv
train1.to_csv('file.csv', index=False)
# append df2 to file.csv
train2.to_csv('file.csv', mode='a', header=False, index=False)
# read the appended csv as df
train = pd.read_csv('file.csv')
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.