我有一个数据帧,其中"组"列可以有 50 个不同的值,还有一列"值"是数字。
一个例子可以是:
pd.DataFrame({'group':['a','b','c','a','a','b','a','c','c'],'value':[2,123,4,2.3,2.5,127,128,4,0.003]})
group value
0 a 2.000
1 b 123.000
2 c 4.000
3 a 2.300
4 a 2.500
5 b 127.000
6 a 128.000
7 c 4.000
8 c 0.003
我想从数据帧中删除其组的异常值,例如 abs(zscore(>3 或类似的东西。在示例中,值 a,128 将被删除,因为它对于组 a 来说是异常的,值 c,0.003 也将被删除。123 和 127 是正常的,因为它们的组是"b"。
输出应类似于(带有 z 分数列(:
group value
0 a 2.0
1 b 123.0
2 c 4.0
3 a 2.3
4 a 2.5
5 b 127.0
6 c 4.0
什么是有效的方法呢?
谢谢!
为 z 分数创建一个列,按特定组分组:
df['z_score'] = df.groupby('group')['value'].apply(lambda x: (x - x.mean())/x.std())
在给定阈值的情况下筛选 df:
df[abs(df['z_score']) > 3]
这是每组中的绝对 z 分数
df.groupby('group').value.transform(lambda x: (x - x.mean()) / x.std()).abs()
0 0.504239
1 0.707107
2 0.577350
3 0.499467
4 0.496286
5 0.707107
6 1.499992
7 0.577350
8 1.154701
Name: value, dtype: float64
不幸的是,数据集太小了,以至于 128 个发挥的作用比你想象的要大。 它的z分数只有1.5。
我建议计算数据点相对于其他一切统计时刻的 z 分数。
这是一个可以做到这一点的函数。 请注意,我需要组中至少 4 个点才能执行此操作。 如果组的长度小于 4,则为整个组返回 0。
def _zscore(x):
if len(x) > 3:
v = x.values
m = (v.sum() - v) / (v.size - 1)
vm = v - m[:, None]
np.fill_diagonal(vm, 0)
s = ((vm ** 2).sum(1) / (v.size - 2)) ** .5
return (v - m) / s
else:
return np.zeros_like(x)
现在,如果我们groupby
并transform
df.groupby('group').value.transform(_zscore)
0 -0.582866
1 0.000000
2 0.000000
3 -0.576658
4 -0.572532
5 0.000000
6 499.613605
7 0.000000
8 0.000000
Name: value, dtype: float64
我们可以清楚地看到,128
得到了499
_zscore
. 我们可以从一个简单的假设检验中安全地得出结论,128
不太可能来自与组中其他数据相同的分布。
我们可以像这样过滤它:
df[df.groupby('group').value.transform(_zscore) <= 3]
group value
0 a 2.000
1 b 123.000
2 c 4.000
3 a 2.300
4 a 2.500
5 b 127.000
7 c 4.000
8 c 0.003