【问题标题】:Performance optimization and parallelization in PythonPython 中的性能优化和并行化
【发布时间】:2023-01-10 08:34:49
【问题描述】:

我编写了两个嵌套的函数,它们通过过滤另一个数据帧 (df2) 并将其转换为具有某种逻辑的列表来扩展一个数据帧 (df1)。这个例子当然只是一个很小的例子。 df1 和 df2 实际上要大得多。由于这个过程需要很多行的大量时间,我想在性能方面优化脚本。这样功能本身就可以尽可能快地工作,然后可以并行化。我已经使用 Swifter 运行了并行化。但是,这不再以某种方式起作用。我想 Swifter 不是这个的最佳模块?

这是数据框:

df1 = pd.DataFrame({'name':['10004', '20005', '10003', 'X2'],
                    'group':['1', '2', '3', 'X2'],
                    'code':['H', 'H', 'H', 'R'],
                    'start':[2, 3, 5, 2],
                    'end':[5, 8, 8, 5] })

df2 = pd.DataFrame({'name': 5*['10004'] + 10*['20005'] + 8*['10003'] + 6*['X2'],
                    'group':5*['1'] +     10*['2'] +     8*['3'] +     6*['X2'],
                    'code': 5*['H'] +     10*['H'] +     8*['H'] +     6*['R'],
                    'ID':list(range(1,6)) + 
                         list(range(1,11)) + 
                         list(range(1,9)) + 
                         list(range(1,7)),
                    'ConcFZ':['1', '1,2' , '', '3', '4', 
                          '3,4', '3', '3', '2', '', '2', '', '2,1', '1', '1',
                          '8', '5','6', '', '6', '', '2', '2',
                          '3', '3', '3,2,1', '2', '2', '1'],
                    'NumFZ':[1, 2 , 0, 1, 1, 
                          2, 1, 1, 1, 0, 1, 0, 2, 1, 1,
                          1, 1,1, 0, 1, 0, 1, 1,
                          1, 1, 3, 1, 1, 1]})

和功能:


def Filter_df(row, counter=0):
    df_filtered = df2[df2['name'].isin([row['name']])&
                   df2['group'].isin([row['group']])&
                   df2['code'].isin([row['code']])&
                   ~df2['NumFZ'].isin([0])]\
                    .set_index('ID')\
                    .loc[row['start']:row['end']]\
                    .drop_duplicates(subset='ConcFZ', keep='last')[['ConcFZ', 'NumFZ']] 
    
    if df_filtered.size == 0:
        print('No Data at Index:', row.name)
        return []
    
    else:
        return TzToList(df_filtered)

def TzToList(df_filtered):
    
    TWTZ = df_filtered[df_filtered['NumFZ'] == 1]['ConcFZ'].astype(int).tolist()
            
    if df_filtered.shape[0] == 1 and df_filtered.iat[0,1] > 1: 
        tz=[]
        tz=[
            int(df_filtered['ConcFZ'].str.split(',').iat[0][f]) 
                for f in range(0, len(df_filtered['ConcFZ'].str.split(',').iat[0][:]))
            ]
        tz.sort
        TWTZ.append(tz[0])
                
    elif df_filtered.shape[0] == 1 and df_filtered.iat[0,1] == 1:
        pass
            
    elif df_filtered.iat[0,1] == 0:
        print('LRILred.iat[0,1] == 0?: ', df_filtered.iat[0,1])
            
    else:
        df_filtered_g1 = df_filtered[df_filtered['NumFZ'] >1]
        for i in range(0, df_filtered_g1.shape[0]):
            tz=[]
            tz=[
                int(df_filtered_g1['ConcFZ'].str.split(',').iat[i][f]) 
                for f in range(0, len(df_filtered_g1['ConcFZ'].str.split(',').iat[i][:]))
                ]
            tz.sort
                    
            if len(list(set(tz).intersection(TWTZ))) == 0:          
                    TWTZ.append(tz[0])
                        
            else:
                continue
                
    return TWTZ

如您所见,函数“Filter_df”使用 df1 中的一些行值来过滤 df2 并返回函数 TzToList 的输出。 TzToList 采用过滤后的 df,进一步简化此数据,并将结果转换为列表。此列表将作为列表列添加到 df1。

我这样做是这样的:

df1['Filtered'] = df1.apply(Filter_df, axis=1)

我的python版本是:3.9.13 我的熊猫版本是:1.5.2 我在带有 jupyter-lab 的 jupyter notebook 中使用这个脚本

这是 Filtered_df 函数的第一个版本,它比上面那个慢:

