数据分析-特征预处理

特征工程

华盛顿大学教授、《终极算法》(The Master Algorithm)的作者佩德罗·多明戈斯曾在 Communications of The ACM 第 55 卷第 10 期上发表了一篇名为《机器学习你不得不知的那些事》(A Few Useful Things to Know about Machine Learning)的小文,介绍了 12 条机器学习中的“金科玉律”,其中的 7/8 两条说的就是对数据的作用的认识。多明戈斯的观点是:数据量比算法更重要。即使算法本身并没有什么精巧的设计,但使用大量数据进行训练也能起到填鸭的效果,获得比用少量数据训练出来的聪明算法更好的性能。这也应了那句老话:数据决定了机器学习的上限,而算法只是尽可能逼近这个上限。

但多明戈斯嘴里的数据可不是硬件采集或者软件抓取的原始数据,而是经过特征工程处理之后的精修数据,在他看来,
特征工程(feature engineering)才是机器学习的关键**。通常来说,原始数据并不直接适用于学习,而是特征筛选、构造和生成的基础。一个好的预测模型与高效的特征提取和明确的特征表示息息相关,如果通过特征工程得到很多独立的且与所属类别相关的特征,那学习过程就变成小菜一碟。


特征的本质是用于预测分类结果的信息,特征工程实际上就是对这些信息的编码。机器学习中的很多具体算法都可以归纳到特征工程的范畴之中,比如使用 L1 正则化项的 LASSO 回归,就是通过将某些特征的权重系数缩小到 0 来实现特征的过滤;再比如主成分分析,将具有相关性的一组特征变换为另一组线性无关的特征。这些方法本质上完成的都是特征工程的任务。


本质上讲,特征工程是一个表示和展现数据的过程;实际工作中,特征工程的目的是去除原始数据中的杂质和冗余,设计更高效的特征以刻画求解的问题与预测模型之间的关系。


特征工程的重要性有以下几点:

  • 特征越好,灵活性越强。好的特征的灵活性在于它允许你选择不复杂的模型,同时运行速度也更快,也更容易和维护。
  • 特征越好,构建的模型越简单。好的特征可以在参数不是最优的情况,依然得到很好的性能,减少调参的工作量和时间,也就可以大大降低模型复杂度。
  • 特征越好,模型的性能越出色。特征工程的目的本来就是为了提升模型的性能。

但是今天,我将不会讨论这些,而是把关注点放在算法之外,看一看在特征工程之前,数据的特征需要经过哪些必要的预处理(preprocessing)

特征预处理

通过特征提取,我们能得到未经处理的特征,这时的特征可能有以下问题:

  • 不属于同一量纲
    即特征的规格不一样,不能够放在一起比较。无量纲化可以解决这一问题。
  • 信息冗余
    对于某些定量特征,其包含的有效信息为区间划分,例如学习成绩,假若只关心“及格”或不“及格”,那么需要将定量的考分,转换成“1”和“0”表示及格和未及格。二值化可以解决这一问题。
  • 定性特征不能直接使用
    某些机器学习算法和模型只能接受定量特征的输入,那么需要将定性特征转换为定量特征。最简单的方式是为每一种定性值指定一个定量值,但是这种方式过于灵活,增加了调参的工作。
    通常使用哑编码的方式将定性特征转换为定量特征:假设有N种定性值,则将这一个特征扩展为N种特征,当原始特征值为第i种定性值时,第i个扩展特征赋值为1,其他扩展特征赋值为0。哑编码的方式相比直接指定的方式,不用增加调参的工作,对于线性模型来说,使用哑编码后的特征可达到非线性的效果。
  • 存在缺失值:缺失值需要补充。
  • 信息利用率低:不同的机器学习算法和模型对数据中信息的利用是不同的,之前提到在线性模型中,使用对定性特征哑编码可以达到非线性的效果。类似地,对定量变量多项式化,或者进行其他的转换,都能达到非线性的效果。


我们使用sklearn中的preproccessing库来进行数据预处理,可以覆盖以上问题的解决方案。
首先介绍数据预处理中,比较简单的部分:

处理缺失值

数据的缺失主要包括记录的缺失和记录中某个字段信息的缺失,两者都会造成分析结果的不准确。
缺失值产生的原因

  • 信息暂时无法获取,或者获取信息的代价太大。
  • 信息被遗漏,人为的输入遗漏或者数据采集设备的遗漏。
  • 属性不存在,在某些情况下,缺失值并不意味着数据有错误,对一些对象来说某些属性值是不存在的,如未婚者的配偶姓名、儿童的固定收入等。

缺失值的影响

  • 数据挖掘建模将丢失大量的有用信息。
  • 数据挖掘模型所表现出的不确定性更加显著,模型中蕴含的规律更难把握。
  • 包含空值的数据会使建模过程陷入混乱,导致不可靠的输出。

缺失值的处理方法

  • 直接使用含有缺失值的特征:当仅有少量样本缺失该特征的时候可以尝试使用;
  • 删除含有缺失值的特征:这个方法一般适用于大多数样本都缺少该特征,且仅包含少量有效值是有效的;
  • 插值补全缺失值

    插值补全缺失值

    最常使用的还是第三种插值补全缺失值的做法,这种做法又可以有多种补全方法。
  1. 均值/中位数/众数补全
    如果样本属性的距离是可度量的,则使用该属性有效值的平均值来补全;
    如果样本属性的距离不可度量,则可以采用众数或者中位数来补全。
  2. 同类均值/中位数/众数补全
    对样本进行分类后,根据同类其他样本该属性的均值补全缺失值,当然同第一种方法类似,如果均值不可行,可以尝试众数或者中位数等统计数据来补全。
  3. 固定值补全
    利用固定的数值补全缺失的属性值。
  4. 建模预测
    利用机器学习方法,将缺失属性作为预测目标进行预测,具体为将样本根据是否缺少该属性分为训练集和测试集,然后采用如回归、决策树等机器学习算法训练模型,再利用训练得到的模型预测测试集中样本的该属性的数值。
    这个方法根本的缺陷是如果其他属性和缺失属性无关,则预测的结果毫无意义;但是若预测结果相当准确,则说明这个缺失属性是没必要纳入数据集中的;一般的情况是介于两者之间。
  5. 高维映射
    将属性映射到高维空间,采用独热码编码(one-hot)技术。将包含 K 个离散取值范围的属性值扩展为 K+1 个属性值,若该属性值缺失,则扩展后的第 K+1 个属性值置为 1。
    这种做法是最精确的做法,保留了所有的信息,也未添加任何额外信息,若预处理时把所有的变量都这样处理,会大大增加数据的维度。这样做的好处是完整保留了原始数据的全部信息、不用考虑缺失值;缺点是计算量大大提升,且只有在样本量非常大的时候效果才好。
  6. 多重插补
    多重插补认为待插补的值是随机的,实践上通常是估计出待插补的值,再加上不同的噪声,形成多组可选插补值,根据某种选择依据,选取最合适的插补值。
  7. 压缩感知和矩阵补全
    压缩感知通过利用信号本身所具有的稀疏性,从部分观测样本中回复原信号。压缩感知分为感知测量和重构恢复两个阶段。
    感知测量:此阶段对原始信号进行处理以获得稀疏样本表示。常用的手段是傅里叶变换、小波变换、字典学习、稀疏编码等
    重构恢复:此阶段基于稀疏性从少量观测中恢复原信号。这是压缩感知的核心
    矩阵补全可以查看知乎上的问题
  8. 手动补全
    除了手动补全方法,其他插值补全方法只是将未知值补以我们的主观估计值,不一定完全符合客观事实。在许多情况下,根据对所在领域的理解,手动对缺失值进行插补的效果会更好。但这种方法需要对问题领域有很高的认识和理解,要求比较高,如果缺失数据较多,会比较费时费力。
  9. 最近邻补全
    寻找与该样本最接近的样本,使用其该属性数值来补全。

    处理异常值

    异常值分析是检验数据是否有录入错误以及含有不合常理的数据。忽视异常值的存在是十分危险的,不加剔除地把异常值包括进数据的计算分析过程中,对结果会产生不良影响。
    异常值是指样本中的个别值,其数值明显偏离其余的观测值。异常值也称为离群点,异常值分析也称为离群点分析。
  • 异常值检测
  1. 简单统计
    比如利用pandas库的describe()方法观察数据的统计性描述,或者简单使用散点图也能观察到异常值的存在,如下图所示:

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

  3. 箱型图
    这种方法是利用箱型图的四分位距(IQR)对异常值进行检测,也叫Tukey‘s test。箱型图的定义如下:

    四分位距(IQR)就是上四分位与下四分位的差值。而我们通过IQR的1.5倍为标准,规定:超过上四分位+1.5倍IQR距离,或者下四分位-1.5倍IQR距离的点为异常值。下面是Python中的代码实现,主要使用了numpy的percentile方法。
