group-by / apply with Pandas and Multiprocessing

时间:2018-02-19 23:17:22

标签: python pandas python-multiprocessing

我正在尝试进行groupby并使用多处理在pandas数据帧上应用操作(希望加快我的代码)。例如,如果我有如下数据框:

            A  B  C
cluster_id         
1           1  2  3
1           1  2  3
2           4  5  6
2           7  8  9

我想在列上应用一个函数,并通过cluster_id对它们进行分组。在一个简单的情况下,函数只是总和

def my_func(x):
    return sum(x)

然后操作应该产生:

            A   B   C
cluster_id         
1           2   4   6
2           11  13  15

在SO上有一些类似的帖子,我确实设法接近某个地方,但还没有真正解决它。我的代码失败了,我不知道如何解决它。这就是我提出的问题

import multiprocessing as mp
import pandas as pd
import numpy as np


def _apply_df(args):
    df, func = args
    return df.groupby(level=0).apply(func)


def mp_apply(df, func):
    workers = 4
    pool = mp.Pool(processes=workers)
    split_dfs = np.array_split(df, workers, axis=1)
    result = pool.map(_apply_df, [(d, func) for d in split_dfs])
    pool.close()
    result = sorted(result, key=lambda x: x[0])
    return pd.concat([i[1] for i in result])


def my_func(x):
    return sum(x)


if __name__ == '__main__':
    df = pd.DataFrame([[1, 2, 3, 1], [1, 2, 3, 1], [4, 5, 6, 2], [7, 8, 9, 2]], columns=['A', 'B', 'C', 'cluster_id'])
    df = df.set_index('cluster_id')
    out = mp_apply(df, my_func)
    print(out)

我收到错误:

  TypeError: unsupported operand type(s) for +: 'int' and 'str'

看起来它在线上失败了

result = pool.map(_apply_df, [(d, func) for d in split_dfs])

传递给d的参数_apply_df看起来是空的。

任何帮助/想法高度赞赏。如果重要的话,我正在使用Python 3.6。谢谢!

1 个答案:

答案 0 :(得分:2)

您的代码中存在两个主要问题原因

  1. 使用python的内置sum函数。这是一个函数,它接受一个可迭代的数字并返回它们的总和。 例如如果你试图对数据帧df的一部分求和,你将得到相同的错误追溯
  2.   

    和(df.loc [1])

    TypeError                                 Traceback (most recent call last)
        <ipython-input-60-6dea0ab0880f> in <module>()
        ----> 1 sum(df.loc[1])
    TypeError: unsupported operand type(s) for +: 'int' and 'str'
    

    要解决此问题,您需要使用pandas sum功能,如下所示

    df.loc[1].sum()
    
    #output 
    A    2
    B    4
    C    6
    dtype: int64
    

    如您所见,这将产生预期的结果。即对数据切片中的列进行求和

    1. 第二个问题是“减少”阶段。每个进程都会返回一个数据帧,即行

      result = sorted(result,key = lambda x:x [0])

      返回pd.concat([i [1] for i in result])

    2. 第一行将产生错误,因为每当结果都没有一个名为0的列时,第二行会出现类似问题。这可以解决如下

      return pd.concat(result,axis=1)
      

      现在,在使用数据的情况下,代码将毫无问题地运行。

      整体代码:

      import multiprocessing as mp
      import pandas as pd
      import numpy as np
      
      
      def _apply_df(args):
          df, func = args
          return df.groupby(level=0).apply(func)
      
      
      def mp_apply(df, func):
          workers = 4
          pool = mp.Pool(processes=workers)
          split_dfs = np.array_split(df, workers, axis=1)
          result = pool.map(_apply_df, [(d, func) for d in split_dfs])
          pool.close()
          #result = sorted(result, key=lambda x: x[0])
          return pd.concat(result,axis=1)
      
      
      def my_func(x):
          return x.sum()
      
      
      if __name__ == '__main__':
          df = pd.DataFrame([[1, 2, 3, 1], [1, 2, 3, 1], [4, 5, 6, 2], [7, 8, 9, 2]], columns=['A', 'B', 'C', 'cluster_id'])
          df = df.set_index('cluster_id')
          out = mp_apply(df, my_func)
          print(out)
      

      输出:

                   A   B   C
      cluster_id            
      1            2   4   6
      2           11  13  15