def Filter_df_1(row, counter=0):
    
    df_filtered = df2[(df2['name']==row['name'])&
               (df2['group']==row['group'])&
               (df2['code']==row['code'])&
               (df2['NumFZ']!=0)]\
               .set_index('ID')\
               .loc[row['start']:row['end']]\
               .drop_duplicates(subset='ConcFZ', keep='last')[['ConcFZ', 'NumFZ']]
    
    if df_filtered.size==0:
        print('No Data at Index:', row.name)
        return []
    else:
        return TzToList(df_filtered)

并行化在 win10 上与 Filter_df_1 和 swifter 以及在我的 Linux 系统上使用 pandarallel 一起工作。不知何故,它不再与 swifter 一起工作。

import swifter

df1['Filtered'] = df1.swifter.apply(Filter_df, axis=1)

无论如何,我需要在具有 32 核 CPU 和 64 线程的 Win10 上运行该程序。 最好使用的模块是什么?达斯克?以及如何使用它?

【问题讨论】:

    标签: python pandas optimization parallel-processing


    【解决方案1】:

    当前实施的最大问题是它运行在二次执行时间O(n**2))。实际上,对于 df1 的每一行,您都遍历了整个 df2 数据框。二次算法在大型数据集上效率低下。

    让我澄清一下:没有神奇的模块可以使这种二次算法快速运行。您需要首先提高复杂性(准线性算法很好)。当有更好的算法可用时,使用分布式计算或使用多核只会浪费更多宝贵的资源。即使复杂性没有更好,做更少的工作比使用更多的计算资源更好.

    不要为每一行移动df2数据帧的关键是对数据框进行排序(经过多个键因为多列上有一个条件)。然后你可以执行一个二分查找在生成的数据帧上。另一种策略是进行 group-by 以便预拆分 df2 并快速返回与所选行匹配的部分。每个数据框组都可以放入一个字典中,以便快速获取它(有关 1 列的示例,请参阅this 帖子)。因为条件 df2['NumFZ']!=0 与目标行无关,所以您可以在进行分组/排序之前对整个 df2 数据帧进行一次预过滤。这种方法将每行的预过滤复杂度从O(len(df))降低到O(1)。预计算取O(len(df))

    在最坏的情况下,下一次过去可能会导致二次执行,但只要范围保持较小,情况就不太可能发生。 drop_duplicates 可以通过以下方式优化预计算哈希对于每个列表,因此您只需要在它们的哈希值相等时比较列表(非常罕见)。使用字典可以在 O(n) 时间内为 n 项目快速删除重复项。排序是通常在O(n log n) 中运行的替代选项(在这种情况下实际上应该更慢)。在这种情况下,当与 JIT 编译器结合使用时,布隆过滤器的效率会更高(请参阅this 相关帖子)。在实践中,Pandas 对于最后一个操作应该具有相对较好的复杂性,但是创建熊猫开销有很大的开销所以最好避免这种情况(通常首先将数据转换为 Numpy)。

    我认为瓶颈主要是 Filter_df 函数,因为第二个函数由于过滤的缘故应该在小得多的数据上运行。也就是说,几乎没有什么优化需要考虑:

    Pandas 操作非常昂贵,尤其是在整个数据帧上,因此通常最好将本机列转换为 Numpy 并在行之前过滤列。例如,在我的机器上,df_filtered['ConcFZ'][df_filtered['NumFZ'].to_numpy() == 1].astype(int).tolist()df_filtered[df_filtered['NumFZ'] == 1]['ConcFZ'].astype(int).tolist() 快 4 倍。

    CPython 解释器不优化复制的表达式.因此,当一个表达式被复制 N 次时,它会被重新计算 N 次。例如,[int(df_filtered['ConcFZ'].str.split(',').iat[0][f]) for f in range(0, len(df_filtered['ConcFZ'].str.split(',').iat[0][:]))]无缘无故地重新计算df_filtered['ConcFZ'].str.split(',').iat[0]N+1次,而df_filtered['ConcFZ'].str.split(',').iat[0]可以在生成器之前预计算一次。请注意[:] 是无用的并执行必要的慢速复制。这也使代码更具可读性和可维护性(参见DRY)。事实上,这条线对我来说似乎很复杂。你可以只写:[int(e) for e in df_filtered['ConcFZ'].str.split(',').iat[0]]。还应记住不要在最后一个循环中重新计算拆分 df_filtered_g1.shape[0] 次。

    可能还有更多的改进要做,但这已经是很多变化,也许足以获得一个快速的程序。

    最后,请注意 tz.sort 什么都不做:您需要添加最后一个 (),以便它调用函数 sort

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-11
      • 1970-01-01
      • 2013-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多