1
2
3
4
Percentile = np.percentile(df['length'],[0,25,50,75,100])
IQR = Percentile[3] - Percentile[1]
UpLimit = Percentile[3]+ageIQR*1.5
DownLimit = Percentile[1]-ageIQR*1.5


也可以使用seaborn的可视化方法boxplot来实现:

1
2
3
f,ax=plt.subplots(figsize=(10,8))
sns.boxplot(y='length',data=df,ax=ax)
plt.show()



上面三种方法是比较简单的异常值检测方法,接下来是一些较复杂的异常值检测方法,因此这里简单介绍下这些方法的基本概念。

  1. 基于模型预测

顾名思义,该方法会构建一个概率分布模型,并计算对象符合该模型的概率,将低概率的对象视为异常点。
如果模型是簇的组合,则异常点是不在任何簇的对象;如果模型是回归,异常点是远离预测值的对象(就是第一个方法的图示例子)。
优缺点:

  • 有坚实的统计学理论基础,当存在充分的数据和所用的检验类型的知识时,这些检验可能非常有效;
  • 对于多元数据,可用的选择少一些,并且对于高维数据,这些检测可能性很差。
  1. 基于近邻度的离群点检测

一个对象的离群点得分由到它的 k-最近邻(KNN)的距离给定。

这里需要注意 k 值的取值会影响离群点得分,如果 k 太小,则少量的邻近离群点可能会导致较低的离群点得分;如果 k 太大,则点数少于 k 的簇中所有的对象可能都成了离群点。为了增强鲁棒性,可以采用 k 个最近邻的平均距离。

优缺点:

  • 简单;
  • 基于邻近度的方法需要 O(m2) 时间,大数据集不适用;
  • k 值的取值导致该方法对参数的选择也是敏感的;
  • 不能处理具有不同密度区域的数据集,因为它使用全局阈值,不能考虑这种密度的变化。
  1. 基于密度的离群点检测

一种常用的定义密度的方法是,定义密度为到k个最近邻的平均距离的倒数。如果该距离小,则密度高,反之亦然。
另一种密度定义是使用 DBSCAN 聚类算法使用的密度定义,即一个对象周围的密度等于该对象指定距离 d 内对象的个数。
优缺点:

  • 给出了对象是离群点的定量度量,并且即使数据具有不同的区域也能够很好的处理;
  • 与基于距离的方法一样,这些方法必然具有 O(m2) 的时间复杂度。对于低维数据使用特定的数据结构可以达到 O(mlogm) ;
  • 参数选择是困难的。虽然 LOF 算法通过观察不同的 k 值,然后取得最大离群点得分来处理该问题,但是,仍然需要选择这些值的上下界。
  1. 基于聚类的离群点检测
    一个对象是基于聚类的离群点,如果该对象不强属于任何簇,那么该对象属于离群点。

