如何在分组之后替换异常值?

4

我有一个风险数值组成的投资组合数据集。我想要按下面数据框中的 "Port" 列进行分组,然后将该组 "Risk" 列中大于该组 95% 分位数的数值替换为该投资组合组的中位数。

df =

    Date           Port       Risk
   2019-04-30        a         21.8
   2019-03-29        a         22.6
   2019-02-28        a         500
   2019-01-31        a         26.1
   2019-04-30        b         36.4
   2019-03-29        b         43.3
   2019-02-28        b         40
   2019-01-31        b         364

我尝试了在stackoverflow上找到的以下代码,但它没有起作用。

def replace(group):
    q = group.quantile(0.95)
    outlier = group>q
    group[outlier] = group.median()
    return group

    df.groupby('Port').transform(replace)

也尝试过

q = pd.DataFrame(df.groupby('Port')['Risk'].quantile(0.95))
df.loc[(((q.loc[df.Port,'Risk']<df['Risk'].values)))]=q.loc[df.Port,'Risk']

预期结果是用组“a”的中位数22.2替换端口“a”的第三个记录,用组“b”的中位数41.6替换端口“b”的第四个记录。

df =

    Date           Port       Risk
   2019-04-30        a         21.8
   2019-03-29        a         22.6
   2019-02-28        a         22.2
   2019-01-31        a         26.1
   2019-04-30        b         36.4
   2019-03-29        b         43.3
   2019-02-28        b         40
   2019-01-31        b         41.6
3个回答

3

坚持你发布的代码:

def replace(group):
    q = group.quantile(0.95)
    outlier = group>q
    group[outlier] = group.median()
    return group

df['Risk'] = (df.groupby('Port').transform(replace))
print(df)

输出:

         Date Port   Risk
0  2019-04-30    a  21.80
1  2019-03-29    a  22.60
2  2019-02-28    a  24.35
3  2019-01-31    a  26.10
4  2019-04-30    b  36.40
5  2019-03-29    b  43.30
6  2019-02-28    b  40.00
7  2019-01-31    b  41.65

2

根据输出的数据框中的注释,中位数似乎与您所说的略有不同。下面是一种使用GroupBy.transformwhere的方法。

“Original Answer”翻译成“最初的回答”。

g = df.groupby('Port').Risk
df['Risk'] = (df.Risk.where(g.transform('quantile', q=0.95) > df.Risk, 
                            g.transform('median')))

      Date     Port  Risk
0  2019-04-30    a  21.80
1  2019-03-29    a  22.60
2  2019-02-28    a  24.35 # -> np.median([21.8, 22.6, 500, 26.1]) = 24.35
3  2019-01-31    a  26.10
4  2019-04-30    b  36.40
5  2019-03-29    b  43.30
6  2019-02-28    b  40.00
7  2019-01-31    b  41.65

谢谢你提供的代码。它完美地运行了。有一个快速逻辑问题,因为我们正在检查在风险>95%分位数的数字替换为中位数,为什么where函数设置为检查95%分位数数组>风险?难道不应该是df.risk>g.transform(quantile, q = 0.95)吗?尽管代码可以工作并给出所需的结果,但还是有些困惑。 - sudhasethu
这是因为 where 会替换掉结果为 False 的部分,@sudhasethu。不用谢 :) 如果有帮助的话请别忘了点击接受答案(绿色勾选标志在投票图标下方)。 - yatu
1
谢谢Yatu。这非常有帮助。 - sudhasethu

1
这是一种方法来实现它:

df = pd.DataFrame({"Port" : ['a', 'a', 'a', 'a', 'b', 'b', 'b' ,'b'],
    "Risk" : [21.8, 22.6, 500, 26.1, 36.4,43.3,40,364]
})

for port in df['Port'].unique():
    mask_port = df['Port'] == port
    quantile_port = df[mask_port].quantile(0.95)
    median_port = df[mask_port].median()
    df.loc[(mask_port) & (df['Risk']>quantile_port.Risk), 'Risk'] = median_port.Risk

In [1] : print(df)
Out[1] :   Port   Risk
0    a  21.80
1    a  22.60
2    a  24.35
3    a  26.10
4    b  36.40
5    b  43.30
6    b  40.00
7    b  41.65

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接