z-score
对于一维数据,最常用评价异常的方法就是z-score方法,它的定义如下:
z
i
=
x
i
−
u
δ
z_i=\frac{x_i-u}{\delta}
zi=δxi−u
其中,
x
i
x_i
xi是样本值,
u
u
u是均值,
δ
\delta
δ是样本标准差。因此
z
i
z_i
zi就是衡量该样本点距离样本均值有多少个标准差,用来表示各原始数据在数据组中的相对位置。
另外,若样本服从正态分布,它可以表示该数据以下或以上数据的比例,即具有了概率的意义;
比如样本服从正态分布,如果设置z-score的阈值为-2(低于的为异常,如果为正,高于的为异常),那么距离均值2个标准差范围的数据量有95%(正态分布的性质),则有2.5%的数据会被标记为异常)
修正的z-score
Motivation:因为均值与标准差对于异常值都很敏感,有时候得到的z-score不是可靠的。
修正的z-score的定义如下:
z
i
=
x
i
−
X
~
M
A
D
z_i=\frac{x_i-\widetilde{X}}{MAD}
zi=MADxi−X
其中,
x
i
x_i
xi是样本值,
X
~
\widetilde{X}
X
是整个样本的中位数,MAD(Median Absolute deviation)是中位数绝对偏差,定义如下:
M
A
D
=
m
e
d
i
a
n
∣
x
i
−
X
~
∣
MAD=median{|x_i-\widetilde{X}|}
MAD=median∣xi−X
∣
标准差的定义是与均值距离的平方和,对异常值更敏感,比如一个较大的样本值在样本内,则会直接影响到样本的标准差,而MAD不会,它具有更好的鲁棒性。
MAD的用法类似于样本标准差,为了使用MAD作为一致估计量来估计标准差,我们可以有(具体推导见MAD):
δ
^
=
k
∗
M
A
D
\hat{\delta}=k*MAD
δ^=k∗MAD
其中,
k
k
k只是一个常量因子,与样本分布有关,如果样本服从正态分布,
k
k
k=1.4826.
代码实现
例子:这里有一个数据集,包含2012年康涅狄格州学区SAT的学生参与率,我们的任务是找到低参与率的学校,可以看做一个异常检测任务。由于我们要找低参与率的学校,所以阈值是一个负数,这里我们设为-2。
ps: 对于较大的数据集,较大的绝对值
z
z
z(通常为
z
=
3
z=3
z=3)通常用作阈值。因为我们的数据集很小,
z
z
z的大值可能导致没有数据被标记为异常。另外,我们在选择z$时比较保守,因为我们想帮助尽可能多的学校。
z-score
import scipy.stats as ss
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import random
#用于展示检测结果
def plot_anomaly(score_data, threshold):
# Mask to plot values above and below threshold in different colors
score_data = score_data.copy().sort_values(ascending=False).values
ranks = np.linspace(1, len(score_data), len(score_data))
mask_outlier = (score_data < threshold)
plt.figure(dpi=150)
plt.plot(ranks[~mask_outlier], score_data[~mask_outlier],'o', color='b',label='OK schools')
plt.plot(ranks[mask_outlier], score_data[mask_outlier],'o', color='r', label='anomalies')
plt.axhline(threshold,color='r',label='threshold', alpha=0.5)
plt.legend(loc = 'lower left')
plt.title('Z-score vs. school district', fontweight='bold')
plt.xlabel('Ranked School district')
plt.ylabel('Z-score')
plt.show()
data = pd.read_csv('SAT_CT_District_Participation_2012.csv')
zscore_rate = ss.zscore(ct_test['Participation Rate'], ddof=0)#ddof是标准差计算中的自由度修正,默认为0,即标准差分母是n,而不是n-1
data=data.assign(zscore=zscore_rate)
plot_anomaly(data['zscore'], -2)
anomalies = data[(data['zscore'] < -2)]
anomalies
效果:
修正的z-score
def plot_anomaly(score_data, threshold):
# Mask to plot values above and below threshold in different colors
score_data = score_data.copy().sort_values(ascending=False).values
ranks = np.linspace(1, len(score_data), len(score_data))
mask_outlier = (score_data < threshold)
plt.figure(dpi=150)
plt.plot(ranks[~mask_outlier], score_data[~mask_outlier],'o', color='b',label='OK schools')
plt.plot(ranks[mask_outlier], score_data[mask_outlier],'o', color='r', label='anomalies')
plt.axhline(threshold,color='r',label='threshold', alpha=0.5)
plt.legend(loc = 'lower left')
plt.title('Z-score vs. school district', fontweight='bold')
plt.xlabel('Ranked School district')
plt.ylabel('Z-score')
plt.show()
#修正z-score方法
def modify_zscore(data,k=1.4826):
data_median=np.median(data)
dev_from_med=np.array(data)-data_median
MAD=np.median(np.abs(dev_from_med))
mod_zscore=dev_from_med/(k*MAD)#使用的是标准差的一致性估计
return mod_zscore,MAD
data = pd.read_csv('SAT_CT_District_Participation_2012.csv')
mod_zscore,MAD=modified_zscore(data['Participation Rate'])
data = data.assign(mod_zscore=mod_zscore)
plot_anomaly(data['mod_zscore'],-2)
anomalies = data[(data['zscore'] < -2)]
anomalies
效果:
效果比较:
我们可以发现,z-score方法识别出的异常点一共4项,而修正后的z-score方法识别结果除此之外,还识别出第55项0.47为低参与率。另外,通过计算,可以发现
k
∗
M
A
D
k*MAD
k∗MAD比样本标准差更小,也体现MAD统计量受异常点影响较小,具有更好的鲁棒性。