离群点对初始聚类的影响:如果通过聚类检测离群点,则由于离群点影响聚类,存在一个问题:结构是否有效。这也是 k-means 算法的缺点,对离群点敏感。
为了处理该问题,可以使用如下方法:对象聚类,删除离群点,对象再次聚类(这个不能保证产生最优结果)。
优缺点:

  • 基于线性和接近线性复杂度(k均值)的聚类技术来发现离群点可能是高度有效的;
  • 簇的定义通常是离群点的补集,因此可能同时发现簇和离群点;
  • 产生的离群点集和它们的得分可能非常依赖所用的簇的个数和数据中离群点的存在性;
  • 聚类算法产生的簇的质量对该算法产生的离群点的质量影响非常大。
  1. 专门的离群点检测

除了以上提及的方法,还有两个专门用于检测异常点的方法比较常用:One Class SVM和Isolation Forest
异常值处理

  • 删除含有异常值的记录:直接将含有异常值的记录删除;
  • 视为缺失值:将异常值视为缺失值,利用缺失值处理的方法进行处理;
  • 平均值修正:可用前后两个观测值的平均值修正该异常值;
  • 不处理:直接在具有异常值的数据集上进行数据挖掘;

将含有异常值的记录直接删除的方法简单易行,但缺点也很明显,在观测值很少的情况下,这种删除会造成样本量不足,可能会改变变量的原有分布,从而造成分析结果的不准确。视为缺失值处理的好处是可以利用现有变量的信息,对异常值(缺失值)进行填补。
在很多情况下,要先分析异常值出现的可能原因,在判断异常值是否应该舍弃,如果是正确的数据,可以直接在具有异常值的数据集上进行挖掘建模。

处理类别不平衡问题

什么是类别不平衡呢?它是指分类任务中存在某个或者某些类别的样本数量远多于其他类别的样本数量的情况。
比如,一个十分类问题,总共有 10000 个样本,但是类别 1 到 4 分别包含 2000 个样本,剩余 6 个类别的样本数量加起来刚刚 2000 个,即这六个类别各自包含的样本平均数量大约是 333 个,相比前四个类别是相差了 6 倍左右的数量。这种情况就是类别不平衡了。


那么如何解决类别不平衡问题呢?
这里介绍八大解决办法。

  1. 扩充数据集

首先应该考虑数据集的扩充,在刚刚图片数据集扩充一节介绍了多种数据扩充的办法,而且数据越多,给模型提供的信息也越大,更有利于训练出一个性能更好的模型。
如果在增加小类样本数量的同时,又增加了大类样本数据,可以考虑放弃部分大类数据(通过对其进行欠采样方法)。

  1. 尝试其他评价指标

一般分类任务最常使用的评价指标就是准确度了,但它在类别不平衡的分类任务中并不能反映实际情况,原因就是即便分类器将所有类别都分为大类,准确度也不会差,因为大类包含的数量远远多于小类的数量,所以这个评价指标会偏向于大类类别的数据。
其他可以推荐的评价指标有以下几种

  • 混淆矩阵:实际上这个也是在分类任务会采用的一个指标,可以查看分类器对每个类别预测的情况,其对角线数值表示预测正确的数量;
  • 精确度(Precision):表示实际预测正确的结果占所有被预测正确的结果的比例,P=TP / (TP+FP)
  • 召回率(Recall):表示实际预测正确的结果占所有真正正确的结果的比例,R = TP / (TP+FN)
  • F1 得分(F1 Score):精确度和召回率的加权平均,F1=2PR / (P+R)
  • Kappa (Cohen kappa)
  • ROC 曲线(ROC Curves):常被用于评价一个二值分类器的优劣,而且对于正负样本分布变化的时候,ROC 曲线可以保持不变,即不受类别不平衡的影响。

其中 TP、FP、TN、FN 分别表示正确预测的正类、错误预测的正类、预测正确的负类以及错误预测的负类。图例如下:

  1. 对数据集进行重采样

可以使用一些策略该减轻数据的不平衡程度。该策略便是采样(sampling),主要有两种采样方法来降低数据的不平衡性。

  • 对小类的数据样本进行采样来增加小类的数据样本个数,即过采样(over-sampling ,采样的个数大于该类样本的个数)。
  • 对大类的数据样本进行采样来减少该类数据样本的个数,即欠采样(under-sampling,采样的次数少于该类样本的个素)。

