【问题标题】:How to concatenate multiple pandas.DataFrames without running into MemoryError如何连接多个 pandas.DataFrames 而不会遇到 MemoryError
【发布时间】:2017-11-26 16:15:12
【问题描述】:

我尝试连接三个 DataFrame。

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

这会导致 MemoryError。我该如何解决这个问题?

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

【问题讨论】:

  • 那些是时间序列吗?你想在日期上连接它们吗?
  • 我想连接索引。这不是时间序列。
  • 您是否因为不想写文件而添加了赏金?
  • @IanS 只是想引起对这个问题的更多关注,看看写入 csv 是否是唯一的选择,或者是否有更优雅的解决方案。
  • 好吧,我唯一的另一个想法是像 JohnE 在他的回答中建议的那样做......

标签: python pandas memory memory-management


【解决方案1】:

我建议您通过连接将数据框放入单个 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')

【讨论】:

  • 但是如果我们想沿列连接,即axis=1,那么您的答案将不起作用!
  • No for big files pandas raises MemoryError
  • 目前你应该使用header=False而不是columns=False
  • @AbhilashAwasthi 将文件转储到磁盘后,粘贴命令可能是更好的选择。
  • 最后一个选项不起作用“ AttributeError: 'str' object has no attribute 'read'”
【解决方案2】:

类似于@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)

【讨论】:

    【解决方案3】:

    这里有点猜测,但也许:

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

    【讨论】:

      【解决方案4】:

      另一种选择:

      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)
      

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          Dask 可能是尝试处理大型数据帧的好选择 - 请通过 Dask Docs

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

              【解决方案8】:

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

              拥有如此庞大的数据,性能是个问题。

              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

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

              【讨论】:

              • 我尝试使用解决方案byhand 并收到错误:cannot create an OBJECT array from memory buffer。我不确定它是否可以在 Python3 中修复。
              • 好帖子!您需要做什么才能按列连接这些?
              • 很棒的比较,信息量很大,谢谢!
              • 除了bycsv 之外,这些似乎都不适用于最新版本的库。
              • @Chrisjan 我错过了import os import pickle。我添加它们。
              【解决方案9】:

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

              为 Windows 32 和 64 位操作系统定义了 memory limits。对于 32 位 进程,它只有 2 GB。因此,即使您的 RAM 超过 2GB,并且即使您正在运行 64 位操作系统,但您正在运行 32 位进程,那么该进程将被限制为仅 2 GB 的 RAM - 在我的情况下,该进程是 Python。

              我升级到 64 位 Python,从那时起就没有出现内存错误!

              其他相关问题有:Python 32-bit memory limits on 64bit windowsShould I use Python 32bit or Python 64bitWhy is this numpy array too big to load?

              【讨论】:

                【解决方案10】:

                在写入硬盘时,df.to_csvcolumns=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')
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2019-10-12
                  • 2011-08-22
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多