Pandas按双列分组查找异常值并用组中位数替换
问题背景
在基于分组与日期维度查找DataFrame异常值时存在实现障碍,需求为:定位并替换指定异常值——2022-06-27日期下A组的数值10、B组的数值20,分别替换为对应分组同日期的中位数(前者替换为3,后者替换为4)。
原问题提供的样例代码与数据(存在列名笔误):
index = [0,1,2,3,4,5,6,7,8,9,10,11] s = pd.Series(['A','A','A','A','A','A','B','B','B','B','B','B'],index= index) t = pd.Series(['2022-06-28','2022-06-28','2022-06-28','2022-06-27','2022-06-27','2022-06-27', '2022-06-28','2022-06-28','2022-06-28','2022-06-27','2022-06-27','2022-06-27'],index= index) r = pd.Series([1,2,1,2,3,10,2,3,2,3,4,20],index= index) df = pd.DataFrame(s,columns = ['group']) df['date'] = t df['vale'] = r print (df)
目标数据结构:
group date val 0 A 2022-06-28 1 1 A 2022-06-28 2 2 A 2022-06-28 1 3 A 2022-06-27 2 4 A 2022-06-27 3 5 A 2022-06-27 10 6 B 2022-06-28 2 7 B 2022-06-28 3 8 B 2022-06-28 2 9 B 2022-06-27 3 10 B 2022-06-27 4 11 B 2022-06-27 20
实现步骤
- 修正列名笔误:原代码赋值数值列时用的列名是
vale,和打印输出的val不一致,先统一列名避免运行报错 - 计算分组中位数:以
group、date为联合维度分组,计算每个分组下数值列的中位数,作为异常值的替换基准 - 定位异常行:如果已知明确的异常值特征,可以直接通过布尔筛选定位行;如果需要通用检测能力,可以用IQR、3σ等离群点检测规则自动识别异常值
- 批量替换值:将定位到的异常位置的数值,替换为所属分组的中位数即可
完整可运行代码
import pandas as pd # 构造样例数据(已修正列名笔误) index = [0,1,2,3,4,5,6,7,8,9,10,11] s = pd.Series(['A','A','A','A','A','A','B','B','B','B','B','B'],index= index) t = pd.Series(['2022-06-28','2022-06-28','2022-06-28','2022-06-27','2022-06-27','2022-06-27', '2022-06-28','2022-06-28','2022-06-28','2022-06-27','2022-06-27','2022-06-27'],index= index) r = pd.Series([1,2,1,2,3,10,2,3,2,3,4,20],index= index) df = pd.DataFrame(s,columns = ['group']) df['date'] = t df['val'] = r # 计算每个(分组+日期)对应的中位数 group_median_map = df.groupby(['group', 'date'])['val'].median().to_dict() # 方法1:已知异常值精准替换(适配本次明确知道两个异常点的场景) df.loc[ (df['date'] == '2022-06-27') & (df['group'] == 'A') & (df['val'] == 10), 'val' ] = group_median_map[('A', '2022-06-27')] df.loc[ (df['date'] == '2022-06-27') & (df['group'] == 'B') & (df['val'] == 20), 'val' ] = group_median_map[('B', '2022-06-27')] # 方法2:通用异常自动识别替换(基于IQR规则,无需手动指定异常值,取消注释即可直接使用) # for (group_name, date_val), sub_df in df.groupby(['group', 'date']): # q1 = sub_df['val'].quantile(0.25) # q3 = sub_df['val'].quantile(0.75) # iqr = q3 - q1 # upper_bound = q3 + 1.5 * iqr # lower_bound = q1 - 1.5 * iqr # # 替换超出上下界的离群值为分组中位数 # df.loc[ # (df['group'] == group_name) & (df['date'] == date_val) & # ((df['val'] > upper_bound) | (df['val'] < lower_bound)), # 'val' # ] = sub_df['val'].median() print(df)
运行后索引5的原值10将替换为3,索引11的原值20将替换为4,符合需求。
内容的提问来源于stack exchange,提问作者Itisai
相关产品推荐
相关产品推荐

