机器学习数据分析之异常值检测

文章目录

  • 1.基于统计学的单变量异常值检验
    • 1.1 3 σ \sigma σ准则
    • 1.2 箱型图
    • 1.3 Grubbs检验
    • 1.4 ESD检验
    • 1.5 Dixon检验
  • 2. 时间序列数据的异常值检验
    • 2.1 ADTK python模块

在检查异常值之前,可以将缺失值填充好。
异常值检验可以分为单变量异常值检验和多变量异常值检验,对于时间序列数据而言还有趋势预测的时间序列异常值检验。

1.基于统计学的单变量异常值检验

可以先采用统计学方法查看数据的描述性统计(均值、标准差、最小值、最大值等信息)

"""假设数据为dataframe"""
# 查看dataframe的描述性统计
df.describe()

1.1 3 σ \sigma σ准则

这个原则有个条件:数据需要服从正态分布。在 3 σ 3\sigma 3σ原则下,异常值如超过3倍标准差,那么可以将其视为异常值。正负 3 σ 3\sigma 3σ的概率是99.7%,那么距离平均值 3 σ 3\sigma 3σ之外的值出现的概率为P(|x-u| > 3 σ 3\sigma 3σ) <= 0.003,属于极个别的小概率事件。如果数据不服从正态分布,也可以用远离平均值的多少倍标准差来描述。
数值分布在(μ-σ,μ+σ)中的概率为0.6827
数值分布在(μ-2σ,μ+2σ)中的概率为0.9545
数值分布在(μ-3σ,μ+3σ)中的概率为0.9973

import numpy as np
import pandas as pd
from scipy.stats import kstest

# 正态分布检验
def KsNormDetect(df):
    # 计算均值
    u = df['value'].mean()
    # 计算标准差
    std = df['value'].std()
    # 计算P值
    res=kstest(df, 'norm', (u, std))[1]
    # 判断p值是否服从正态分布,p<=0.05 则服从正态分布,否则不服从。
    if res<=0.05:
        print('该列数据服从正态分布------------')
        print('均值为:%.3f,标准差为:%.3f' % (u, std))
        print('------------------------------')
        return 1
    else:
        return 0

# 异常值检测并删除
def OutlierDetection(df, ks_res):
    # 计算均值
    u = df['value'].mean()
    # 计算标准差
    std = df['value'].std()
    if ks_res==1:
        # 定义3σ法则识别异常值
        # 识别异常值
        error = df[np.abs(df['value'] - u) > 3 * std]
        # 剔除异常值,保留正常的数据
        data_c = df[np.abs(df['value'] - u) <= 3 * std]
        # 输出异常数据
        print(error)
        return error

    else:
        print('请先检测数据是否服从正态分布-----------')
        return None

df = pd.DataFrame([111, 333, 12, 290, 265, 152, 222, 1213, 242467, 114, 231, 122, 33, 2, 1, 5, 22, 44], columns=["value"])
ks_res = KsNormDetect(df)
result = OutlierDetection(df, ks_res)
print(result)

1.2 箱型图

这种方法是利用箱型图的四分位距(IQR)对异常值进行检测,也叫Tukey‘s test。
四分位距(IQR)就是上四分位与下四分位的差值。而我们通过IQR的1.5倍为标准,规定:超过(上四分位+1.5倍IQR距离,或者下四分位-1.5倍IQR距离)的点为异常值。

import numpy as np
import pandas as pd

# 箱型图实现
def OutlierDetection(df):
    # 计算下四分位数和上四分位
    Q1 = df.quantile(q=0.25)
    Q3 = df.quantile(q=0.75)

    # 基于1.5倍的四分位差计算上下须对应的值
    low_whisker = Q1 - 1.5 * (Q3 - Q1)
    up_whisker = Q3 + 1.5 * (Q3 - Q1)

    # 寻找异常点
    kk = df[(df > up_whisker) | (df < low_whisker)]
    data1 = pd.DataFrame({'id': kk.index, '异常值': kk})
    return data1

# 创建数据
data = [1222, 87, 77, 92, 68, 80, 78, 84, 77, 81, 80, 80, 77, 92, 86, 76, 80, 81, 75, 77, 72, 81, 72, 84, 86, 80, 68, 77, 87, 76, 77, 78, 92, 75, 80, 78, 123, 3, 1223, 1232]
df = pd.DataFrame(data, columns=['value'])
df = df.iloc[:,0]
result = OutlierDetection(df)
print('箱线图检测到的异常值如下---------------------')
print(result)

# seaborn可视化箱型图
f,ax=plt.subplots(figsize=(10,8))
sns.boxplot(data=df, ax=ax)
plt.show()

1.3 Grubbs检验

3 σ \sigma σ准则的加强版,适合于近似正态分布数据的检验。

# pip3 install outlier_utils==0.0.3
from outlier import smirnov_grubbs as grubbs
# 默认双边检测,alpha是显著性,输出的结果会删除不满足要求的数据
data = pd.Series([1, 8, 9, 10, 9])
grubbs.test(data, alpha=0.05)
# 1     8
# 2     9
# 3    10
# 4     9
# dtype: int64

