数据预处理——异常值查找与剔除
全糖奶茶屋
2022年08月30日 11:58

        本专栏介绍的是数据预处理的过程中, 我们遇到异常值时的处理方法. 尤其是在面对大数据的题目时, 有时会出现统计的异常值, 他们明显偏离于平均值或一般值, 我们需要将其找出来, 并进行剔除. 

        值得注意的是, 这个步骤作为数据处理, 也是建模之前的准备步骤, 一定不要花大量篇幅进行书写, 通常我们只会使用一段话进行过程的叙述, 并且明确说明删除异常值的结果就可以了. 此类方法一般也不会进入到文章的关键词中.

1、3σ 法(拉以达法则)(正态分布样本数据)

       3σ准则是指先假设一组检测数据只含有随机误差,对其进行计算处理得到标准偏差,按一定概率确定一个区间,认为凡超过这个区间的误差,就不属于随机误差而是粗大误差,含有该误差的数据应予以剔除。

       这种判别处理原理及方法仅局限于对正态或近似正态分布的样本数据处理,它是以测量次数充分大为前提(样本>10),当测量次数少的情形用准则剔除粗大误差是不够可靠的。

    

    3σ法则为:

  • 数值分布在(μ-σ,μ+σ) 中的概率为0.6827

  • 数值分布在(μ-2σ,μ+2σ) 中的概率为0.9545

  • 数值分布在(μ-3σ,μ+3σ) 中的概率为0.9973

  • 可以认为,Y 的取值几乎全部集中在(μ-3σ,μ+3σ) 区间内,超出这个范围的可能性仅占不到0.3%

3σ法则下的分布

代码块
JavaScript
自动换行
复制代码
load a
%% 判断数据是否符合正太分布
[h,p]=lillietest(a)    %判断是否正态。h=0
%返回值h为假设,只有0和1两种情况,h=0假设符合正态分布,h=1假设不符合正态分布
%返回值p为方差概率,也可以说事情的发生概率,p<0.05(显著性水平通常取0.05,还有0.025和0.01三种情况)为不可能事件,拒绝;p>0.05,接受
%%
subplot(2,1,1);
plot(a);
aa=mean(a); 
sig=std(a); %算出a的标准偏差。
m=zeros(1,length(a));
num = [];%记录异常值
n = [];%构建一个新的矩阵用以储存新的数据集(异常值已经替换)
i=1;
for t=1:length(a)
	m(t)=abs(a(t)-aa);
	if m(t)>3*sig
  		n(t)=aa;%这里把异常值替换成了均值,也可以直接替换成其他的值如0等,然后进行剔除
  		num(i)=a(t);%显示异常数据,如果没有异常数据的话将不会产生num变量
  		i=i+1;
	else
  		n(t)=a(t);
  	end
end
b=0:1:length(n)-1;
subplot(2,1,2);
plot(b,n);
title('拉以达法则剔除坏值')
xlabel('采样时间');
ylabel('采样点数')

复制成功

2、Z-score方法 (正态分布样本数据)

        Z-Score也称为标准分数,是一维或低维特征空间中的参数异常检测方法。假定数据服从高斯分布,异常值是分布尾部的数据点,远离数据的平均值。

        其数学定义:

Z_i%E2%80%8B%20%3D%20%5Cfrac%7Bx_i-u%7D%7B%5Csigma%7D%0A%E2%80%8B%0A

其中, x_i 是一个数据点,u 为所有点 x_i 的平均值;%5Csigma 是所有点  x_i 的标准偏差,

        那么如何确定异常值的阈值呢?我们一般将阈值 Z_%7Bthr%7D 设置为2.5、3.0或3.5,  若

%7CZ_i%7C%3EZ_%7Bthr%7D

我们可以将超出此范围的数据点视为异常值。

注意事项

  基于正态分布的3σ法则或Z-score方法是以假定数据服从正态分布为前提的,但实际数据往往并不严格服从正态分布。它们判断异常值的标准是以计算数据批的均值和标准差为基础的,而均值和标准差的耐抗性极小,异常值本身会对它们产生较大影响,这样产生的异常值个数不会多于总数0.7%。显然,应用这种方法于非正态分布数据中判断异常值,其有效性是有限的。

3、四分位(箱型图)(任何数据集)

       箱形图是数字数据通过其四分位数形成的图形化描述。这是一种非常简单但有效的可视化离群点的方法。考虑把上下触须作为数据分布的边界。任何高于上触须或低于下触须的数据点都可以认为是离群点或异常值

        四分位间距 (IQR) 的概念被用于构建箱形图。IQR 是统计学中的一个概念,通过将数据集分成四分位来衡量统计分散度和数据可变性。简单来说,任何数据集或任意一组观测值都可以根据数据的值以及它们与整个数据集的比较情况被划分为四个确定的间隔。四分位数会将数据分为三个点和四个区间。

       四分位间距对定义离群点非常重要。它是第三个四分位数和第一个四分位数的差 (IQR = Q3 - Q1)。在这种情况下,离群点被定义为低于箱形图下触须(或 Q1 − 1.5 × IQR)或高于箱形图上触须(或 Q3 + 1.5 × IQR)的观测值。

箱形图示意图

4、LOF算法 (任何数据集)

        LOF是基于密度的算法,其最核心的部分是关于数据点密度的刻画。如果对 distanced-based 或者 density-based 的聚类算法有些印象,你会发现 LOF中用来定义密度的一些概念和K-Means算法一些概念很相似。

        首先用视觉直观的感受一下,如下图,对于C1集合的点,整体间距,密度,分散情况较为均匀一致。可以认为是同一簇;对于C2集合点,同样可认为是一簇。o1,  o2点相对孤立,可以认为是异常点或离散点。现在的问题是,如何实现算法的通用性,可以满足C1 和 C2 这种密度分散情况迥异的集合的异常点识别。LOF可以实现我们的目标,LOF不会因为数据密度分散情况不同而错误的将正确点判定为异常点。

