熊猫并行groupBy消耗大量内存

时间:2016-09-02 01:07:59

标签: python pandas group-by

我有一个中等大小的文件(~300MB),其中包含个人列表(~300k)以及他们执行的操作。我正在尝试使用groupByapply的并列版本描述here为每个人应用操作。它看起来像这样

import pandas
import multiprocessing
from joblib import Parallel, delayed

df = pandas.read_csv(src)
patients_table_raw = apply_parallel(df.groupby('ID'), f)

def applyParallel(dfGrouped, func):
    retLst = Parallel(n_jobs=multiprocessing.cpu_count())(delayed(func)(group) for name, group in dfGrouped)
    return pd.concat(retLst)

但不幸的是,这消耗了很多空间。我认为这与简单命令的事实有关:

list_groups = list(df.groupby('ID'))

消耗几GB的内存!如何开始?我最初的想法是在小的'堆栈'中迭代groupBy,而不是消耗太多的内存(但是我没有找到一种方法这样做而不将其转换为列表)。

更详细的背景

我有一个简单的CSV数据集,方式如下:

|-------------------------|
| ID | Timestamp | Action |
|-------------------------|
|1   | 0         | A      |
|1   | 10        | B      |
|1   | 20        | C      |
|2   | 0         | B      |
|2   | 15        | C      |
         ...

我基本上要做的是创建一个不同的表,其中包含个人及其ID的操作序列/时间戳的描述。这将帮助我找回个人

|------------------|
| ID | Description |
|------------------|
|1   | 0A10B20C    |
|2   | 0B15C       |
         ...

为了做到这一点,并遵循Pythonic方式,我的想法基本上是在pandas DataFrame中加载第一个表,groupBy ID,并在分组中应用一个函数,该函数返回我想要的表的一行对于每个组(每个ID)。但是,我的数据集中有很多个人(大约100万),而且groupBy操作非常昂贵(没有明确的垃圾收集,正如我在自己的回答中提到的那样)。此外,并行化groupBy暗示了重要的内存使用,因为显然有些事情会重复。

因此,更详细的问题是:如何使用groupBy(因此比你实现自己的循环更快地进行数据处理)并且不会获得这么大的内存开销?

2 个答案:

答案 0 :(得分:2)

试试这个(没有并行化):

In [87]: df
Out[87]:
   ID  Timestamp Action
0   1          0      A
1   1         10      B
2   1         20      C
3   2          0      B
4   2         15      C

In [88]: df.set_index('ID').astype(str).sum(axis=1).groupby(level=0).sum().to_frame('Description').reset_index()
Out[88]:
   ID Description
0   1    0A10B20C
1   2       0B15C

答案 1 :(得分:0)

我发现了一些评论和解决方案:

  • 我已经尝试dask并没有太大的区别。我想这是因为文件不够大,无法使用辅助内存。

  • 如果在应用于组的函数内执行垃圾收集,则内存性能会显着提高。我设法通过一个简单的gc.collect()来实现这一目标,每次$ 10000美元的互动就会发生这种情况。类似的东西:

    x['ID'].head(1).values[0] % 10000 == 0:
        gc.collect()
    
  • 垃圾收集实际上使我的并行版本运行。但是return pd.concat(retLst)是另一个巨大的瓶颈,消耗了大量的内存!

我的最终解决方案是以外部方式对解决方案进行并列化:

  • 我创建了一个函数,它将执行groupBy并在范围[X,Y]

  • 范围内申请具有ID的个人
  • 我只是创建一个池并并行运行它们。每个进程根据其范围

    保存具有不同名称的文件
    f = functools.partial(make_patient_tables2, src="in", dest="out")
    range_of = [(0, 10000), (10000, 20000), (20000, 30000)]
    with Pool(cpu_count()) as p:
        ret_list = p.map(f, range_of)
    
  • 最后但并非最不重要的是,我连接了所有生成的文件。

请注意,这仍然是内存密集型的,因为我们必须复制表的读取(这在 make_patient_tables2 中完成,但无论如何都会发生,因为多处理不会因此,一个更好的解决方案会包含共享资源,但是垃圾收集器+不使用concat +复制原始数据只需要2-3次就足够了!

当然不漂亮。希望它可以为其他人提供帮助。