基于Pandas按组实现行与前一行累计差值计算的代码优化问询
优化Pandas分组累计差值计算代码的问询
需求说明
目标是按组实现第N行与第N-1行的差值计算。给定如下DataFrame:
| years | nchar | nval | |
|---|---|---|---|
| 0 | 2019 | a | 1 |
| 1 | 2019 | b | 1 |
| 2 | 2019 | c | 1 |
| 3 | 2020 | a | 1 |
| 4 | 2020 | s | 4 |
- 按年份分组,以2019年组(记为df_2019)为例:
- 设定初始常量值10;
- 新增列'B',组内第0行的'B'值计算规则为:
df_2019.loc[df_2019.index[0], 'B'] = 10 - df_2019['nval'].values[0]; - 组内其余行(索引N)的'B'值计算规则为:
df_2019.loc[df_2019.index[N], 'B'] = df_2019['B'].values[N-1] - df_2019['nval'].values[N]; - 计算后2019年组的结果如下:
| years | nchar | nval | C | D | B | |
|---|---|---|---|---|---|---|
| 1 | 2019 | a | 1 | 9 | ||
| 2 | 2019 | b | 1 | 8 | ||
| 3 | 2019 | c | 1 | 7 |
- 2020年组及后续组采用相同计算逻辑,区别在于初始常量值为上一组'B'列最后一行的值(如2020年组初始值为2019年组'B'列最后一行的7)。
现有实现代码
为支持多分组场景,已编写如下代码:
import pandas as pd year=[2019,2019,2019,2020,2020,2020,2020,2022,2022,2022] nval=[1,1,1,1,4,1,4,5,6,7] nchar=['a','b','c','a','s','c','a','b','c','g'] df=pd.DataFrame(zip(year,nchar,nval),columns=['years','nchar','nval']) print(df) year_ls=[2019,2020,2022] nspacing_total=2 nspacing_between_df=4 all_df=[] default_val=10 for idx,dyear in enumerate(year_ls): df_=df[df['years']==dyear].reset_index(drop=True) t=pd.DataFrame([[''] * 3]*len(df_), columns=["C", "D", "B"]) df_=pd.concat([df_,t],axis=1) Total = df_['nval'].sum() df_=pd.DataFrame([[''] * len(df.columns)]*1, columns=df.columns).append(df_).reset_index(drop=True) if idx ==0: df_.loc[df_.index[0], 'B']=default_val if idx !=0: pre_df=all_df[idx-1] pre_val=pre_df['B'].values[-1] nposi=1 pre_years=pre_df['years'].values[nposi] df_.loc[df_.index[0], 'nchar']=f'From {pre_years}' df_.loc[df_.index[0], 'B']=pre_val for ndexd in range(df_.shape[0]-1): df_.loc[df_.index[ndexd+1], 'B']=df_['B'].values[ndexd]-df_['nval'].values[ndexd+1] df_=df_.append(pd.DataFrame([[''] * len(df.columns)]*nspacing_total, columns=df.columns)).reset_index(drop=True) df_.loc[df_.index[-1], 'nval']=Total df_.loc[df_.index[-1], 'nchar']='Total' df_.loc[df_.index[-1], 'B']=df_['B'].values[0]-df_['nval'].values[-1] all_df.append(df_)
最终输出格式
希望生成可导出至Excel的如下格式表格:
| years | nchar | nval | C | D | B | |
|---|---|---|---|---|---|---|
| 0 | 10 | |||||
| 1 | 2019 | a | 1 | 9 | ||
| 2 | 2019 | b | 1 | 8 | ||
| 3 | 2019 | c | 1 | 7 | ||
| 4 | ||||||
| 5 | Total | 3 | 7 | |||
| 6 | ||||||
| 7 | ||||||
| 8 | ||||||
| 9 | ||||||
| 10 | From 2019 | 7 | ||||
| 11 | 2020 | a | 1 | 6 | ||
| 12 | 2020 | s | 4 | 2 | ||
| 13 | 2020 | c | 1 | 1 | ||
| 14 | 2020 | a | 4 | -3 | ||
| 15 | ||||||
| 16 | Total | 10 | -3 | |||
| 17 | ||||||
| 18 | ||||||
| 19 | ||||||
| 20 | ||||||
| 21 | From 2020 | -3 | ||||
| 22 | 2022 | b | 5 | -8 | ||
| 23 | 2022 | c | 6 | -14 | ||
| 24 | 2022 | g | 7 | -21 | ||
| 25 | ||||||
| 26 | Total | 18 | -21 | |||
| 27 | ||||||
| 28 | ||||||
| 29 | ||||||
| 30 |
生成上述表格的补充代码如下:
# Optional to represent the table above all_ap_df=[] for a_df in all_df: df=a_df.append(pd.DataFrame([[''] * len(df.columns)]*nspacing_between_df, columns=df.columns)).reset_index(drop=True) all_ap_df.append(df) df=pd.concat(all_ap_df,axis=0).reset_index(drop=True) df.loc[df_.index[0], 'D']=df['B'].values[0] df.loc[df_.index[0], 'B']='' df = df.fillna('')
优化诉求
目前的实现代码嵌套了不少循环和手动的索引操作,显得比较繁琐且容易出错。想请教一下,能不能利用Pandas的groupby或者其他内置特性来简化这段代码?比如有没有更高效的方式来处理组间的初始值传递,以及组内的累计差值计算?
优化方案建议
当然可以!我们可以利用Pandas的groupby结合累计计算,再加上模块化的辅助行处理来简化代码,核心思路是:
- 用累计和替代手动循环计算B值:组内的B值本质是初始值减去从第一行到当前行的
nval累计和,用cumsum可以直接完成这个计算,避免逐行循环。 - 简化组间初始值传递:遍历分组时直接记录上一组的最后B值,作为下一组的初始值,无需从列表中回溯取数。
- 模块化构建格式行:把头部行、Total行、空行的创建逻辑拆分,用
pd.concat批量拼接,减少嵌套复杂度。
下面是具体的优化代码:
import pandas as pd # 初始化原始数据 year = [2019,2019,2019,2020,2020,2020,2020,2022,2022,2022] nval = [1,1,1,1,4,1,4,5,6,7] nchar = ['a','b','c','a','s','c','a','b','c','g'] df = pd.DataFrame({'years': year, 'nchar': nchar, 'nval': nval}) default_val = 10 nspacing_total = 2 nspacing_between_df = 4 # 1. 按年份分组,计算组内累计nval和、组总nval grouped = df.groupby('years', sort=False) df['cum_nval'] = grouped['nval'].cumsum() group_totals = grouped['nval'].sum().to_dict() # 2. 计算每个组的B列,传递组间初始值 current_initial = default_val group_results = [] for year_group, group_df in grouped: group_df['B'] = current_initial - group_df['cum_nval'] current_initial = group_df['B'].iloc[-1] group_results.append((year_group, group_df, group_totals[year_group])) # 3. 构建最终格式的DataFrame final_parts = [] prev_year = None prev_final_b = default_val for idx, (year_group, group_df, total_nval) in enumerate(group_results): # 创建组头部行 if idx == 0: header_row = pd.DataFrame({ 'years': [''], 'nchar': [''], 'nval': [''], 'C': [''], 'D': [default_val], 'B': [''] }) else: header_row = pd.DataFrame({ 'years': [''], 'nchar': [f'From {prev_year}'], 'nval': [''], 'C': [''], 'D': [''], 'B': [prev_final_b] }) # 格式化组数据,添加空列 group_formatted = group_df[['years', 'nchar', 'nval']].copy() group_formatted[['C', 'D']] = '' group_formatted['B'] = group_df['B'] # 创建Total行 total_row = pd.DataFrame({ 'years': [''], 'nchar': ['Total'], 'nval': [total_nval], 'C': [''], 'D': [''], 'B': [prev_final_b - total_nval] }) # 组内空行 spacing_rows = pd.DataFrame([['']*6]*nspacing_total, columns=['years','nchar','nval','C','D','B']) # 拼接当前组所有部分 group_full = pd.concat([ header_row, group_formatted, spacing_rows.iloc[:1], total_row, spacing_rows.iloc[1:] ], ignore_index=True) # 添加组间空行(最后一组不加) if idx != len(group_results)-1: between_spacing = pd.DataFrame([['']*6]*nspacing_between_df, columns=['years','nchar','nval','C','D','B']) group_full = pd.concat([group_full, between_spacing], ignore_index=True) # 更新上一组的记录 prev_year = year_group prev_final_b = group_df['B'].iloc[-1] final_parts.append(group_full) # 拼接所有部分并填充空值 final_df = pd.concat(final_parts, ignore_index=True).fillna('') print(final_df)
优化点说明
- 去掉手动循环计算B值:用
cumsum实现向量式累计计算,比逐行循环更高效,代码更简洁。 - 简化组间值传递:遍历分组时直接记录上一组的最终B值,避免了从列表中取前一个DataFrame的复杂操作。
- 模块化处理格式行:把头部、Total、空行的创建逻辑拆分,代码结构更清晰,易维护。
- 贴合Pandas最佳实践:全程使用
groupby、concat等原生方法,比手动操作索引更稳定,数据量大时优势更明显。
内容的提问来源于stack exchange,提问作者rpb
相关产品推荐
相关产品推荐

