首页 > 解决方案 > 是否有一种更简洁的方法来编写代码以在数据框中使用组均值有条件地替换异常值

问题描述

对于下面的 DF - 在值列中,产品 3(即 100)和产品 4(即 98)具有异常值。我想要

  1. 按 ['Class'] 分组

  2. 获得不包括异常值的 [Value] 的平均值

  3. 用步骤 2 中计算的平均值替换异常值。

任何关于如何构造代码的建议都非常感谢。我的代码在给定示例表的情况下可以工作,但我有一种感觉,当我在真正的解决方案中实施时,它可能无法工作。

 Product,Class,Value
0   1   A   5
1   2   A   4
2   3   A   100
3   4   B   98
4   5   B   20
5   6   B   25

我的代码实现:

# Establish the condition to remove the outlier rows from the DF
stds = 1.0 
filtered_df = df[~df.groupby('Class')['Value'].transform(lambda x: abs((x-x.mean()) / x.std()) > stds)]

输出:

Product Class   Value
0   1   A   5
1   2   A   4
4   5   B   20
5   6   B   25
# compute mean of each class without the outliers
class_means = filtered_df[['Class', 'Value']].groupby(['Class'])['Value'].mean()

输出:

Class 
A     4.5
B    22.5
#extract rows in DF that are outliers and fail the test
outlier_df = df[df.groupby('Class')['Value'].transform(lambda x: abs((x-x.mean()) / x.std()) > stds)]
outlier_df

输出:

Product Class   Value
2   3   A   100
3   4   B   98
#replace outlier values with computed means grouped by class
outlier_df['Value'] = np.where((outlier_df.Class == class_means.index), class_means,outlier_df.Value)
outlier_df

输出:

    Product Class   Value
2   3   A   4.5
3   4   B   22.5
#recombine cleaned dataframes
df_cleaned = pd.concat([filtered_df,outlier_df], axis=0 )
df_cleaned

输出:

Product Class   Value
0   1   A   5.0
1   2   A   4.0
4   5   B   20.0
5   6   B   25.0
2   3   A   4.5
3   4   B   22.5

标签: pythonpandasdataframe

解决方案


进行如下操作:

  1. 从您的代码开始:

    stds = 1.0
    
  2. 将您的 lambda 函数保存在一个变量下:

    isOutlier = lambda x: abs((x - x.mean()) / x.std()) > stds
    
  3. 定义以下函数,以应用于每个组:

    def newValue(grp):
        val = grp.Value
        outl = isOutlier(val)
        return val.mask(outl, val[~outl].mean())
    
  4. 生成新的列:

     df.Value = df.groupby('Class', group_keys=False).apply(newValue)
    

结果是:

   Product Class  Value
0        1     A    5.0
1        2     A    4.0
2        3     A    4.5
3        4     B   22.5
4        5     B   20.0
5        6     B   25.0

您甚至不会丢失原始的行顺序。

编辑

或者您可以将 lambda 函数的内容“合并”到newValue中 (因为您不会在任何其他地方调用它):

def newValue(grp):
    val = grp.Value
    outl = abs((val - val.mean()) / val.std()) > stds
    return val.mask(outl, val[~outl].mean())

推荐阅读