data = np.array([1, 8, 9, 10, 9])
grubbs.test(data, alpha=0.05) # array([8, 9, 10, 9])
# 返回离群指数的单侧(最小)检验
grubbs.min_test_indices([8, 9, 10, 1, 9], alpha=0.05) #[3]
#返回异常值的单侧(max)测试
grubbs.max_test_outliers([8, 9, 10, 1, 9], alpha=0.05) #[]
grubbs.max_test_outliers([8, 9, 10, 50, 9], alpha=0.05) #[50]

1.4 ESD检验

适合多个异常值检验。预先设定异常值个数上限r,执行r轮单独的检验。

import math
import numpy as np
from scipy import stats


def get_r(arr):
    m_arr = np.mean(arr)
    d_arr = abs(arr - m_arr)
    s = np.std(arr, ddof=1)  # 样本标准差,注意分母n-1
    out_ind = np.argmax(d_arr)
    return np.max(d_arr) / s, out_ind


def esd(data, alpha=0.05, max_anoms=0.10):
    n = len(data)
    if isinstance(max_anoms, float):
        r = math.ceil(max_anoms * n)
    else:
        r = max_anoms
    outliers = []
    for i in range(1, r + 1):
        p = 1 - alpha / (n - i + 1) / 2
        t = stats.t.ppf(p, n - i - 1)  # p分位点
        _lambda = (n - i) * t / math.sqrt((n - i - 1 + t ** 2) * (n - i + 1))
        arr = np.delete(data, outliers)
        _r, out_ind = get_r(arr)
        if _r > _lambda:  # 超出临界值,视为异常点
            outliers.append(out_ind)
        else:
            break
    return np.delete(data, outliers), data[outliers]

1.5 Dixon检验

下面这篇博文写得很清楚了,就不搬了:
Dixon检验判断正态分布离群值

本节其他参考:
正态分布检验以及异常值处理
异常值检测和处理
数据处理中常用的检验方法汇总

2. 时间序列数据的异常值检验

2.1 ADTK python模块

pip3 install adtk

时间序列的数据主要包括时间和相应的指标(如cpu,内存,数量等)。python中数据分析一般都是pandas的DataFrame,adtk要求输入数据的索引必须是DatetimeIndex。
pandas提供了时间序列的时间生成和处理方法。

# pd.date_range
stamps = pd.date_range("2012-10-08 18:15:05", periods=4, freq="D")
# DatetimeIndex(['2012-10-08 18:15:05', '2012-10-09 18:15:05',
# '2012-10-10 18:15:05', '2012-10-11 18:15:05'],
#  dtype='datetime64[ns]', freq='D')

# pd.Timestamp
tmp = pd.Timestamp("2018-01-05") + pd.Timedelta("1 day")
print(tmp, tmp.timestamp(), tmp.strftime('%Y-%m-%d'))
# 2018-01-06 00:00:00 1515196800.0 2018-01-06
pd.Timestamp( tmp.timestamp(), unit='s', tz='Asia/Shanghai')
# Timestamp('2018-01-06 08:00:00+0800', tz='Asia/Shanghai')
pd.to_datetime

adtk提供是validate_series来验证时间序列数据的有效性,如是否按时间顺序:

import pandas as pd
from adtk.data import validate_series
from adtk.visualization import plot
df = pd.read_csv('./data/nyc_taxi.csv', index_col="timestamp", parse_dates=True)
df = validate_series(df)
plot(df)

2.1.1 ThresholdAD

adtk.detector.ThresholdAD(low=None, high=None)
参数:
low:下限,小于此值,视为异常
high:上限,大于此值,视为异常
原理:通过认为设定上下限来识别异常
总结:固定阈值算法

from adtk.detector import ThresholdAD
threshold_ad = ThresholdAD(high=30, low=15)
anomalies = threshold_ad.detect(s)

2.1.2 QuantileAD

QuantileAD
adtk.detector.QuantileAD(low=None, high=None)
参数:
low:分位下限,范围(0,1),当low=0.25时,表示Q1
high:分位上限,范围(0,1),当low=0.25时,表示Q3
原理:通过历史数据计算出给定low与high对应的分位值Q_low,Q_high,小于Q_low或大于Q_high,视为异常
总结:分位阈值算法

from adtk.detector import QuantileAD
quantile_ad = QuantileAD(high=0.99, low=0.01)
anomalies = quantile_ad.fit_detect(s)

2.1.3 InterQuartileRangeAD

InterQuartileRangeAD
adtk.detector.InterQuartileRangeAD(c=3.0)
参数:
c:分位距的系数,用来确定上下限,可为float,也可为(float,float)
原理:
当c为float时,通过历史数据计算出 Q3+cIQR 作为上限值,大于上限值视为异常
当c=(float1,float2)时,通过历史数据计算出 (Q1-c1
IQR, Q3+c2*IQR) 作为正常范围,不在正常范围视为异常
总结:箱线图算法

