Pandas DataFrame中用最近正常点均值替换z-score超阈值离群值
实现逻辑
核心思路是先标记离群值为临时NaN,再通过前后向填充获取最近非离群值计算均值,同时保留原始NaN不被修改。
- 第一步:先记录原始数据中value列为NaN的位置,避免后续填充操作修改原始缺失值
- 第二步:筛选出符合条件的离群值(绝对z-score≥3,且原始value不是NaN),将这些离群值的value临时设为NaN
- 第三步:分别对临时NaN的value列做向前填充(取最近的前序非离群值)和向后填充(取最近的后序非离群值),两者取均值作为替换值
- 第四步:把第一步记录的原始NaN位置的value恢复为NaN,保证原始缺失值不变
代码示例
import pandas as pd import scipy.stats # 需保证df已经按TimeStamp升序排序 # 备份原始NaN的位置 raw_na_mask = df['value'].isna() # 计算z-score,跳过原始NaN避免影响统计结果 df['zscore'] = scipy.stats.zscore(df['value'], nan_policy='omit') # 标记离群值:绝对zscore≥3 且 不属于原始NaN outlier_mask = (df['zscore'].abs() >= 3) & (~raw_na_mask) # 将离群值临时设为NaN方便后续填充 df.loc[outlier_mask, 'value'] = pd.NA # 计算前后最近非离群值的均值 ffill_vals = df['value'].ffill() bfill_vals = df['value'].bfill() df['value'] = (ffill_vals + bfill_vals) / 2 # 恢复原始NaN,保证原始缺失值不被修改 df.loc[raw_na_mask, 'value'] = pd.NA # 得到最终结果,删除辅助计算的zscore列 df_mod = df.drop(columns=['zscore'])
效果验证
你给出的示例中,离群值为2021-01-03、2021-01-04,临时设为NaN后:
- 向前填充得到两个位置的值都是前序最近的非离群值5
- 向后填充得到两个位置的值都是后序最近的非离群值7
- 均值为
(5+7)/2=6,和你期望的输出结果完全一致
边界场景说明
如果离群值出现在序列最开头,没有前序非离群值,此时向前填充结果为NaN,均值会自动取后向填充的结果;同理出现在序列末尾时会自动取前向填充的结果,无需额外处理。
内容的提问来源于stack exchange,提问作者kspr
相关产品推荐
相关产品推荐