采样算法往往很容易实现,并且其运行速度快,并且效果也不错。 一些经验法则:

  • 考虑对大类下的样本(超过 1 万、十万甚至更多)进行欠采样,即删除部分样本;
  • 考虑对小类下的样本(不足 1万甚至更少)进行过采样,即添加部分样本的副本;
  • 考虑尝试随机采样与非随机采样两种采样方法;
  • 考虑对各类别尝试不同的采样比例,比一定是 1:1,有时候 1:1 反而不好,因为与现实情况相差甚远;
  • 考虑同时使用过采样与欠采样。
  1. 尝试人工生成数据样本
    一种简单的人工样本数据产生的方法便是,对该类下的所有样本每个属性特征的取值空间中随机选取一个组成新的样本,即属性值随机采样。

你可以使用基于经验对属性值进行随机采样而构造新的人工样本,或者使用类似朴素贝叶斯方法假设各属性之间互相独立进行采样,这样便可得到更多的数据,但是无法保证属性之前的线性关系(如果本身是存在的)。
有一个系统的构造人工数据样本的方法 SMOTE(Synthetic Minority Over-sampling Technique)。SMOTE 是一种过采样算法,它构造新的小类样本而不是产生小类中已有的样本的副本,即该算法构造的数据是新样本,原数据集中不存在的。
它基于距离度量选择小类别下两个或者更多的相似样本,然后选择其中一个样本,并随机选择一定数量的邻居样本,然后对选择的那个样本的一个属性增加噪声,每次处理一个属性。这样就构造了更多的新生数据。
python 实现的 SMOTE 算法代码地址如下,它提供了多种不同实现版本,以及多个重采样算法。
https://github.com/scikit-learn-contrib/imbalanced-learn

  1. 尝试不同分类算法

强烈建议不要对待每一个分类都使用自己喜欢而熟悉的分类算法。应该使用不同的算法对其进行比较,因为不同的算法适用于不同的任务与数据。
决策树往往在类别不均衡数据上表现不错。它使用基于类变量的划分规则去创建分类树,因此可以强制地将不同类别的样本分开。目前流行的决策树算法有:C4.5、C5.0、CART和Random Forest等。

  1. 尝试对模型进行惩罚

你可以使用相同的分类算法,但使用一个不同的角度,比如你的分类任务是识别那些小类,那么可以对分类器的小类样本数据增加权值,降低大类样本的权值(这种方法其实是产生了新的数据分布,即产生了新的数据集),从而使得分类器将重点集中在小类样本身上。


一个具体做法就是,在训练分类器时,若分类器将小类样本分错时额外增加分类器一个小类样本分错代价,这个额外的代价可以使得分类器更加“关心”小类样本。如 penalized-SVM 和 penalized-LDA 算法。
如果你锁定一个具体的算法时,并且无法通过使用重采样来解决不均衡性问题而得到较差的分类结果。这样你便可以使用惩罚模型来解决不平衡性问题。但是,设置惩罚矩阵是一个复杂的事,因此你需要根据你的任务尝试不同的惩罚矩阵,并选取一个较好的惩罚矩阵。

  1. 尝试一个新的角度理解问题

从一个新的角度来理解问题,比如我们可以将小类的样本作为异常点,那么问题就变成异常点检测与变化趋势检测问题。

  • 异常点检测:即是对那些罕见事件进行识别。如通过机器的部件的振动识别机器故障,又如通过系统调用序列识别恶意程序。这些事件相对于正常情况是很少见的。
  • 变化趋势检测:类似于异常点检测,不同在于其通过检测不寻常的变化趋势来识别。如通过观察用户模式或银行交易来检测用户行为的不寻常改变。

将小类样本作为异常点这种思维的转变,可以帮助考虑新的方法去分离或分类样本。这两种方法从不同的角度去思考,让你尝试新的方法去解决问题。

  1. 尝试创新

