【问题标题】:MemoryError with python/pandas and large left outer joins带有 python/pandas 和大的左外连接的 MemoryError
【发布时间】:2015-09-17 16:13:19
【问题描述】:

我对 Python 和 Pandas 都很陌生,并试图找出在大约 1100 万行的左侧数据集和大约 160K 行和四列的右侧数据集之间执行庞大的左外连接的最快方法。这应该是多对一的情况,但如果右侧有重复的行,我希望加入不会引发错误。我在具有 8 Gb RAM 的 Windows 7 64 位系统上使用 Canopy Express,我对此非常坚持。

这是迄今为止我整理的代码模型:

import pandas as pd

leftcols = ['a','b','c','d','e','key']
leftdata = pd.read_csv("LEFT.csv", names=leftcols)

rightcols = ['x','y','z','key']
rightdata = pd.read_csv("RIGHT.csv", names=rightcols)

mergedata = pd.merge(leftdata, rightdata, on='key', how='left')
mergedata.to_csv("FINAL.csv")

这适用于小文件,但会在我的系统上产生 MemoryError,文件大小比我实际需要合并的文件大小小两个数量级。

我一直在浏览相关问题(one、two、three),但没有一个答案能真正解决这个基本问题 - 或者如果他们这样做,它的解释还不够好,我无法识别潜在的解决方案。接受的答案无济于事。我已经在 64 位系统上并使用最新的稳定版 Canopy(1.5.5 64 位,使用 Python 2.7.10)。

避免此 MemoryError 问题的最快和/或最 Python 的方法是什么?

【问题讨论】:

  • 最pythonic的方法是获取更多内存。不认真。 Pandas 始终将您的数据保存在内存中。如果您的文件太大,您根本无法使用 pandas。
  • 如果具有 8 GB RAM 的系统不足以合并两个文件,每个文件大小是我需要的文件大小的 1/100,那么多少 RAM 才足够? 1TB?我需要组装多少台超级计算机?必须有一种不同的方式来使用更少的内存。如果不是熊猫,怎么办?
  • AFAIK,所有加入/合并操作都可以通过排序合并完成。所以没有必要把事情记在内存里。
  • 您可能在合并“键”中有重复的值,这会导致输出中此类行的笛卡尔积
  • 当你说“接受的答案没有帮助”时,你的意思是包括pd.concat 答案吗?尝试在调用中将 index_col='key' 设置为 read_csv,然后连接(或加入,如果这抱怨重复索引)?

标签: python python-2.7 join pandas canopy


【解决方案1】:

为什么不直接将右侧文件读入 pandas(甚至读入一个简单的字典),然后使用 csv 模块循环遍历左侧文件以读取、扩展和写入每一行?处理时间是否是一个重要的限制因素(相对于您的开发时间)?

【讨论】:

  • 处理时间并不是特别重要;我会尝试一下。如果我想出一个可行的解决方案,我会接受答案并在下面发布代码。谢谢!
  • 谢谢乔纳森,这种方法奏效了——见下文。我想我会将下面的评论标记为答案,因为它将为有类似问题的浏览用户提供可使用的代码模型。但如果可以的话,我会给你第二次投票。谢谢!
【解决方案2】:

这种方法最终奏效了。这是我的代码模型:

import csv

idata = open("KEY_ABC.csv","rU")
odata = open("KEY_XYZ.csv","rU")

leftdata = csv.reader(idata)
rightdata = csv.reader(odata)

def gen_chunks(reader, chunksize=1000000):
    chunk = []
    for i, line in enumerate(reader):
        if (i % chunksize == 0 and i > 0):
            yield chunk
            del chunk[:]
        chunk.append(line)
    yield chunk

count = 0

d1 = dict([(rows[3],rows[0]) for rows in rightdata])
odata.seek(0)    
d2 = dict([(rows[3],rows[1]) for rows in rightdata])
odata.seek(0)
d3 = dict([(rows[3],rows[2]) for rows in rightdata])

for chunk in gen_chunks(leftdata):
    res = [[k[0], k[1], k[2], k[3], k[4], k[5], k[6], 
                d1.get(k[6], "NaN")] for k in chunk]
    res1 = [[k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7], 
                d2.get(k[6], "NaN")] for k in res]
    res2 = [[k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7], k[8],
                d3.get(k[6], "NaN")] for k in res1]
    namestart = "FINAL_"
    nameend = ".csv"
    count = count+1
    filename = namestart + str(count) + nameend
    with open(filename, "wb") as csvfile:
        output = csv.writer(csvfile)
        output.writerows(res2)

通过将左侧数据集拆分为块,将右侧数据集转换为每个非键列一个字典,并通过向左侧数据集添加列(使用字典和键匹配填充它们),脚本设法做到了整个左加入大约四分钟,没有记忆问题。

也感谢用户miku 在this post 的评论中提供了块生成器代码。

也就是说:我非常怀疑这是最有效的方法。如果有人有改进这种方法的建议,请开火。

【讨论】:

    【解决方案3】:

    正如另一个问题 "Large data" work flows using pandas 中所建议的,dask (http://dask.pydata.org) 可能是一个简单的选择。

    简单示例

    import dask.dataframe as dd
    df1 = dd.read_csv('df1.csv')
    df2 = dd.read_csv('df2.csv')
    df_merge = dd.merge(df1, df2, how='left')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-24
      • 2018-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-12
      相关资源
      最近更新 更多