如何按分组替换异常值?优化Pandas收益率修正代码
我明白你现在的问题——p3那两个极端收益率(-90%和900%)拉垮了后续的价格修正效果,而且原来用日期中位数替换异常值的逻辑不太合理,毕竟不同类型(p1/p2/p3)的正常收益率分布本来就不一样。咱们可以把整个流程封装成更清晰、可复用的函数,同时修正异常值替换的逻辑,让结果更贴合实际需求。
优化思路拆解
- 先明确异常值判断规则:按每个
type分组,筛选出收益率绝对值超过阈值(比如0.5)的记录 - 对每个
type,计算正常收益率的统计值(均值/中位数,可灵活配置) - 用对应
type的统计值替换异常收益率 - 基于修正后的收益率重新计算价格,严格保留初始价格的基准
优化后的代码实现
import pandas as pd def clean_and_adjust_perf(df, threshold=0.5, stat='median'): # 1. 计算原始收益率 df['perf'] = df.groupby('type')['price'].pct_change() # 2. 标记异常值:按type分组,收益率绝对值超过阈值则标记为True df['is_outlier'] = df.groupby('type')['perf'].transform( lambda x: x.abs() >= threshold ) # 3. 校验统计类型,计算每个type的正常收益率统计值 if stat not in ['mean', 'median']: raise ValueError("stat参数只能是'mean'或'median'") type_stats = df[~df['is_outlier']].groupby('type')['perf'].agg(stat) # 4. 替换异常值:异常值用对应type的统计值填充,正常值保留原始值 df['perf_clean'] = df.apply( lambda row: type_stats[row['type']] if row['is_outlier'] else row['perf'], axis=1 ) # 5. 计算修正后的价格:初始价格 × 修正后收益率的累积乘积 df['price2'] = df.groupby('type').apply( lambda g: g['price'].iloc[0] * (1 + g['perf_clean']).cumprod() ).reset_index(level=0, drop=True) return df.sort_values(['type', 'date']) # 你的原始数据生成代码 data = {'p1': [100., 101, 102, 100, 100], 'p2': [100., 99., 98., 100., 100], 'p3': [1000., 1000., 100., 1000., 1000]} df = (pd.DataFrame(data, index=pd.bdate_range(start='20100101', periods=5)) .stack() .reset_index() .rename(columns={'level_0': 'date', 'level_1': 'type', 0: 'price'}) .sort_values('date')) # 使用函数处理,这里选择用中位数替换异常值 df_cleaned = clean_and_adjust_perf(df, threshold=0.5, stat='median') print(df_cleaned[['date', 'type', 'price', 'perf', 'perf_clean', 'price2']])
关键改进点
- 逻辑更合理:异常值替换用的是同类型的正常收益率统计值,而非跨类型的日期中位数,符合不同产品收益率的分布特性
- 灵活复用:封装成函数后,你可以随时调整异常阈值(比如改成0.3)、切换均值/中位数,不用重复编写冗余代码
- 可读性提升:每一步都有明确注释,变量名更直观(比如
is_outlier标记异常、perf_clean存储修正后的收益率) - 避免索引错误:用
apply结合分组统计值替换的方式,比原来的链式loc操作更稳定,不容易出现索引不匹配的问题
比如针对p3的异常值,它的正常收益率只有0,所以中位数为0,替换后p3的perf_clean会变成0,修正后的price2会保持初始的1000,不会出现大起大落的情况,完全符合你的预期。
内容的提问来源于stack exchange,提问作者Jacques Tebeka
相关产品推荐
相关产品推荐