仔细对问题进行分析和挖掘,是否可以将问题划分为多个更小的问题,可以尝试如下方法:

  • 将你的大类压缩成小类;
  • 使用 One Class 分类器(将小类作为异常点);
  • 使用集成方式,训练多个分类器,然后联合这些分类器进行分类;

对于类别不平衡问题,还是需要具体问题具体分析,如果有先验知识可以快速挑选合适的方法来解决,否则最好就是逐一测试每一种方法,然后挑选最好的算法。最重要的还是多做项目,多积累经验,这样遇到一个新的问题,也可以快速找到合适的解决方法。
以上针对异常值,缺失数据,分类不均问题做了详细的描述,那么接下来,就要转化数据,使之成为有效的特征。常用的方法是标准化,归一化,特征的离散化等。

无量纲化

量纲:就是单位,特征的单位不一致,特征就不能放在一起比较。
无量纲化使不同规格的数据转换到同一规格。常见的无量纲化方法有标准化和区间缩放法。标准化的前提是特征值服从正态分布,标准化后,其转换成标准正态分布。区间缩放法利用了边界值信息,将特征的取值区间缩放到某个特点的范围,例如[0, 1]等。

标准化

比方说有一些数字的单位是千克,有一些数字的单位是克,这个时候需要统一单位。如果没有标准化,两个变量混在一起搞,那么肯定就会不合适。

  • 0-1标准化

是对原始数据进行线性变换,将特征值映射成区间为[0,1]的标准值中:

  • Z标准化


基于特征值的均值(mean)和标准差(standard deviation)进行数据的标准化。它的计算公式为:
标准化的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas
data = pandas.read_csv('路径.csv')

#(一)Min-Max 标准化

from sklearn.preprocessing import MinMaxScaler
#初始化一个scaler对象
scaler = MinMaxScaler()
#调用scaler的fit_transform方法,把我们要处理的列作为参数传进去

data['标准化后的A列数据'] = scaler.fit_transform(data['A列数据'])
data['标准化后的B列数据'] = scaler.fit_transform(data['B列数据'])

#(二)Z-Score标准化 (可在scale中直接实现)

from sklearn.preprocessing import scale
data['标准化后的A列数据'] = scale(data['A列数据'])
data['标准化后的B列数据'] = scale(data['B列数据'])

区间缩放法

区间缩放法的思路有多种,常见的一种为利用两个最值进行缩放,公式表达为:

使用preproccessing库的MinMaxScaler类对数据进行区间缩放的代码如下:

1
2
3
4
from sklearn.preprocessing import MinMaxScaler

#区间缩放,返回值为缩放到[0, 1]区间的数据
MinMaxScaler().fit_transform(iris.data)

归一化

归一化是因为在特征会在不同的尺度下有不同的表现形式,归一化会使得各个特征能够同时以恰当的方式表现。比方说某个专辑的点击播放率一般不会超过0.2,但是专辑的播放次数可能会达到几千次,所以说为了能够在模型里面得到更合适结果,需要先把一些特征在尺度上进行归一化,然后进行模型训练。
与标准化的区别,简单来说,标准化是依照特征矩阵的列处理数据,其通过求z-score的方法,将样本的特征值转换到同一量纲下。归一化是依照特征矩阵的行处理数据,其目的在于样本向量在点乘运算或其他核函数计算相似性时,拥有统一的标准,也就是说都转化为“单位向量”。规则为l2的归一化公式如下:

使用preproccessing库的Normalizer类对数据进行归一化的代码如下:

1
2
3
4
5
from sklearn.preprocessing import Normalizer
scaler = Normalizer()
#归一化可以同时处理多个列,所以[0]第一个进行赋值
data['归一化后的A列数据'] = scaler.fit_transform(data['A列数据'])[0]
data['归一化后的B列数据'] = scaler.fit_transform(data['B列数据'])[0]

对定量特征二值化

定量特征二值化的核心在于设定一个阈值,大于阈值的赋值为1,小于等于阈值的赋值为0,公式表达如下:

使用preproccessing库的Binarizer类对数据进行二值化的代码如下:

1
2
3
4
from sklearn.preprocessing import Binarizer