from adtk.detector import InterQuartileRangeAD
iqr_ad = InterQuartileRangeAD(c=1.5)
anomalies = iqr_ad.fit_detect(s)

2.1.4 GeneralizedESDTestAD

GeneralizedESDTestAD
adtk.detector.GeneralizedESDTestAD(alpha=0.05)
参数:
alpha:显著性水平 (Significance level),alpha越小,表示识别出的异常约有把握是真异常
原理:将样本点的值与样本的均值作差后除以样本标准差,取最大值,通过t分布计算阈值,对比阈值确定异常点
计算步骤简述:
设置显著水平alpha,通常取0.05
指定离群比例h,若h=5%,则表示50各样本中存在离群点数为2
计算数据集的均值mu与标准差sigma,将所有样本与均值作差,取绝对值,再除以标准差,找出最大值,得到esd_1
在剩下的样本点中,重复步骤3,可以得到h个esd值
为每个esd值计算critical value: lambda_i (采用t分布计算)
统计每个esd是否大于lambda_i,大于的认为你是异常

from adtk.detector import GeneralizedESDTestAD
esd_ad = GeneralizedESDTestAD(alpha=0.3)
anomalies = esd_ad.fit_detect(s)

2.1.5 PersistAD

adtk.detector.PersistAD(window=1, c=3.0, side=‘both’, min_periods=None, agg=‘median’)
参数:
window:参考窗长度,可为int, str
c:分位距倍数,用于确定上下限范围
side:检测范围,为’positive’时检测突增,为’negative’时检测突降,为’both’时突增突降都检测
min_periods:参考窗中最小个数,小于此个数将会报异常,默认为None,表示每个时间点都得有值
agg:参考窗中的统计量计算方式,因为当前值是与参考窗中产生的统计量作比较,所以得将参考窗中的数据计算成统计量,默认’median’,表示去参考窗的中位值
原理:
用滑动窗口遍历历史数据,将窗口后的一位数据与参考窗中的统计量做差,得到一个新的时间序列s1;
计算s1的(Q1-cIQR, Q3+cIQR) 作为正常范围;
若当前值与它参考窗中的统计量之差,不在2中的正常范围内,视为异常。
调参:
window:越大,模型越不敏感,不容易被突刺干扰
c:越大,对于波动大的数据,正常范围放大较大,对于波动较小的数据,正常范围放大较小
min_periods:对缺失值的容忍程度,越大,越不允许有太多的缺失值
agg:统计量的聚合方式,跟统计量的特性有关,如 'median’不容易受极端值影响
总结:先计算一条新的时间序列,再用箱线图作异常检测

from adtk.detector import PersistAD
persist_ad = PersistAD(c=3.0, side='positive')
anomalies = persist_ad.fit_detect(s)

2.1.6 LevelShiftAD

LevelShiftAD
adtk.detector.LevelShiftAD(window, c=6.0, side=‘both’, min_periods=None)
参数:
window:支持(10,5),表示使用两个相邻的滑动窗,左侧的窗中的中位值表示参考值,右侧窗中的中位值表示当前值
c:越大,对于波动大的数据,正常范围放大较大,对于波动较小的数据,正常范围放大较小,默认6.0
side:检测范围,为’positive’时检测突增,为’negative’时检测突降,为’both’时突增突降都检测
min_periods:参考窗中最小个数,小于此个数将会报异常,默认为None,表示每个时间点都得有值
原理:
该模型用于检测突变情况,相比于PersistAD,其抗抖动能力较强,不容易出现误报

from adtk.detector import LevelShiftAD
level_shift_ad = LevelShiftAD(c=6.0, side='both', window=5)
anomalies = level_shift_ad.fit_detect(s)

2.1.7 SeasonalAD

adtk.detector.SeasonalAD(freq=None, side=‘both’, c=3.0, trend=False)
SeasonalAD主要是根据ClassicSeasonalDecomposition来处理,判断。
参数:
freq:季节性周期
c:越大,对于波动大的数据,正常范围放大较大,对于波动较小的数据,正常范围放大较小,默认6.0
side:检测范围,为’positive’时检测突增,为’negative’时检测突降,为’both’时突增突降都检测
trend: 是否考虑趋势

from adtk.detector import SeasonalAD
seasonal_ad = SeasonalAD(c=3.0, side="both")
anomalies = seasonal_ad.fit_detect(s)
plot(s, anomaly=anomalies, ts_markersize=1, anomaly_color='red', anomaly_tag="marker", anomaly_markersize=2)

2.1.8 组合算法

from adtk.pipe import Pipeline
steps = [
    ("deseasonal", ClassicSeasonalDecomposition()),
    ("quantile_ad", QuantileAD(high=0.995, low=0.005))
]
pipeline = Pipeline(steps)
anomalies = pipeline.fit_detect(s)
plot(s, anomaly=anomalies, ts_markersize=1, anomaly_markersize=2, anomaly_tag="marker", anomaly_color='red')

本节参考:
时间序列异常检测ADTK

你可能感兴趣的:(时间序列分析与处理,python)