【问题标题】:Pandas Memory Management issue熊猫内存管理问题
【发布时间】:2018-01-02 01:04:13
【问题描述】:

我遇到了 Pandas 占用过多 RAM 的问题。我有一个 5.5gb 的文件,其中有 2 列,我想简单地将所有唯一值保存在第一列中,如下所示:

Main File
    Follower    Friend
0   12          260009730
1   12          17568791
2   12          22512883
3   12          15808761
4   12          10135072
5   12          988
6   12          22424855
7   13          9163182
8   14          22990962
9   15          7681662
10  15          17289517

到

Result File
     User
0    12
1    13
2    14
3    15

由于 RAM 限制,我以 30 个为单位导入主文件,尝试从内存中清除数据帧,并且每次仅附加结果文件。经过两次迭代(共 30 次),结果文件为 13.5mb。但它在第 6 次迭代后一直崩溃,我可以在我的进程管理中看到 python 占用了 4.5gb 的 RAM。我想打电话给垃圾收集器,但显然它不起作用,你们能帮帮我吗?我的代码如下:

i = 0
userRelation = pd.DataFrame(columns=['User'])
Location = 'file.txt'
while i < 30:
    userRelationHelp = pd.DataFrame(columns=['User'])
    print(str(i))
    network = pd.read_csv(Location, sep="\t", header=None, encoding='Latin', low_memory=False, skiprows=(i * math.ceil(284884514/30)), nrows=(((i+1) * math.ceil(284884514/30))), names=['Follower', 'Friend'])

    userRelationHelp['User'] = network['Follower'].unique()
    userRelation = userRelation.append(userRelationHelp)
    lst = [userRelationHelp, network]
    del lst
    gc.collect()
    i += 1

根据我在 i += 1 之前阅读的最后 3 行的内容,应该可以从内存中清除较大的文件。每次迭代后,我可以看到我在循环开始时使用的 RAM 不断增加约 200mb,并且在循环期间,每次运行都会增加更多。

运行上述代码之前的基本 Python RAM 使用情况: 76mb

循环开始时 Python RAM 的近似使用率

0: 300
1: 800
2: 1000
3: 1300

循环结束时的大概 Python RAM 使用量

0: 633
1: 2000
2: 2900
3: 3700

能想象指出我在做什么或假设不正确吗?

【问题讨论】:

  • 查看 read_csv()s chunksize 参数。也许是 dask

标签: python pandas csv memory


【解决方案1】:

您拆分大文件的方式效率极低。当您使用skiprows 参数时,阅读器需要逐行浏览文件,计算行结束字符,直到通过了请求的行数。因此,在您的最后一次迭代中,整个文件在您已经多次处理大部分文件后再次被读取。

顺便请注意,您对nrows 的使用在我看来也是错误的。我希望nrows=math.ceil(284884514/30)。我相信这就是对您的记忆问题的解释。

您需要的是一种迭代方式一次性运行文件,该文件由chunksize 参数提供。 chunksize 的使用在 Pandas IO Tools docs 中有很好的说明。

说明性代码示例:

userRelation = pd.DataFrame(columns=['User'])
Location = 'file.txt'
chunksize = math.ceil(284884514/30)

reader = pd.read_csv(Location, sep="\t", header=None, encoding='Latin', low_memory=False, chunksize=chunksize, names=['Follower', 'Friend'])
for network in reader:
    userRelationHelp = pd.DataFrame(columns=['User'])
    userRelationHelp['User'] = network['Follower'].unique()
    userRelation = userRelation.append(userRelationHelp)
    lst = [userRelationHelp, network]

【讨论】:

  • 非常好的和详细的答案! ++ 我认为我们可以通过跳过不需要的列来挤压更多 ;-)
  • 谢谢!我不知道 nrows 是如何工作的,这把它搞砸了。 Chunksize 在这种情况下要好得多。
  • 您对 nrows 的使用是完全正确的。我是个小丑,出于某种原因,我认为 skiprow:nrow 与 df[skiprow:nrow] 相同,我知道这是完全错误的。
【解决方案2】:

@ypnos gave you a perfect description of how it should be done in Pandaic way.

这是我编写代码的尝试:

生成样本数据并将其写入 CSV:

fn = r'c:/temp/data.csv'

pd.DataFrame(np.random.randint(1001, 9999, (10**5, 2))) \
  .to_csv(fn, sep='\t', index=False, header=None)

仅处理 CSV 中我们需要的那些列:

chunksize=10**2  # you may want to use 10**7 as a chunk size
reader = pd.read_csv(fn, sep='\t', usecols=[0], names=['Follower'], 
                     chunksize=chunksize, squeeze=True)

df = pd.DataFrame(np.unique(np.concatenate([ser.unique() for ser in reader])),
                  columns=['User'])

结果:

In [62]: df
Out[62]:
      User
0     1001
1     1002
2     1003
3     1004
4     1005
5     1006
6     1007
7     1008
8     1009
9     1010
...    ...
8988  9989
8989  9990
8990  9991
8991  9992
8992  9993
8993  9994
8994  9995
8995  9996
8996  9997
8997  9998

[8998 rows x 1 columns]

【讨论】:

  • 漂亮!一种单行解决方案,还展示了如何组合块结果。
  • @ypnos,谢谢!我非常喜欢你的解释(在你的回答中)!
  • 谢谢你!很好的答案,直接可用。希望我以前知道 chunksize,可以更有效地完成很多大文件管理。
  • @user3394131,很高兴我能帮上忙 :)
  • @user3394131,是的,ser 是每个块的 Pandas.Series。参数squeeze=True将只有一列的DataFrame转换成Series
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-23
  • 2011-01-07
  • 2016-10-20
  • 2011-04-11
相关资源
最近更新 更多