Python公共对手统计循环的提速优化方法咨询
函数性能优化方案
原有代码的性能瓶颈
- 循环遍历单个对手时,每次迭代触发多次pandas单元格/单行索引访问,单次索引的 overhead 叠加后,耗时随对手数量增长线性飙升
- 所有求和操作在Python解释器层完成,未利用pandas/numpy的底层向量化计算能力
优化思路
- 一次性批量提取p1、p2对应的整列数据,避免重复的索引查找开销
- 利用MultiIndex的批量筛选逻辑,一次性提取所有公共对手的统计项,通过
unstack转为二维表后做向量化计算,完全消除Python层的显式循环 - 加入除零保护,避免分母为0时触发报错
优化后代码
import numpy as np import pandas as pd def common_opponents(p1, p2): # 计算公共对手列表 common_opps = list(s_opponents[p1].intersection(s_opponents[p2])) length = len(common_opps) # 无公共对手直接返回全0元组,和原逻辑保持一致 if length == 0: return (0, 0, 0, 0, 0, 0, 0, 0) # 一次性提取两名选手对应的整列数据,避免多次重复索引 col_p1 = df_versus[p1] col_p2 = df_versus[p2] # 批量筛选公共对手对应的所有行,unstack为二维表:行=公共对手姓名,列=统计项 mask_p1 = col_p1.index.get_level_values(0).isin(common_opps) stats_p1 = col_p1[mask_p1].unstack(level=1) mask_p2 = col_p2.index.get_level_values(0).isin(common_opps) stats_p2 = col_p2[mask_p2].unstack(level=1) # 向量化计算各指标的累计比例,分母为0时自动返回0 def calc_total_ratios(stats): serve_total = stats["serve_total"] break_total = stats["break_total"] serve_sum = np.divide(stats["serve_won"], serve_total, where=serve_total != 0).sum() ace_sum = np.divide(stats["ace"], serve_total, where=serve_total != 0).sum() df_sum = np.divide(stats["df"], serve_total, where=serve_total != 0).sum() break_sum = np.divide(stats["break_won"], break_total, where=break_total != 0).sum() return serve_sum, ace_sum, df_sum, break_sum serve1, ace1, df1, break1 = calc_total_ratios(stats_p1) serve2, ace2, df2, break2 = calc_total_ratios(stats_p2) # 计算平均值返回,顺序和原函数完全匹配 return ( serve1 / length, serve2 / length, ace1 / length, ace2 / length, df1 / length, df2 / length, break1 / length, break2 / length )
优化效果
- 完全消除Python层的显式循环,所有计算由pandas/numpy底层C实现,500个以上公共对手的场景下,耗时通常可降低10~100倍
- 不需要额外配置Numba,完全兼容现有
df_versus的结构,不需要修改原有数据的构建逻辑 - 自动处理分母为0的边界情况,避免原代码的除零报错
可选进一步优化
- 若函数调用频率极高、相同选手对重复调用较多,可新增缓存逻辑,直接存储已计算过的
(p1,p2)组合的结果,避免重复计算 - 若后续数据量进一步扩大,可提前将
df_versus转成长表(列包含对阵选手、对手、统计项、数值),进一步缩短批量筛选的耗时
内容的提问来源于stack exchange,提问作者Pleban
相关产品推荐
相关产品推荐