#二值化,阈值设置为3,返回值为二值化后的数据
Binarizer(threshold=3).fit_transform(iris.data)

特征的离散化

在进行建模时,变量中经常会有一些变量为离散型变量,例如性别。这些变量我们一般无法直接放到模型中去训练模型。因此在使用之前,我们往往会对此类变量进行处理。一般是对离散变量进行one-hot编码。下面具体介绍通过python对离散变量进行one-hot的方法。


离散化是把连续型的数值型特征分段,每一段内的数据都可以当做成一个新的特征。具体又可分为等步长方式离散化和等频率的方式离散化,等步长的方式比较简单,等频率的方式更加精准,会跟数据分布有很大的关系。 代码层面,可以用pandas中的cut方法进行切分。总之,离散化的特征能够提高模型的运行速度以及准确率。


比方说年龄特征是一个连续的特征,但是把年龄层分成5-18岁(中小学生),19-23岁(大学生),24-29岁(工作前几年),30-40岁(成家立业),40-60岁(中年人)从某些层面来说比连续的年龄数据(比如说某人年龄是20岁1月3日之类的)更容易理解不同年龄层人的特性。典型的离散化步骤:对特征做排序-> 选择合适的分割点-> 作出区间的分割 -> 作出区间分割-> 查看是否能够达到停止条件。
分桶
1.离散化的常用方法是分桶:

2.分桶的数量和边界通常需要人工指定。一般有两种方法:
根据业务领域的经验来指定。如:对年收入进行分桶时,根据 2017 年全国居民人均可支配收入约为 2.6 万元,可以选择桶的数量为5。其中:
收入小于 1.3 万元(人均的 0.5 倍),则为分桶 0 。
年收入在 1.3 万元 ~5.2 万元(人均的 0.5~2 倍),则为分桶 1 。
年收入在 5.3 万元~26 万元(人均的 2 倍~10 倍),则为分桶 2 。
年收入在 26 万元~260 万元(人均的 10 倍~100 倍),则为分桶 3 。
年收入超过 260 万元,则为分桶 4 。
根据模型指定。根据具体任务来训练分桶之后的数据集,通过超参数搜索来确定最优的分桶数量和分桶边界。
3.选择分桶大小时,有一些经验指导:分桶大小必须足够小,使得桶内的属性取值变化对样本标记的影响基本在一个不大的范围。即不能出现这样的情况:单个分桶的内部,样本标记输出变化很大。分桶大小必须足够大,使每个桶内都有足够的样本。如果桶内样本太少,则随机性太大,不具有统计意义上的说服力。每个桶内的样本尽量分布均匀。
离散特征的两种数据类型
离散特征的取值之间有大小的意义:例如:尺寸(L、XL、XXL)
离散特征的取值之间没有大小的意义:例如:颜色(Red、Blue、Green)
离散特征值有大小意义的虚拟变量处理

  • 离散特征的取值之间有大小意义的处理函数,我们只需要把大小值以字典的方式,作为第一个参数传入即可;
  • (1) dict
  • 映射的字典pandas.Series.map(dict)

离散特征值没有大小意义的虚拟变量处理
离散特征的取值之间没有大小意义的处理方法,我们可以使用get_dummies方法处理,它有6个常用的参数

  • (1) data 要处理的DataFrame
  • (2) prefix 列名的前缀,在多个列有相同的离散项时候使用
  • (3) prefix_sep 前缀和离散值的分隔符,默认为下划线,默认即可
  • (4) dummy_na 是否把NA值,作为一个离散值进行处理,默认不处理
  • (5) columns 要处理的列名,如果不指定该列,那么默认处理所有列
  • (6) drop_first 是否从备选项中删第一个,建模的时候为避免共线性使用pandas.getdummies(data,prefix=None,prefix_sep=’‘,dummy_na=False,columns=None,drop_first=False)

举例:

1
2
3
4
5
6
import pandas
#有些朋友也可能是encoding='utf8'或其他
data=pandas.read_csv(
'file:///Users/apple/Desktop/jacky_1.csv',encoding='GBK'
)
print(data)


