基于Pandas 0.2的90k记录流动性分析性能优化求助
问题描述
我有一个包含90k条记录的大型CSV表格,使用Python + Pandas v0.2进行数据分析(受程序限制只能使用该版本)。每条记录包含日期、账户编号及两列浮点数值。
在流动性分析中,需要计算每个账户在每年每一天的指定累计值:以7月5日、账户3000为例,需计算所有账户编号=3000且日期<=7月5日的记录中两列的累计和,同时还要区分非到期部分。
当前采用双重循环(遍历全年365天+遍历10000-90000的账户)实现,每次循环通过df.loc查询计算,总计算量达3200万次,耗时约18小时,速度过慢。曾考虑增量累加,但因后续逻辑需依赖历史记录判断,暂未发现明显优化空间。
原代码如下:
analyseRange = pd.date_range(settings.analyseStartDatum, settings.analyseEndDatum) for datum in analyseRange: globals()[str(datum) + "-vbTable"] = pd.DataFrame(columns=["Konto","Kontobezeichnung", "Datum", "Saldo","nicht Fällig", "Fällige VB"]) for i in range(settings.accountNummernStart ,settings.accountNummernEnde): startTimer = time.perf_counter() buchungen = df.loc[(df[settings.kontoNrCol] == i) & (df[settings.belegDatumCol] <= datum),[settings.kontoNrCol, settings.belegDatumCol, settings.sollCol, settings.habenCol, settings.faelligkeitsCol, "FaelligBisClean"]] verbindlichkeiten = buchungen[settings.habenCol].sum() - buchungen[settings.sollCol].sum() nichtFaelligeBuchungen = df.loc[(df[settings.kontoNrCol] == konto) & (df[settings.belegDatumCol] <= datum) & (df["FaelligBisClean"] > datum),[settings.sollCol, settings.habenCol]] nichtFaelligeVerbindlichkeit = nichtFaelligeBuchungen[settings.habenCol].sum() if(verbindlichkeiten != 0): globals()[str(datum) + "-vbTable"].append([i,"tbd.",datum,verbindlichkeiten,nichtFaelligeVerbindlichkeiten,(verbindlichkeiten - nichtFaelligeVerbindlichkeiten)]) print("append: " + str(i) + "-tbd.-" + " - "+ str(datum) + " - "+ str(verbindlichkeiten) + " - "+ str(nichtFaelligeVerbindlichkeiten) + " - "+ str(verbindlichkeiten - nichtFaelligeVerbindlichkeiten)+ ",time: " + str(time.perf_counter() - startTimer), flush=True) else: print("Konto is even: " + str(i) + ",time: " + str(time.perf_counter() - startTimer), flush=True) return ""
性能优化方案
针对Pandas v0.2版本,核心优化思路是避免循环内重复查询,利用分组预计算+批量聚合替代逐次计算,以下是具体方案:
1. 按账户分组预计算累计值
先按账户编号分组并排序,预计算每个账户的累计Saldo(haben - soll的累计和),同时标记每条记录的到期状态,避免后续重复计算:
# 按账户+交易日期排序,确保累计顺序正确 df_sorted = df.sort_values([settings.kontoNrCol, settings.belegDatumCol]) # 按账户分组,计算累计Saldo df_sorted['Saldo_Kumulativ'] = df_sorted.groupby(settings.kontoNrCol)[settings.habenCol].cumsum() - df_sorted.groupby(settings.kontoNrCol)[settings.sollCol].cumsum() # 标记每条记录是否属于"非到期"(后续筛选用) df_sorted['Ist_Nicht_Faellig'] = df_sorted['FaelligBisClean'] > df_sorted[settings.belegDatumCol]
2. 生成账户-日期全量网格,替代双重循环
先生成所有需要分析的账户与日期的笛卡尔积,避免循环内反复遍历:
# 生成账户列表 konten = pd.DataFrame({'Konto': range(settings.accountNummernStart, settings.accountNummernEnde)}) # 生成分析日期列表 daten = pd.DataFrame({'Datum': analyseRange}) # Pandas v0.2无merge cross,用列表推导生成笛卡尔积 konto_datum_grid = pd.DataFrame( [(k, d) for k in konten['Konto'] for d in daten['Datum']], columns=['Konto', 'Datum'] )
3. 批量关联+聚合计算最终结果
将预计算数据与账户-日期网格关联,按账户和日期批量聚合,直接得到所有账户每日的累计值:
# 关联网格与预计算数据(保留所有账户-日期组合) merged = konto_datum_grid.merge( df_sorted, left_on=['Konto', 'Datum'], right_on=[settings.kontoNrCol, settings.belegDatumCol], how='left' ) # 按账户+日期分组,计算累计Saldo和非到期部分 result = merged.groupby(['Konto', 'Datum']).agg( Saldo=('Saldo_Kumulativ', 'max'), # 取该日期及之前的累计最大值 Nicht_Faellig=(settings.habenCol, lambda x: x[merged['Ist_Nicht_Faellig'] & (merged['FaelligBisClean'] > merged['Datum'])].sum()) ).reset_index() # 补充剩余字段 result['Fällige VB'] = result['Saldo'] - result['Nicht_Faellig'] result['Kontobezeichnung'] = 'tbd.' result.rename(columns={'Nicht_Faellig': 'nicht Fällig'}, inplace=True)
4. 其他关键优化点
- 去掉循环内的
print语句:批量打印或写入日志,减少IO开销 - 禁用
globals()存储结果:直接用单个DataFrame存储所有结果,后续按需筛选日期即可 - 避免
df.append():Pandas v0.2中append效率极低,改用批量生成DataFrame的方式 - 提前转换日期类型:确保所有日期列是
datetime类型,避免查询时重复类型转换
内容的提问来源于stack exchange,提问作者Introser
相关产品推荐
相关产品推荐

