数据清洗
在实际的项目中,数据从生产和收集过程中往往由于机器或人的问题导致脏数据的生成,这些脏数据包括缺失,噪声,不一致等等一系列问题数据。脏数据的产生是不可避免的,但在后期的建模分析过程中,如果直接使用原始数据进行建模分析,则得到的结果会受到脏数据的影响从而表现很差。
Data Cleaning
Data Laundering
Data Scrubbing
Data Massaging
处理缺失值
缺失值是指数据中未被记录的特征值,在 R 语言中用 NA 表示。在机器学习模型中比不是所有的方法都接受包含缺失值的数据用于建模分析,因此,在分析建模前我们需要对数据中的缺失值进行处理。
最简单的检查数据缺失值的方法是利用 summary() 函数对数据进行探索性分析,例如:对 airquality 数据集进行缺失值检测,该数据集统计了 1973 年 5 月到 9 月之间纽约每天的空气质量,包括臭氧(Ozone),日照(Solar.R),风力(Wind)和温度(Temp)。
summary (airquality[, 1 : 4 ])
Ozone Solar.R Wind Temp
Min. : 1.00 Min. : 7.0 Min. : 1.700 Min. :56.00
1st Qu.: 18.00 1st Qu.:115.8 1st Qu.: 7.400 1st Qu.:72.00
Median : 31.50 Median :205.0 Median : 9.700 Median :79.00
Mean : 42.13 Mean :185.9 Mean : 9.958 Mean :77.88
3rd Qu.: 63.25 3rd Qu.:258.8 3rd Qu.:11.500 3rd Qu.:85.00
Max. :168.00 Max. :334.0 Max. :20.700 Max. :97.00
NA's :37 NA's :7
处理缺失值
对于数据缺失的情况,Rubin 1 从缺失机制的角度分为 3 类:完全随机缺失(missing completely at random,MCAR),随机缺失(missing at random)和非随机缺失(missing not at random,MNAR)。在 Missing Data 2 中定义有,对于一个数据集,变量 \(Y\) 存在数据缺失,如果 \(Y\) 的缺失不依赖于 \(Y\) 和数据集中其他的变量,称之为 MCAR。如果在控制其他变量的前提下,变量 \(Y\) 不依赖于 \(Y\) 本身,称之为 MAR,即:
\[
P \left(Y_{missing} | Y, X\right) = P \left(Y_{missing} | X\right)
\]
如果上式不满足,则称之为 MNAR。例如:在一次人口调研中,我们分别收集了用户的年龄和收入信息,收入信息中存在缺失值,如果收入的缺失值仅依赖于年龄,则缺失值的类型为 MAR,如果收入的缺失值依赖于收入本身,则缺失值的类型为 MNAR。通过进一步分析,我们得到高收入者和低收入者在收入上的缺失率更高,因此收入的缺失类型属于 MNAR。
缺失值处理
在对缺失值进行处理之前,我们需要对缺失值有一个更加详细的了解,在 R 中 mice 扩展包的 md.pattern() 函数提供了一个探索缺失值模式的方法。
md.pattern() 函数将数据中所有的缺失情况进行了汇总,在汇总的结果中,0 表示缺失值,1 表示非缺失值,每一行都是一个缺失模式。每一行的第一个数字表示该缺失模式下样本的数量,最后一个数字表示该缺失模式下缺失变量的个数,例如第二行表示仅 Ozone 缺失的样本共有 35 个。最后一行统计了每个变量出现缺失的次数。
library (mice)
md.pattern (airquality, plot = FALSE )
Wind Temp Month Day Solar.R Ozone
111 1 1 1 1 1 1 0
35 1 1 1 1 1 0 1
5 1 1 1 1 0 1 1
2 1 1 1 1 0 0 2
0 0 0 0 7 37 44
缺失值处理
VIM 扩展包还提供了可视化分析缺失值的方法,首先是 aggr():
library (VIM)
aggr (airquality, prop = F, numbers = T)
左边的柱状图表示每个变量出现缺失值的次数或占比,右边由颜色块组成的矩阵,每一行表示一种缺失模式,右侧的数字表示该缺失模式出现的次数。
缺失值处理
VIM 还提供了一种可视化分析两个变量间缺失值关系的方法 marginplot()。左下角的 3 个数字分别表示两个变量缺失的数量以及同时缺失的数量。对于两组箱线图,外侧(左和下侧)的表示另一个变量缺失的情况下,该变量的箱线图,内侧(右和上侧)的表示该变量的整体箱线图。右上的为两个变量的散点图。如果一个变量的缺失属于完全随机缺失的话,则每组箱线图中的两个箱线图应该有较高的相似度。
marginplot (tao[, c ("Air.Temp" , "Humidity" )])
缺失值处理
删除法
删除法就是删除包含缺失值的数据(样本),但是这种方法仅在完全随机缺失的情况下是有效的。因此在进行删除前我们需要考虑样本的数量以及删除包含缺失值的样本是否会导致偏差的出现。
na.fail:仅当数据中不存在缺失值是返回对象,否则产生错误
na.omit:返回剔除缺失值的对象
na.exclude:同 na.omit,但残差和预测值同样本保持长度一致
na.pass:不做任何处理,直接返回
插补法
插补法是删除缺失值占比较高的特征,在删除特征我们还需要权衡其对预测结果的重要性。如果该特征对最终的预测结果影响较小,则我们可以直接删除该特征;相反如果该特征对预测结果影响较大,直接删除会对模型造成较大的影响,此时我们需要利用其它的方法对该特征的缺失值进行填补。
其中最简单的方式是利用均值,中位数或众数等统计量对其进行简单插补。这种插补方法是建立在完全随机缺失的前提假设下,同时会造成变量方差变小。
删除重复值
去重处理是指对于数据中重复的部分进行删除操作,对于一个数据集,我们可以从“样本”和“特征”两个角度去考虑重复的问题。
样本去重
从“样本”的角度,相同的事件或样本(即所有特征的值均一致)重复出现是可能发生的。但从业务理解的角度上考虑,并不是所有的情况都允许出现重复样本,例如:我们考察一个班级的学生其平时表现和最终考试成绩之间的相关性时,假设利用学号作为学生的唯一标识,则不可能存在两个学号完全相同的学生。则这种情况下我们就需要对于重复的“样本”做出取舍。
删除重复值
对于如下学生属性的数据框,我们分别以“id”或“id”和“gender”两种不同唯一性判断标准对数据框进行重复值判断:
students <- data.frame (
id = c (1 , 2 , 3 , 3 , 4 , 4 ),
gender = c ("F" , "M" , "F" , "F" , "M" , "F" )
)
dim (students)
length (unique (students[, c ("id" )]))
dim (unique (students[, c ("id" , "gender" )]))
删除重复值
特征去重
从“特征”的角度,不同的特征在数值上有差异,但其背后表达的物理含义可能是相同的。例如:一个人的月均收入和年收入两个特征,尽管在数值上不同,但其表达的都是一个人在一年内的收入能力,两个特征仅相差常数倍。因此,对于仅相差常数倍的特征需要进行去重处理,保留任意一个特征即可。
常量特征剔除
对于常量或方差近似为零的特征,其对于样本之间的区分度贡献为零或近似为零,这些特征对于后面的建模分析没有任何意义。对于常量特征,我们可以通过如下方法进行识别:
students <- data.frame (
id = c (1 , 2 , 3 ),
gender = c ("F" , "M" , "M" ),
grade = c (6 , 6 , 6 ),
height = c (1.7 , 1.81 , 1.75 )
)
sapply (students, function (x) length (unique (x)))
id gender grade height
3 2 1 3
删除重复值
对于识别方差近似为零的特征,我们可以利用 caret 扩展包中的 nearZeroVar() 函数,函数定义如下:
nearZeroVar (
x, freqCut = 95 / 5 , uniqueCut = 10 , saveMetrics = FALSE , names = FALSE , foreach = FALSE , allowParallel = TRUE )
x
向量,矩阵或数据框
freqCut
最高频次和次高频次样本数的截断比例
95/5
uniqueCut
非重复的样本占总体样本的截断百分比
10
saveMetrics
FALSE 则返回常量和近似常量的列;TRUE 则返回更过信息
FALSE
names
FALSE 则返回列号;TRUE 则返回列名
FALSE
删除重复值
我们利用 nearZeroVar() 函数对上文中的学生的各个特征进行常量和方差近似为零的常量特征检查:
library (caret)
nearZeroVar (students)
nearZeroVar (students, saveMetrics = T)
freqRatio percentUnique zeroVar nzv
id 1 100.00000 FALSE FALSE
gender 2 66.66667 FALSE FALSE
grade 0 33.33333 TRUE TRUE
height 1 100.00000 FALSE FALSE
其中,freqRatio 为最高频次和次高频次样本数的比例,percentUnique 为非重复的样本占总体样本的百分比,zeroVar 表示是否是常量,nzv 表示是否是方差近似为零的特征。
异常值处理
异常值是指样本中存在的同样本整体差异较大的数据,异常数据可以划分为两类:
异常值不属于该总体,而是从另一个总体错误抽样到样本中而导致的较大差异。
异常值属于该总体,是由于总体所固有的变异性而导致的较大差异。
对于数值型的单变量,我们可以利用拉依达准则对其异常值进行检测。假设总体 \(x\) 服从正态分布,则:
\[
P\left(|x - \mu| > 3 \sigma\right) \leq 0.003
\]
其中 \(\mu\) 表示总体的期望,\(\sigma\) 表示总体的标准差。因此,对于样本中出现大于 \(\mu + 3\sigma\) 或小于 \(\mu - 3\sigma\) 的数据的概率是非常小的,从而可以对大于 \(\mu + 3\sigma\) 或小于 \(\mu - 3\sigma\) 的数据予以剔除。
异常值处理
异常检测(Anomaly Detection) 是指对不符合预期模式或数据集中异常项目、事件或观测值的识别。通常异常的样本可能会导致银行欺诈、结构缺陷、医疗问题、文本错误等不同类型的问题。异常也被称为离群值、噪声、偏差和例外。
异常检测
异常检测技术用于各种领域,如入侵检测、欺诈检测、故障检测、系统健康监测、传感器网络事件检测和生态系统干扰检测等。它通常用于在预处理中删除从数据集的异常数据。在监督式学习中,去除异常数据的数据集往往会在统计上显著提升准确性。
常用的异常检测算法有:
基于密度的方法:最近邻居法、局部异常因子等
One-Class SVM
基于聚类的方法
Isolation Forest
AutoEncoder
异常检测
箱线图(Boxplot),是利用数据中的五个统计量:最小值、第一四分位数、中位数、第三四分位数与最大值来描述数据的一种方法,它也可以粗略地看出数据是否具有有对称性,分布的分散程度等信息。
\[
\begin{split}
LowerLimit &= \max \{Q_1 - 1.5 * IQR, Minimum\} \\
UpperLimit &= \min \{Q_3 + 1.5 * IQR, Maximum\} \\
IQR &= Q_3 - Q_1
\end{split}
\]
异常检测
Isolation Forest,Isolation 意为孤立、隔离,是名词,Forest 是森林,合起来就是“孤立森林”了,也有叫“独异森林”,并没有统一的中文叫法,大家更习惯用其英文的名字 isolation forest,简称 iForest 1 2 。
iForest 算法用于挖掘异常数据,或者说离群点挖掘,总之是在一大堆数据中,找出与其它数据的规律不太符合的数据。通常用于网络安全中的攻击检测和流量异常等分析,金融机构则用于挖掘出欺诈行为。对于找出的异常数据,然后要么直接清除异常数据,如数据清理中的去除噪声数据,要么深入分析异常数据,比如分析攻击、欺诈的行为特征。
异常检测
iForest 属于非监督学习的方法,假设我们用一个随机超平面来切割数据空间,切一次可以生成两个子空间。之后我们再继续用一个随机超平面来切割每个子空间,循环下去,直到每子空间里面只有一个数据点为止。iForest 由 \(t\) 个 iTree(Isolation Tree)孤立树组成,每个 iTree 是一个二叉树结构,其实现步骤如下:
从训练集中随机选择 \(\phi\) 个点样本点,放入树的根节点。
随机指定一个特征,在当前节点数据中随机产生一个切割点 \(p\) ,切割点产生于当前节点数据中指定维度的最大值和最小值之间。
以此切割点生成了一个超平面,将当前数据空间划分为 2 个子空间:小于 \(p\) 的数据作为当前节点的左孩子,大于等于 \(p\) 的数据作为当前节点的右孩子。
在孩子节点中递归步骤 2 和 3,不断构造新的孩子节点,直到孩子节点只有一个数据或孩子节点已到达限定高度。
获得 \(t\) 个 iTree 之后,iForest 训练就结束,然后我们可以用生成的 iForest 来评估测试数据了。对于一个训练数据 \(x\) ,我们令其遍历每一棵 iTree,然后计算 \(x\) 最终落在每个树第几层。然后我们可以得出 \(x\) 在每棵树的高度平均值。获得每个测试数据的平均深度后,我们可以设置一个阈值,其平均深度小于此阈值的即为异常。
异常检测
通过 remotes::install_github("gravesee/isofor") 安装 iForest 扩展包。
library (isofor)
x <- c (rnorm (1000 , 0 , 0.5 ), rnorm (1000 * 0.05 , - 1.5 , 1 ))
y <- c (rnorm (1000 , 0 , 0.5 ), rnorm (1000 * 0.05 , 1.5 , 1 ))
data <- data.frame (x, y)
if_model <- iForest (data, 100 , 32 )
p <- predict (if_model, data)
ol <- c (rep (0 , 1000 ), rep (1 , (0.05 * 1000 ))) + 2
col <- ifelse (p > quantile (p, 0.95 ), "red" , "blue" )
plot (x, y, col = col, pch = ol)
数据采样
简单随机抽样 :从总体 \(N\) 个单位中随机地抽取 \(n\) 个单位作为样本,使得每一个容量为样本都有相同的概率被抽中。特点是:每个样本单位被抽中的概率相等,样本的每个单位完全独立,彼此间无一定的关联性和排斥性。
x <- 1 : 10
sample (x, 5 , replace = F)
sample (x, 5 , replace = T)
dplyr:: sample_n (as.data.frame (x), 5 , replace = F)$ x
数据采样
分层抽样 :将抽样单位按某种特征或某种规则划分为不同的层,然后从不同的层中独立、随机地抽取样本。从而保证样本的结构与总体的结构比较相近,从而提高估计的精度。
iris_ <- iris[c (1 : 50 , 51 : 60 , 101 : 130 ), ]
ct <- table (iris_$ Species)
ct
setosa versicolor virginica
50 10 30
n <- round (as.numeric (ct) * 0.8 )
s_i <- sampling:: strata (iris_,
stratanames = "Species" , size = n, method = "srswor" )
Species ID_unit Prob Stratum
1 setosa 1 0.8 1
2 setosa 2 0.8 1
3 setosa 3 0.8 1
s <- iris_[s_i$ ID_unit, ]
table (s$ Species)
setosa versicolor virginica
40 8 24
数据采样
欠采样和过采样 :在处理有监督的学习问题的时候,我们经常会碰到不同分类的样本比例相差较大的问题,这种问题会对我们构建模型造成很大的影响,因此从数据角度出发,我们可以利用欠采样或过采样处理这种现象。
library (ROSE)
data (hacide)
table (hacide.train$ cls)
us <- ovun.sample (cls ~ ., data = hacide.train,
method = "under" , N = 400 , seed = 112358 )
os <- ovun.sample (cls ~ ., data = hacide.train,
method = "over" , N = 1200 , seed = 112358 )
table (os$ data$ cls)
数据集分割
将数据分割为训练集和测试集的目的是要确保机器学习算法可以从中获得有用价值的信息。因此没必要将太多信息分配给测试集。然而,测试集越小,泛化误差的估计就越不准确。将数据集分割为训练集和测试集就是对两者的平衡。
在实践中,最常用的分割比例为 60:40,70:30,80:20,具体取决于数据集的规模,对于大数据集分割比例为 90:10 或 99:1 也是常见和适当的做法。
一般的做法是在模型训练和评估后保留测试数据,然后在整个数据集上再进行训练,以提高模型的性能。虽然通常推荐这种做法,但它可能会导致较差的泛化性能。
iris_split <- createDataPartition (
y = iris$ Species, p = 0.8 , list = FALSE )
train_data <- iris[iris_split, ]
table (train_data$ Species)
setosa versicolor virginica
40 40 40
test_data <- iris[- iris_split, ]
table (test_data$ Species)
setosa versicolor virginica
10 10 10