数据密度分散情况不同示意图

        LOF算法是通过比较每个点p和邻域点的密度来判断该点是否为异常:点p的密度越低,越有可能是异常点。而点的密度是通过点之间的距离来计算的,点之间距离越远,密度越低;距离越近,密度越高。也就是说,LOF算法中点的密度是通过点的k邻域计算得到的,而不是通过全局计算得到,这里的"k邻域”也就是该算法中“局部”的概念。

主程序:

代码块
JavaScript
自动换行
复制代码
clear
clc
load x.txt;%这里的x时间维度数据竖向排列

K=3;%设置第K距离,如果数据点较多,可以设置大一点
%一般K设置为总样本量的5%~10%最佳
[n,m]=size(x);
dist=[];
dist=pdist2(x,x);%pdist2函数默认为欧式距离计算方式
lof = LOF(dist,K);

%一般一维、二维好画图,高维的可以选取两列数据绘制
if m==1
    figure;
    plot(x(:,1),x(:,2),'rx');
    hold on
    for i=1:n
        %这里绘制的圈大小按照lof值的十倍来,这样可视化后更明显
        plot(x(i,1),'bo','Markersize',lof(i)*10)
    end
end
if m==2
    figure;
    plot(x(:,1),x(:,2),'rx');
    hold on
    for i=1:n
        %这里绘制的圈大小按照lof值的十倍来,这样可视化后更明显
        plot(x(i,1),x(i,2),'bo','Markersize',lof(i)*10)
    end
end
复制成功

LOF函数定义:

代码块
JavaScript
自动换行
复制代码
%LOF算法
function lof = LOF(dist,K)
m=size(dist,1);                 %m为对象数,dist为两两之间的距离
distance = zeros(m,m);
num = zeros(m,m);               %distance 和num用来记录排序后的顺序,和对象编号顺序
kdistance = zeros(m,1);         %计算每个对象的kdistance
count  = zeros(m,1);            %k邻域的对象数
reachdist = zeros(m,m);         %计算两两之间的reachable-distance
lrd = zeros(m,1);
lof = zeros(m,1);
%计算k-距离
for i=1:m
    [distance(i,:),num(i,:)]=sort(dist(i,:),'ascend');
    kdistance(i)=distance(i,K+1); 
    count(i) = -1;%自己的距离为0,要去掉自己
    for j = 1:m
        if dist(i,j)<=kdistance(i)
            count(i) = count(i)+1;
        end
    end
end
for i = 1:m
    for j=1:i-1
        reachdist(i,j) = max(dist(i,j),kdistance(j));
        reachdist(j,i) = reachdist(i,j);
    end
end
for i = 1:m
    sum_reachdist=0;
    for j=1:count(i)
        sum_reachdist=sum_reachdist+reachdist(i,num(j+1));
    end
    %计算每个点的lrd
    lrd(i)=count(i)/sum_reachdist;
end
% 得到lof值
for i=1:m
    sumlrd=0;
    for j=1:count(i)
        sumlrd=sumlrd+lrd(num(j+1))/lrd(i);
    end
    lof(i)=sumlrd/count(i);
end
end
复制成功

结果图实例:

LOF算法结果示意图

5、孤独森林(Isolation Forest) (连续数据集)

        孤立森林是属于无监督学习范畴中检测异常值的一种模型,他不同于其他通过计算距离和密度来识别样本点是否是孤立点,而是通过样本的疏密程度来判断样本的是否孤立。仅适用于连续数据。即不需要有标记的样本来训练,但特征需要是连续的。

        孤立森林采用多重二分法将样本点进行分区,该算法将样本中所有样本进行切分,直到每个样本点或极少样本点被划分在同一区域呢,这样样本越密集的区域,区域中的点被孤立时所需要的切分次数就越多,同理样本是孤立点,则该点被孤立时切分的次数就越低。

孤独森林原理示意图

在python中实现孤立森林的包是sklearn中ensemble中的 IsolationForest

代码块
JavaScript
自动换行
复制代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import IsolationForest
 
rng = np.random.RandomState(42)
 
# 生成训练数据
X = 0.3 * rng.randn(100, 2)
X_train = np.r_[X + 2, X - 2]
# 产生一些常规的新观察
X = 0.3 * rng.randn(20, 2)
X_test = np.r_[X + 2, X - 2]
# 产生一些异常新奇的观察结果
X_outliers = rng.uniform(low=-4, high=4, size=(20, 2))
 
# fit the model
clf = IsolationForest(max_samples=100,
                      random_state=rng, contamination=0.05)
clf.fit(X_train)
y_pred_train = clf.predict(X_train)
y_pred_test = clf.predict(X_test)
y_pred_outliers = clf.predict(X_outliers)
复制成功

sklearn.ensemble.iforest中关于孤立森林算法实现的函数及参数含义

IsolationForest(n_estimators=100, max_samples='auto&#​39;, contamination='legacy&#​39;, max_features=1.0, bootstrap=False, n_jobs=None, behaviour='old&#​39;, random_state=None, verbose=0, warm_start=False)

n_estimators:孤立树的个数,默认是100个;

max_samples:从测试集中抽取的样本个数,这些样本会训练每一个孤立树,默认值是“auto”意思是最多的样本数不超过256个

contamination:数据集中异常数据的比例

max_features=1.0:测试集样本中纳入孤立树进行计算的特征个数

bootstrap=False:是否有放回取样,false是无放回取样,true是独立树从测试集中有放回取样