如何基于多条件批量计算Pandas DataFrame压力情景的实际数值?
批量转换压力情景数据至基准单位的Pandas实现方案
场景描述
需要基于匹配条件对DataFrame中的压力情景行执行乘法运算,将百分比差值转换为与基准情景一致的绝对数值单位。
DataFrame示例(从xlsx文件导入)
Model Scenario Region Variable Unit Year1 Year2 ... Year50 1 Base 1 GDP M USD 10 15 20 1 Base 2 GDP M USD 30 35 50 1 Base 3 GDP M USD 20 75 80 1 Stress 1 1 GDP % diff 0.48 0.11 0.31 1 Stress 1 2 GDP % diff 0.12 0.33 0.89 1 Stress 1 3 GDP % diff 0.76 0.54 0.08 1 Stress 2 1 GDP % diff 0.37 0.94 0.13 1 Stress 2 2 GDP % diff 0.73 0.76 0.35 1 Stress 2 3 GDP % diff 0.15 0.45 0.37 1 Stress 3 1 GDP % diff 0.49 0.14 0.37 1 Stress 3 2 GDP % diff 0.14 0.73 0.94 1 Stress 3 3 GDP % diff 0.96 0.26 0.85
核心规则
- 压力情景的百分比差值对应相同模型、区域、变量的基准情景数值,计算逻辑为:
基准值 * (1 + 压力差值) - 原始DataFrame包含多组模型、情景、区域和变量,结构统一(所有模型的情景集合一致,所有情景的区域集合一致等)
目标
将所有压力情景行的Year列数值转换为基准情景的单位,同时保留其他列的信息(如Scenario、Unit等)。
计算示例
公式示意
Model Scenario ... Year1 Year2 ... Year50 1 Stress 1 10*(1+0.48) 15*(1+0.11) 20*(1+0.31)
输出结果
Model Scenario ... Year1 Year2 ... Year50 1 Stress 1 14.8 16.65 26.2
已尝试方案及问题
曾用df.loc硬编码匹配条件计算,但存在两个核心问题:
test_df.loc[((test_df['Model'] == '1') & (test_df['Scenario'] == 'Stress1') & (test_df['Region'] == "1") & (test_df['Variable'] == 'GDP'))] = test_df.loc[((test_df['Model'] == '1') & (test_df['Scenario'] == 'Base') & (test_df['Region'] == "1") & (test_df['Variable'] == 'GDP'))] * (1 + test_df.loc[((test_df['Model'] == '1') & (test_df['Scenario'] == 'Stress1') & (test_df['Region'] == "1") & (test_df['Variable'] == 'GDP'))])
- 无法精准控制仅修改Year列,会覆盖Scenario、Unit等非数值列
- 需要为每个模型/情景/区域/变量组合单独编写代码,无法批量处理
最优解决方案
利用Pandas的分组合并功能,一次性匹配所有对应基准值并计算,无需硬编码条件:
步骤1:拆分基准与压力数据
先将基准情景数据单独提取,作为匹配的参照表:
# 提取基准情景数据,保留匹配键和Year列 base_df = test_df[test_df['Scenario'] == 'Base'].copy() # 重命名Year列为前缀,避免合并后列名冲突 year_cols = [col for col in test_df.columns if col.startswith('Year')] base_df = base_df.rename(columns={col: f'Base_{col}' for col in year_cols})
步骤2:合并基准与压力数据
以Model、Region、Variable为匹配键,将基准数据合并到压力情景行:
# 筛选压力情景数据 stress_df = test_df[test_df['Scenario'] != 'Base'].copy() # 合并基准数据 merged_df = stress_df.merge( base_df[['Model', 'Region', 'Variable'] + [f'Base_{col}' for col in year_cols]], on=['Model', 'Region', 'Variable'], how='left' )
步骤3:执行计算并更新Year列
遍历Year列,用基准值乘以(1+压力差值),同时更新Unit列为基准单位:
# 遍历所有Year列计算 for col in year_cols: merged_df[col] = merged_df[f'Base_{col}'] * (1 + merged_df[col]) # 按变量匹配对应基准单位(适配多变量场景) merged_df['Unit'] = merged_df.apply( lambda row: base_df[(base_df['Variable'] == row['Variable']) & (base_df['Model'] == row['Model'])]['Unit'].iloc[0], axis=1 ) # 清理临时列 merged_df = merged_df.drop(columns=[f'Base_{col}' for col in year_cols])
步骤4:合并基准与转换后的压力数据(可选)
如果需要保留原始基准行,将转换后的压力数据与原基准数据合并:
final_df = pd.concat([test_df[test_df['Scenario'] == 'Base'], merged_df], ignore_index=True)
完整代码示例
import pandas as pd # 导入原始数据 test_df = pd.read_excel('your_file.xlsx') # 提取基准情景并处理列名 year_cols = [col for col in test_df.columns if col.startswith('Year')] base_df = test_df[test_df['Scenario'] == 'Base'].copy() base_df = base_df.rename(columns={col: f'Base_{col}' for col in year_cols}) # 处理压力情景数据 stress_df = test_df[test_df['Scenario'] != 'Base'].copy() merged_df = stress_df.merge( base_df[['Model', 'Region', 'Variable'] + [f'Base_{col}' for col in year_cols]], on=['Model', 'Region', 'Variable'], how='left' ) # 计算转换后数值 for col in year_cols: merged_df[col] = merged_df[f'Base_{col}'] * (1 + merged_df[col]) # 更新单位并清理临时列 merged_df['Unit'] = merged_df.apply( lambda row: base_df[(base_df['Variable'] == row['Variable']) & (base_df['Model'] == row['Model'])]['Unit'].iloc[0], axis=1 ) merged_df = merged_df.drop(columns=[f'Base_{col}' for col in year_cols]) # 合并基准与结果 final_df = pd.concat([test_df[test_df['Scenario'] == 'Base'], merged_df], ignore_index=True)
方案优势
- 无需硬编码任何模型/情景/区域/变量的匹配条件,自动适配所有分组
- 精准控制仅修改Year列,保留其他列的原始信息
- 代码简洁高效,适配大规模DataFrame处理
内容的提问来源于stack exchange,提问作者DGMS89
相关产品推荐
相关产品推荐

