在 Python 中为具有不同实际值的多列删除异常值并计算修剪后的平均值

Removing outliers and calculating a trimmed mean in Python for multiple columns with different number of actual values

提问人:Anakin Skywalker 提问时间:8/14/2023 最后编辑:Anakin Skywalker 更新时间:8/15/2023 访问量:70

问:

我有一个数据集。假设有 10010 行和 100 列,列值可能包括 NaN,并且每列 NaN 可以不同。

我想要

  • 从该数据集中选取 n 个列(假设 20 列,不带顺序,例如 Column1、Column2 等)。
  • 修剪异常值(每个选定列的最高值的 2.5% 和最低值的 2.5%),不包括 NaN 值(因此,如果 10010 个值中有 10 个值是 Column1 中的 NaN,我需要从顶部修剪掉实际最高的 250 个值,从 10000 个值的底部修剪掉 250 个实际最低值)
  • 但是,如果 Column2 最初有 110 NaN,我想从每侧修剪 2.5% 的实际值数(在本例中为 9900,而不是像 Column1 列中的 10000)
  • 计算每个选定列的修剪平均值
  • 修剪后有一个新数据集,其中所有修剪的异常值都转换为 NaN
Python pandas 修剪 异常值

评论

1赞 user19077881 8/14/2023
就新 DF 而言,删除值意味着什么?由于数据集 DF 很可能对每列具有不同的异常值行索引,因此您不是说删除整行吗?您是否尝试过对您的需求进行编码,如果是这样,它有什么问题。
0赞 Anakin Skywalker 8/14/2023
@user19077881,我并不是要丢行。我的代码现在一团糟:)
1赞 user19077881 8/14/2023
然后会发生什么?删除的异常值转换为 NaN?您真的想要一个“修剪”的 DF 还是只需要修剪后的平均值。
0赞 Anakin Skywalker 8/14/2023
是的,将这些异常值转换为 NaN 很棒,我将更新问题。我确实需要一个新的“修剪”数据帧,我需要修剪的均值。提前致谢!
0赞 Anakin Skywalker 8/14/2023
@user19077881,我稍微更新了一下问题。提前感谢您的时间和帮助!

答:

2赞 user19077881 8/14/2023 #1

下面的这个简化示例显示了一种可能有用的方法,并使用 pd.quantile。代码可以根据您的要求进行开发(显然包括分位数参数)。

import pandas as pd

df = pd.DataFrame({'col1': [ 1, 2, 3, 4, None, 6, 7, 8, 54],
                   'col2': [3, 5, 13, 14, 2, 16, 17, 18, 19] })

cols = ['col1', 'col2']
for col in cols:
    lo = df[col].quantile(0.1)
    hi = df[col].quantile(0.9)
    df[col] = df[col].where((df[col]> lo) & (df[col] < hi), None)
    print(f'mean for {col} is: ', df[col].mean().round(2))


print(df)

给:

mean for col1 is:  5.0
mean for col2 is:  12.29

   col1  col2
0   NaN   3.0
1   2.0   5.0
2   3.0  13.0
3   4.0  14.0
4   NaN   NaN
5   6.0  16.0
6   7.0  17.0
7   8.0  18.0
8   NaN   NaN

上面的代码使用值阈值将异常值更改为 NaN;这将是通常的方法。 如果要求是在任一极端更改多个值,那么这可能是 通过保存和操作索引、按值排序、更改异常值来完成 比例,然后使用索引恢复原始顺序。下面的代码假设 最初使用了默认的数字索引;如果不是,则用户 索引需要保存,然后最终重新恢复。

cut_val = 0.2     # proportion of non_NaN values to remove from each extreme
num_rows = len(df)

cols = ['col1', 'col2']
for col in cols:
    num_not_nan = num_rows - df[col].isna().sum()
    cut = int(num_not_nan*cut_val)
    dfx = df[col].sort_values()
    idx = dfx.index.to_list()   #save sorted index
    dfx.index = range(num_rows)       #use numerical re-index so .loc can be used
    dfx.loc[0:cut-1] = None
    dfx.loc[num_not_nan-cut:num_not_nan] = None
    dfx.index=idx              #impose original index
    df[col] = dfx.sort_index()
    print(f'mean for {col} is: ', df[col].mean().round(2))

print(df)

评论

0赞 Anakin Skywalker 8/15/2023
一个问题。你的代码很棒,它可以修剪低于和高于阈值的所有内容。如何额外更新此代码,而不是根据特定值删除异常值,而是删除值的数量。例如,如果我有 10000(其中 9 个留空),那么 10000 的 2.5% = 250 个值,我将删除前 250 个值和后 250 个值,然后计算修剪后的平均值。你看到区别了吗?例如,如果值 2 在阈值范围内,则您的代码会将所有值 = 2 转换为 NaN,但我想删除特定数量的值,而不是所有值 <=2(例如)
1赞 user19077881 8/15/2023
请参阅我认为符合此值数量要求的编辑答案,