其实,虚拟变量的实质就是要把离散型的数据转化为连续型的数据,因为第1列年龄已经是连续值,所以我们就不需要处理了。
我们看看如何处理学历和性别?
因为不同学历之间是有高低之分的,因此我们要使用Map方法来处理这种类型的离散型数据;

第1步: 首先我们要处理不同学历之间的大小值我们使用drop_duplicates方法,来看看数据列都有哪些学历

1
2
#查看学历去重之后的情况
data['学历'].drop_duplicates()


第2步:理解数据值背后的意义,作出我们自己的解析,对每个学历进行评分#构建学历字典

1
2
3
educationLevelDict={'博士':4,'硕士':3,'大学':2,'大专':1}
#调用Map方法进行虚拟变量的转换
data['Education Level Map']=data['Education Level'].map(educationLevelDict)


第3步 对于性别这种没有大小比较的离散变量,我们使用get_dummies方法,来进行调用处理即可;

1
2
3
4
5
6
7
dummies=pandas.get_dummies(
data,
columns=['性别'],
prefix=['性别'],
prefix_sep='_',
dummy_na=False,
drop_first=False)



sklearn 处理离散变量

  • preprocessing.LabelEncoder:用于标签,将分类转换为分类数值 一般就是一维的
  • preprocessing.OrdinalEncoder :特征专用,要求至少为二维矩阵
  • preprocessing.OneHotEncoder
    虽然上面已经将文字类型转换成了数值类型,但是考虑下舱门这特征Embarked(S,C,Q),表示成了[0,1,2]这样合适吗?这三个数字在算法看来,是连续且可以计算的,这三个数字相互不等,有大小,并且有着可以相加相乘的联系。所以算法会把舱门,学历这样的分类特征,都误会成是体重这样的分类特征。这是说,我们把分类转换成数字的时候,忽略了数字中自带的数学性质,所以给算法传达了一些不准确的信息,而这会影响我们的建模。
    类别OrdinalEncoder可以用来处理有序变量,但对于名义变量,我们只有使用哑变量的方式来处理,才能够尽量向算法传达最准确的信息:

1
2
3
4
5
6
7
#独热编码  创建哑变量
from sklearn.preprocessing import OneHotEncoder
data_ = data.copy()
encoder=OneHotEncoder()
encoder.fit(data_.iloc[:,1:-1])
res=encoder.transform(data_.iloc[:,1:-1]).toarray()#这里要转换成数组,否则得到的是一个对象
res
  • scikit DictVectorizer 热编码(只处理类别型变量)
1
2
3
4
5
6
7
8
9
10
11
12
# 数据预处理
df.transpose().to_dict().values()
feature = df.iloc[:, :-1]
feature


# 热编码
from sklearn.feature_extraction import DictVectorizer
dvec = DictVectorizer(sparse=False)

X = dvec.fit_transform(feature.transpose().to_dict().values())
pd.DataFrame(X, columns=dvec.get_feature_names())



哑编码

我们针对类别型的特征,通常采用哑编码(One_Hot Encodin)的方式。所谓的哑编码,直观的讲就是用N个维度来对N个类别进行编码,并且对于每个类别,只有一个维度有效,记作数字1 ;其它维度均记作数字0。但有时使用哑编码的方式,可能会造成维度的灾难,所以通常我们在做哑编码之前,会先对特征进行Hash处理,把每个维度的特征编码成词向量。


以上为大家介绍了几种较为常见、通用的数据预处理方式,但只是浩大特征工程中的冰山一角。往往很多特征工程的方法需要我们在项目中不断去总结积累比如:针对缺失值的处理,在不同的数据集中,用均值填充、中位数填充、前后值填充的效果是不一样的;


对于类别型的变量,有时我们不需要对全部的数据都进行哑编码处理;对于时间型的变量有时我们有时会把它当作是离散值,有时会当成连续值处理等。所以很多情况下,我们要根据实际问题,进行不同的数据预处理。
参考:
https://mp.weixin.qq.com/s/BnTXjzHSb5-4s0O0WuZYlg