话题来自近期和小狐狸同学讨论了数据异常值问题。小编其实这几年一直都有被同事们追问,不下几十次了吧。但是,从来没有认真总结过。小编过去都是靠经验来调试异常值。正好,花了2天好好总结了下,分享在公众号。
异常值分为三类,分别是:离群值、高杠杆值、强影响点。 离群值:明显偏离该变量集中趋势的点。高杠杆值:回归建模中与其他covariates变量有关的离群点。强影响点:对建模结果产生明显影响的数据点,剔除该点前后模型差别明显。第一种情况的异常值只针对于变量自身而言,第二种需要探讨x与covariates的关系,最后一种需要建模model才有明显体现。后面2种往往是高阶的处理技巧,平时很少用。常说的异常值,就指代为离群值outliers。
一、 SCI模板
引用几篇高分论文常见处理方法。在statistics部分几乎见不到描述。
二、异常值识别
图示法:有直方图,boxplot,Q-Q图、散点图,3D散点图。boxplot 与3σ图示如下,二者关联紧密。
检验法:包括参考值范围、3σ准则、Q检验、格拉布斯法(Grubbs)检验、狄克逊(Dixon)检验、Cook距离、马氏距离
经验法:行业允许的参考值范围,如正常人检查参考范围可作为临床判定正常与异常的参考标准。小编最喜欢这条,经常第一时间在数据排序后,观察下p1,p99的变量是否存在问题。
小编概括思维导图如下。
三、outliers处理
异常值的处理要十分谨慎。产生异常值既有可能是由于实验操作不当,产生了错误结果和读数,也有可能是其中蕴藏了一些新的有趣的东西。产生原因:第一种情况:异常值是由于实验操作不当,或观测、记录、计算错误导致;第二种情况:异常值不是弄错了,而是真实存在的固有的。处理方法:
“①不删除;②删除;明显错误则删除后填补(P1,P99法;LOD/√2法;线性差值法、MI等等缺失值填补系列);③变换logit变换、log/ln/平方根/倒数/平方根反正弦、Box-Cox、johnson变换;④稳健回归Theil Sen回归,RANSAC回归,Huber回归;⑤判断非人为错误可转换为分类,如age。
”
四、异常值其他分析方法
识别异常值的方法还有:DBSCAN、CBLOF、Isolation Forest、潜在类别模型LPA等。专门处理异常值的包,如:extremevalues、mvoutlier、outliers等等。孟德尔随机中还有MR多效性残差和异常值(MR- presso)方法检测可能的异常值。
五、R代码
dt=carData::Davis$weight
set.seet <- 123
x <- data.frame(id=seq(1,15,1),value=ceiling(c(rnorm(10,5,1),1,2,20,30,40)))
############### 任意分布 ##############
summary(dt) #先做简单描述
hist(dt,breaks=20) #设置20个条
#------一、图形法--------
#-----1 2D3D散点图-----
dt=carData::Davis
scatter.smooth(dt$weight,dt$height)
#3个变量 3d散点图
scatterplot3d::scatterplot3d(dt$weight,dt$height,dt$repwt)
library(rgl)
plot3d(dt$weight,dt$height,dt$repwt,col=rainbow(20),size=10) #注意rgl包不支持中文变量名
#-----2 boxplot-----
boxplot(dt$weight~dt$sex) #绘制根据性别分类的箱式图
#-----3 残差图-----
#标准化残差的绝对值大于2的点就是离群点
dt2=as.data.frame(datasets::state.x77) #调用state.x77数据集
#线性回归
mod=lm(Murder~Population+Illiteracy+Income+Frost,data = dt2) #构建回归方程
par(mfrow=c(2,2)) #把4幅图整合在一个画面中
plot(mod) #绘图
#----3.1 QQ图带区间-----
car::qqPlot(mod,labels=row.names(dt2),simulate=T,main="Q-Q图")
#-----4 强影响点-----
car::outlierTest(mod)
#-----5 强杠杆hat value-----
#该点的hat value大于其均值的2~3倍,就被认为是异常点
hat.plot <- function(mod){
p <- length(coefficients(mod))
n <- length(fitted(mod))
plot(hatvalues(mod))
abline(h = c(2, 3) * p/n, col = "red", lty = 2)
identify(1:n, hatvalues(mod), names(hatvalues(mod)))
}
hat.plot(mod)
#-----6 CooK距离-----
#Cook's D距离的值大于4/(n-k-1)为异常
#需要手动再确认?
cooksd <- cooks.distance(mod)
plot(cooksd, pch="*", cex=2) #绘制Cook距离
abline(h = 4*mean(cooksd, na.rm=T), col="red") #添加参考线
text(x=1:length(cooksd)+1, y=cooksd, labels=ifelse(cooksd>4*mean(cooksd, na.rm=T),names(cooksd),""), col="red") #添加标签
#-----7 汇总图-----
#influencePlo函数把离群点,高杠杆值点,强影响点这些异常值都整合在一个图
#纵坐标超过+2或小于-2的点可被认为是离群点,水平轴超过0.2或0.3的点是高杠杆点,圆圈大小与影响成比例,圆圈过大的点是对模型参数的估计造成的不成比例影响的强影响点
car::influencePlot(mod)
#-----8 KM聚类-----
dt=carData::Davis
dt=dt[2:5] #还是使用那个身高体重的数据,但是先去掉性别那一列
dt=na.omit(dt) #去掉缺失值
rt=kmeans(dt, centers=3) #进行聚类
ct=rt$centers[rt$cluster, ]
jl=sqrt(rowSums((dt-ct)^2)) #计算数据与簇中心的距离
outliers=order(jl, decreasing = T)[1:5] #挑选出前5个最大距离
print(dt[outliers,]) #输出异常值
#------二、分布法--------
#-----1 3σ标准差-----
mean(dt)-3*sd(dt) #计算下限
mean(dt)+3*sd(dt) #计算上限
#找出>上限和<下限的行号
dt[which(dt<mean(dt)-3*sd(dt) )]
dt[which(dt>mean(dt)+3*sd(dt) )]
#-----2 推荐!boxplot.stats 偏态分布-----
boxplot(dt) #箱式图
bout <- boxplot.stats(dt)[["out"]]#输出的结果$out异常值,3个
#异常值的标准是:小于P25-1.5*IQR,或大于P75+1.5*IQR,
dt[which(dt$weight %in% bout),]
#dataframe案例
set.seet <- 123
x <- data.frame(id=seq(1,15,1),value=ceiling(c(rnorm(10,5,1),1,2,20,30,40)))
bout <- boxplot(x$value)$out
bout
x[which(x$value %in% bout),]
#-----3 Hampel滤波-----
median(dt)-3*mad(dt) #中位数减去3倍的绝对中位差
median(dt)+3*mad(dt) #中位数加上3倍的绝对中位差
dt[which(dt>median(dt)+3*mad(dt))] #取出大于上限的值,5个
dt[which(dt<median(dt)-3*mad(dt))] #取出小于下限的值
#-----5 LOF,不好看-----
#局部异常因子分析(Local Outlier Factor, LOF)
#一个点的局部密度明显的比它周围的点的密度小异常
pacman::p_load(DMwR2)
ot <- lofactor(dt, k=10) #k是计算局部异常因子所需要判断异常点周围的点的个数,ot就是每个数据的比值
plot(density(ot)) #绘制比值的核密度图,可画可不画
wz <- order(ot, decreasing = T)[1:3] #挑出比值最高的前五名数据作为异常值
dt[wz] #输出异常值
#----三、经验法-------
#-----3.1 盖帽法P1 P99替换-----
#盖帽法
ul=quantile(dt,0.99)
ll=quantile(dt,0.01)
dt[dt<ll]<-ll #小于P1的数都被P1替换
dt[dt>ul]<-ul #大于P99的数都被P99替换
#---3.2 盖帽pcap函数!批量替换连续变量P1 P99----
x$id <- factor(x$id)
pcap <- function(x){
for (i in which(sapply(x, is.numeric))) {
quantiles <- quantile( x[,i], c(.01, .99 ), na.rm =TRUE)
x[,i] = ifelse(x[,i] < quantiles[1] , quantiles[1], x[,i])
x[,i] = ifelse(x[,i] > quantiles[2] , quantiles[2], x[,i])}
x}
pcap(x)
#---- 四、检验法(正态) ----
#-----4.1 Grubbs正态异常检验-----
pacman::p_load(outliers)
#outliers::Grubbs检验
grubbs.test(dt)#P<0.05为异常
#-----4.2 EnvStats包,不好看-----
pacman::p_load(EnvStats)
rosnerTest(dt) #函数默认找3个数判断是不是离群值,也可以设置参数k=5改成测五个
rosnerTest(x$value,3,warn=F)
六、小结
异常值需根据实际情况灵活科学的处理。小编以为,非人为错误不推荐剔除。冲高分可以做敏感性分析。
“论文结果不好的时候,剔除outliers往往有惊喜!
”