You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Pandas按组实现行与前一行累计差值计算的代码优化问询

优化Pandas分组累计差值计算代码的问询

需求说明

目标是按组实现第N行与第N-1行的差值计算。给定如下DataFrame:

yearsncharnval
02019a1
12019b1
22019c1
32020a1
42020s4
  1. 按年份分组,以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年组的结果如下:
yearsncharnvalCDB
12019a19
22019b18
32019c17
  1. 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的如下格式表格:

yearsncharnvalCDB
010
12019a19
22019b18
32019c17
4
5Total37
6
7
8
9
10From 20197
112020a16
122020s42
132020c11
142020a4-3
15
16Total10-3
17
18
19
20
21From 2020-3
222022b5-8
232022c6-14
242022g7-21
25
26Total18-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结合累计计算,再加上模块化的辅助行处理来简化代码,核心思路是:

  1. 用累计和替代手动循环计算B值:组内的B值本质是初始值减去从第一行到当前行的nval累计和,用cumsum可以直接完成这个计算,避免逐行循环。
  2. 简化组间初始值传递:遍历分组时直接记录上一组的最后B值,作为下一组的初始值,无需从列表中回溯取数。
  3. 模块化构建格式行:把头部行、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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 10:47:29