如何连接多个pandas.DataFrames而不遇到MemoryError

问题描述 投票:0回答:11

我正在尝试连接三个 DataFrame。

concat_df = pd.concat([df1, df2, df3])

这会导致内存错误。我该如何解决这个问题?

请注意,大多数现有的类似问题都是关于读取大文件时发生的 MemoryErrors。我没有这个问题。我已将我的文件读入 DataFrames 中。我只是无法连接这些数据。

python pandas memory memory-management
11个回答
42
投票

就像在其他答案中看到的那样,问题是记忆问题。解决方案是将数据存储在磁盘上,然后构建唯一的数据框。

面对如此庞大的数据,性能是一个问题。

csv 解决方案非常慢,因为发生文本模式转换。 由于使用二进制模式,HDF5 解决方案更短、更优雅、更快。 我提出了二进制模式的第三种方法,即 pickle,它似乎更快,但更具技术性并且需要更多空间。第四个,用手。

这里是代码:

import numpy as np
import pandas as pd
import os
import pickle

# 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

如果您愿意的话,如果您提供有关数据结构和大小的更多信息,我可以编辑它。漂亮的问题!


22
投票

我建议您通过串联将数据帧放入单个 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')

11
投票

有点猜测,但也许:

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。正如您目前所做的那样,您将保留所有较小的数据帧,即使在连接它们之后也是如此。


8
投票

与@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)

5
投票

Dask 可能是尝试处理大型数据帧的不错选择 - 请浏览Dask 文档


4
投票

我感谢社区的回答。然而,就我而言,我发现问题实际上是由于我使用的是 32 位 Python。

为 Windows 32 和 64 位操作系统定义了内存限制。对于 32 位进程,它只有 2 GB。因此,即使您的 RAM 超过 2GB,即使您运行的是 64 位操作系统,但运行的是 32 位进程,那么该进程将仅限于 2 GB RAM - 在我的例子中是该进程是Python。

我升级到了 64 位 Python,从那以后就再也没有出现过内存错误!

其他相关问题是:

64位Windows上的Python 32位内存限制我应该使用Python 32位还是Python 64位为什么这个numpy数组太大而无法加载?


3
投票
您可以将各个数据帧存储在 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)
    

3
投票
我在尝试将大量 DataFrame 连接到“不断增长的”DataFrame 时遇到了类似的性能问题。

我的解决方法是将所有子数据帧附加到一个列表中,然后在子数据帧的处理完成后连接数据帧列表。这将使运行时间几乎减少一半。


2
投票
另一种选择:

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)
    

0
投票
写入硬盘时,

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

0
投票
使用此函数将每列数据类型转换为可能的最小值,以适合该列中的任何值,而不是在任何地方都只有 float64,而只是足够的最小值,如 int8、int16、float32,具体取决于每列。将对象转换为类别非常重要。在某些情况下,它甚至可以缩小 10 倍。之后是一个循环,将它们全部附加到一个列表中,一个 pd.concat 用于大文件,并删除列表以在最后释放内存。

# Function to verify memory use def memory_usage(df): return round(df.memory_usage(index=True, deep=True).sum() / (1024 ** 2)) # Function to reduce memory usage def reduce_size(df): print(f'Memory usage before transform: {memory_usage(df)} MB') # Convert boolean to int bool_cols = df.select_dtypes(include=[bool]).columns df[bool_cols] = df[bool_cols].astype(np.int8) # Convert non_numericals to category cat_cols = df.select_dtypes(exclude=[np.number, np.datetime64]).columns df[cat_cols] = df[cat_cols].astype('category') # Define data types lists int_types = [np.int8, np.int16, np.int32, np.int64] float_types = [np.float32, np.float64] num_cols = df.select_dtypes(include=[np.number]).columns # Convert numericals to the lowest dtype for num_col in num_cols: col_type = df[num_col].dtype c_min = df[num_col].min() c_max = df[num_col].max() if col_type in int_types: if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: df[num_col] = df[num_col].astype(np.int8) elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max: df[num_col] = df[num_col].astype(np.int16) elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max: df[num_col] = df[num_col].astype(np.int32) elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max: df[num_col] = df[num_col].astype(np.int64) elif col_type in float_types: if c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max: df[num_col] = df[num_col].astype(np.float32) print(f'Memory usage after transform: {memory_usage(df)} MB') return df
    
© www.soinside.com 2019 - 2024. All rights reserved.