Pandas.groupby.apply()中的内存泄漏?

10 投票
2 回答
4936 浏览
提问于 2025-04-18 16:01

我现在在做一个项目,使用Pandas处理大约600mb的csv文件。在分析过程中,我把csv文件读入一个数据框(dataframe),然后根据某一列进行分组,并对分组后的数据框应用一个简单的函数。我发现这个过程中我的系统开始使用交换内存(Swap Memory),所以我做了一个简单的测试:

首先,我在命令行中创建了一个相当大的数据框:

import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randn(3000000, 3),index=range(3000000),columns=['a', 'b', 'c'])

我定义了一个没什么用的函数,叫做do_nothing():

def do_nothing(group):
    return group

然后我运行了以下命令:

df = df.groupby('a').apply(do_nothing)

我的系统有16GB的内存,运行的是Debian(Mint)。在创建数据框后,我大约使用了600MB的内存。当apply方法开始执行时,这个内存使用量开始飙升。它稳步上升到大约7GB(!),然后在命令完成后又降回到5.4GB(当命令行仍然在运行时)。问题是,我的工作需要做的不止是“什么都不做”,所以在执行真正的程序时,我的16GB内存被用满了,开始使用交换内存,这让程序变得无法使用。这是正常现象吗?我不明白为什么Pandas需要7GB的内存来有效地“什么都不做”,即使它需要存储分组后的对象。

有没有人知道是什么原因导致的,或者怎么解决这个问题?

谢谢,

.P

2 个回答

0

我的解决方案:

result_df = None
        for i, (account_id, grp) in enumerate(grouped_df):
            grp.name = account_id
            if i % 500 == 0:
                print(f"\rStep {i}", end="", flush=True)
                gc.collect()

            series = partial_func(grp)
            if (
                series is not None
            ):  
                dataframed = series.to_frame().transpose()

                if result_df is None:
                    result_df = dataframed
                else:
                    result_df.append(dataframed)

            else:
                print("Cleaning dropped row.")

       
        grouped_df = result_df
        del result_df
        gc.collect()
13

使用0.14.1版本,我觉得没有内存泄漏(占用你框架的1/3大小)。

In [79]: df = DataFrame(np.random.randn(100000,3))

In [77]: %memit -r 3 df.groupby(df.index).apply(lambda x: x)
maximum of 3: 1365.652344 MB per loop

In [78]: %memit -r 10 df.groupby(df.index).apply(lambda x: x)
maximum of 10: 1365.683594 MB per loop

对于如何处理这样的问题,有两个一般性的建议:

1) 如果可能的话,使用cython级别的函数,这样会快得多,而且会占用更少的内存。换句话说,通常把一个groupby表达式拆开来用是值得的,尽量避免使用函数(当然,有些事情太复杂了,没办法,但你要的就是把事情拆解开)。比如:

不要这样做:

df.groupby(...).apply(lambda x: x.sum() / x.mean())

这样做要好得多:

g = df.groupby(...)
g.sum() / g.mean()

2) 你可以通过手动进行聚合来轻松“控制”groupby(这样还可以在需要时进行定期输出和垃圾回收)。

results = []
for i, (g, grp) in enumerate(df.groupby(....)):

    if i % 500 == 0:
        print "checkpoint: %s" % i
        gc.collect()


    results.append(func(g,grp))

# final result
pd.concate(results)

撰写回答