数据, 术→技巧

机器学习/数据分析之缺失值处理

钱魏Way · · 722 次浏览

在机器学习数据预处理阶段经常需要对数据进行缺失值处理。关于缺失值的处理并没有想象中的那么简单。以下为一些经验分享。

数据缺失类型

  • 完全随机丢失(MCAR,Missing Completely at Random):某个变量是否缺失与它自身的值无关,也与其他任何变量的值无关。例如,由于测量设备出故障导致某些值缺失。
  • 随机丢失(MAR,Missing at Random): 在控制了其他变量已观测到的值后,某个变量是否缺失与它自身的值无关。例如,人们是否透露收入可能与性别、教育程度、职业等因素有关系。
  • 非随机丢失(MNAR,Missing not at Random):数据的缺失与不完全变量自身的取值有关。分为两种情况:缺失值取决于其假设值(例如,高收入人群通常不希望在调查中透露他们的收入);或者,缺失值取决于其他变量值(假设女性通常不想透露她们的年龄,则这里年龄变量缺失值受性别变量的影响)。

在前两种情况下可以根据其出现情况删除缺失值的数据,同时,随机缺失可以通过已知变量对缺失值进行估计。在第三种情况下,删除包含缺失值的数据可能会导致模型出现偏差,同时,对数据进行填充也需要格外谨慎。正确判断缺失值的类型,能给我们的工作带来很大的便利,但目前还没有一套规范的缺失值类型判定标准。大多是依据经验或业务进行判断。

缺失值处理

解决缺失数据问题的方法主要有:成列删除、成对删除、虚拟变量调整、插补、多重插补和最大似然等。

缺失值统计

missing_value_formats = ["n.a.","?","NA","n/a", "na", "--"]
df = pd.read_csv("employees.csv", na_values = missing_value_formats)
df.isna().sum()

缺失值删除

成列删除(listwise deletion)

常见的操作方法:删除掉所有存在缺失值的个案。

优点:

  • 可用于任何类型的统计分析。
  • 不需要特别的运算方法。
  • 如果数据是MCAR,则减少的样本将会是原样本的一个随机次样本。
  • 如果任何因变量缺失数据的概率不取决于自变量的值,则使用成列删除的回归估计值将会是无偏误的。

缺点:

  • 标准误通常较大。
  • 如果数据不是MCAR而只是MAR,那么成列删除可能会产生有偏误的估计值。(例如,教育缺失数据的概率取决于职业地位,那么对于二者的回归会产生一个有偏误的回归系数估计值。)

成对删除(pairwise deletion)

一般的备选方案,在进行多变量的联立时,只删除掉需要执行的变量的缺失数据。例如在ABC三个变量间,需要计算A和C的协方差,那么只有同时具备A/C的数据会被使用。文献指出,当变量间的相关性普遍较低时,成对删除会产生更有效的估计值。然而当变量间的相关性较高时,建议还是使用成列删除。理论上成对删除不建议作为成列删除的备选方案。这是一种保守的处理方法,最大限度地保留了数据集中的可用信息。

优点:如果数据为MCAR,成对删除就产生一致的参数估计值(在大样本中接近无偏误),且有比成列删除更少的抽样变异(较小的真实标准误),而当变量间相关性普遍较低时,成对删除会产生更有效的估计值。

缺点:

  • 如果数据是MAR但不是随机被观察到的,估计值可能会严重偏误。
  • 由统计软件所产生的标准误和检验统计量估计时偏误的。
  • 在小样本中,建构的协方差或相关矩阵可能不是“正定的”。

Pandas中的dropna()方法

DataFrame.dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)

参数说明:

  • axis:轴。0或’index’,表示按行删除;1或’columns’,表示按列删除。
  • how:筛选方式。‘any’,表示该行/列只要有一个以上的空值,就删除该行/列;‘all’,表示该行/列全部都为空值,就删除该行/列。
  • thresh:非空元素最低数量。int型,默认为None。如果该行/列中,非空元素数量小于这个值,就删除该行/列。
  • subset:子集。列表,元素为行或者列的索引。如果axis=0或者‘index’,subset中元素为列的索引;如果axis=1或者‘column’,subset中元素为行的索引。由subset限制的子区域,是判断是否删除该行/列的条件判断区域。
  • inplace:是否原地替换。布尔值,默认为False。如果为True,则在原DataFrame上进行操作,返回值为None。

使用示例:

# 删除所有含空的行
df.dropna(inplace=True)

# 删除某列含控制的行
df.dropna(subset=['列名'],inplace=True)

虚拟变量调整(哑变量,dummy variables)

新建两个变量,其中一个变量D为“是否缺失”,缺失值设为0,存在值设为1。

另一个变量X’,将缺失值设为c(可以是任何常数),存在值设为本身。随后,对X’,D和其他变量(因变量和其他预设模型中的自变量)进行回归。这种调整的好处是它利用了所有可用的缺失数据的信息(是否缺失)。为了便利,一个好的c的设置方式是现有非缺失数据X的均数。这样做的好处是,D的系数可以被解释成“在控制了其他变量的情况下,X具缺失数据的个体其Y的预测值减去具X平均数的个体于Y的预测值”

缺失值插补

相对丢弃而言,补全是更加常用的缺失值处理方式。通过一定的方法将缺失的数据补上,从而形成完整的数据记录,对于后续的数据处理、分析和建模至关重要。常用的补全方法如下。

  • 统计法:对于数值型的数据,使用均值、加权均值、中位数等方法补足;对于分类型数据,使用类别众数最多的值补足。
  • 模型法:更多时候我们会基于已有的其他字段,将缺失字段作为目标变量进行预测,从而得到最为可能的补全值。如果带有缺失值的列是数值变量,采用回归模型补全;如果是分类变量,则采用分类模型补全。
  • 专家补全:对于少量且具有重要意义的数据记录,专家补足也是非常重要的一种途径。
  • 其他方法:例如随机法、特殊值法、多重填补等。

人工填写(filling manually)

当你对自己手头的数据集足够了解时,可以选择自己填写缺失值。

特殊值填充(Treating Missing Attribute values as Special values)

将空值作为一种特殊的属性值来处理,它不同于其他的任何属性值。如所有的空值都用“unknown”填充。一般作为临时填充或中间过程。有时可能导致严重的数据偏离,一般不推荐。

平均值填充(Mean/Mode Completer)

将初始数据集中的属性分为数值属性和非数值属性来分别进行处理。

如果空值是数值型的,就根据该属性在其他所有对象的取值的平均值来填充该缺失的属性值; 如果空值是非数值型的,就根据统计学中的众数原理,用该属性在其他所有对象的取值次数最多的值(即出现频率最高的值)来补齐该缺失的属性值。

与其相似的另一种方法叫条件平均值填充法(Conditional Mean Completer)。在该方法中,用于求平均的值并不是从数据集的所有对象中取,而是从与该对象具有相同决策属性值的对象中取得。这两种数据的补齐方法,其基本的出发点都是一样的,以最大概率可能的取值来补充缺失的属性值,只是在具体方法上有一点不同。与其他方法相比,它是用现存数据的多数信息来推测缺失值。

热卡填充(Hot deck imputation,或就近补齐)

对于一个包含空值的对象,热卡填充法在完整数据中找到一个与它最相似的对象,然后用这个相似对象的值来进行填充。不同的问题可能会选用不同的标准来对相似进行判定。该方法概念上很简单,且利用了数据间的关系来进行空值估计。这个方法的缺点在于难以定义相似标准,主观因素较多。

聚类填充(clustering imputation)

最为典型的代表是K均值(K-means clustering),先根据欧式距离或相关分析来确定距离具有缺失数据样本最近的K个样本,将这K个值加权平均来估计该样本的缺失数据。

同均值插补的方法都属于单值插补,不同的是,它用层次聚类模型预测缺失变量的类型,再以该类型的均值插补。

假设X=(X1,X2…Xp)为信息完全的变量,Y为存在缺失值的变量,那么首先对X或其子集行聚类,然后按缺失个案所属类来插补不同类的均值。如果在以后统计分析中还需以引入的解释变量和Y做分析,那么这种插补方法将在模型中引入自相关,给分析造成障碍。

K最近距离邻法(K-means clustering)

先根据欧式距离或相关分析来确定距离具有缺失数据样本最近的K个样本,将这K个值加权平均来估计该样本的缺失数据。这个方法要求我们选择k的值(最近邻居的数量),以及距离度量。KNN既可以预测离散属性(k近邻中最常见的值)也可以预测连续属性(k近邻的均值)。

根据数据类型的不同,距离度量也不尽相同:

  • 连续数据:最常用的距离度量有欧氏距离,曼哈顿距离以及余弦距离。
  • 分类数据:汉明(Hamming)距离在这种情况比较常用。对于所有分类属性的取值,如果两个数据点的值不同,则距离加一。汉明距离实际上与属性间不同取值的数量一致。

KNN算法最吸引人的特点之一在于,它易于理解也易于实现。其非参数的特性在某些数据非常“不寻常”的情况下非常有优势。

KNN算法的一个明显缺点是,在分析大型数据集时会变得非常耗时,因为它会在整个数据集中搜索相似数据点。此外,在高维数据集中,最近与最远邻居之间的差别非常小,因此KNN的准确性会降低。

使用所有可能的值填充(Assigning All Possible values of the Attribute)

这种方法是用空缺属性值的所有可能的属性取值来填充,能够得到较好的补齐效果。但是,当数据量很大或者遗漏的属性值较多时,其计算的代价很大,可能的测试方案很多。一般不推荐。

另有一种方法,填补遗漏属性值的原则是一样的,不同的只是从决策相同的对象中尝试所有的属性值的可能情况,而不是根据信息表中所有对象进行尝试,这样能够在一定程度上减小原方法的代价。

组合完整化方法(Combinatorial Completer)

这种方法是用空缺属性值的所有可能的属性取值来试,并从最终属性的约简结果中选择最好的一个作为填补的属性值。这是以约简为目的的数据补齐方法,能够得到好的约简结果;但是,当数据量很大或者遗漏的属性值较多时,其计算的代价很大。

另一种称为条件组合完整化方法(Conditional Combinatorial Complete),填补遗漏属性值的原则是一样的,不同的只是从决策相同的对象中尝试所有的属性值的可能情况,而不是根据信息表中所有对象进行尝试。条件组合完整化方法能够在一定程度上减小组合完整化方法的代价。在信息表包含不完整数据较多的情况下,可能的测试方案将巨增。

回归(Regression)

基于完整的数据集,建立回归方程,或利用机器学习中的回归算法。对于包含空值的对象,将已知属性值代入方程来估计未知属性值,以此估计值来进行填充。当变量不是线性相关时会导致有偏差的估计。较常用。但是要注意防止过拟合。

期望值最大化方法(Expectation maximization,EM)

EM算法是一种在不完全数据情况下计算极大似然估计或者后验分布的迭代算法。在每一迭代循环过程中交替执行两个步骤:

  • E步(Excepctaion step,期望步),在给定完全数据和前一次迭代所得到的参数估计的情况下计算完全数据对应的对数似然函数的条件期望
  • M步(Maximzation step,极大化步),用极大化对数似然函数以确定参数的值,并用于下步的迭代。

算法在E步和M步之间不断迭代直至收敛,即两次迭代之间的参数变化小于一个预先给定的阈值时结束。该方法可能会陷入局部极值,收敛速度也不是很快,并且计算很复杂。

极大似然估计(Max Likelihood ,ML)

在缺失类型为随机缺失的条件下,假设模型对于完整的样本是正确的,那么通过观测数据的边际分布可以对未知参数进行极大似然估计(Little and Rubin)。这种方法也被称为忽略缺失值的极大似然估计,对于极大似然的参数估计实际中常采用的计算方法是期望值最大化(Expectation Maximization,EM)。

该方法比删除个案和单值插补更有吸引力,它一个重要前提:适用于大样本。有效样本的数量足够以保证ML估计值是渐近无偏的并服从正态分布。但是这种方法可能会陷入局部极值,收敛速度也不是很快,并且计算很复杂。

多重插补(Multiple Imputation,MI)

多值插补的思想来源于贝叶斯估计,认为待插补的值是随机的,它的值来自于已观测到的值。具体实践上通常是估计出待插补的值,然后再加上不同的噪声,形成多组可选插补值。根据某种选择依据,选取最合适的插补值。

多重插补方法分为三个步骤:

  1. 为每个空值产生一套可能的插补值,这些值反映了无响应模型的不确定性;每个值都可以被用来插补数据集中的缺失值,产生若干个完整数据集合。
  2. 每个插补数据集合都用针对完整数据集的统计方法进行统计分析。
  3. 对来自各个插补数据集的结果,根据评分函数进行选择,产生最终的插补值。

多重插补和贝叶斯估计的思想是一致的,但是多重插补弥补了贝叶斯估计的几个不足。

  • 贝叶斯估计以极大似然的方法估计,极大似然的方法要求模型的形式必须准确,如果参数形式不正确,将得到错误得结论,即先验分布将影响后验分布的准确性。而多重插补所依据的是大样本渐近完整的数据的理论,在数据挖掘中的数据量都很大,先验分布将极小的影响结果,所以先验分布的对结果的影响不大。
  • 贝叶斯估计仅要求知道未知参数的先验分布,没有利用与参数的关系。而多重插补对参数的联合分布作出了估计,利用了参数间的相互关系。

同时,多重插补保持了单一插补的两个基本优点,即应用完全数据分析方法和融合数据收集者知识的能力。相对于单一插补,多重插补有三个极其重要的优点:

  • 为表现数据分布,随机抽取进行插补,增加了估计的有效性。
  • 当多重插补是在某个模型下的随机抽样时,按一种直接方式简单融合完全数据推断得出有效推断,即它反映了在该模型下由缺失值导致的附加变异。
  • 在多个模型下通过随机抽取进行插补,简单地应用完全数据方法,可以对无回答的不同模型下推断的敏感性进行直接研究。

多重插补也有以下缺点:

  • 生成多重插补比单一插补需要更多工作
  • 贮存多重插补数据集需要更多存储空间
  • 分析多重插补数据集比单一插补需要花费更多精力

C4.5方法

通过寻找属性间的关系来对遗失值填充。它寻找之间具有最大相关性的两个属性,其中没有遗失值的一个称为代理属性,另一个称为原始属性,用代理属性决定原始属性中的遗失值。这种基于规则归纳的方法只能处理基数较小的名词型属性。

就几种基于统计的方法而言,删除元组法和平均值法差于热卡填充法、期望值最大化方法和多重填充法;回归是比较好的一种方法,但仍比不上hot deck和EM;EM缺少MI包含的不确定成分。值得注意的是,这些方法直接处理的是模型参数的估计而不是空缺值预测本身。它们合适于处理无监督学习的问题,而对有监督学习来说,情况就不尽相同了。譬如,你可以删除包含空值的对象用完整的数据集来进行训练,但预测时你却不能忽略包含空值的对象。另外,C4.5和使用所有可能的值填充方法也有较好的补齐效果,人工填写和特殊值填充则是一般不推荐使用的。

Pandas中的fillna方法

DataFrame.fillna(value=None,method=None,axis=None,inplace=False,limit=None,downcast=None,**kwargs)

参数说明:

  • value:接收常数、dict、Series 或 DataFrame,表示填充缺失值的值。
  • method:表示填充缺失值的方法,method 的取值为{’pad’,’ffill’,’backfill’,’bfill’,None}。pad/ffill:用前一个非缺失值去填充该缺失值。backfill/bfill:用下一个非缺失值去填充该缺失值。None:指定一个值去替换缺失值(缺省默认这种方式)。
  • axis:指定填充方向,当 axis=1 按列填充,axis=0 按行填充。
  • inplace:接收 True 或 False。True 表示直接修改原对象,False 表示创建一个副本,修改副本,原对象不变,默认为 False。
  • limit:表示限制填充的个数,如果 limit=2,则只填充两个缺失值。
  • downcast:默认为 None,如果需要将填充的值向下转换为适当的相等数据类型的数值,如将 float64 数据类型转换为 int64 数据类型时,则此参数的值为 ‘infer’。

代码示例:

# 用常量0填充
df1 = df.fillna(0)

# 用字典填充
df2 = df.fillna({'Normal':60,'exam':40})

# 指定method = 'ffill'/'pad':用前一个非缺失值去填充该缺失值
df2 = df.fillna(method='ffill')

# 将exam列的缺失值用均值替换
exa_mea = df['exam'].fillna(df['exam'].mean())

# 将Normal列的缺失值用中位数替换
Nor_med = df['Normal'].fillna(df['Normal'].median())

# 使众数(mode)填充
df['Normal'].fillna(df['Normal'].mode())

# 使用随机方式填充
df["column"].fillna(lambda x: random.choice(df[df[column] != np.nan]["column"]), inplace =True)

Pandas中的interpolate()方法

Series 和 DataFrame 对象都有interpolate()方法,默认情况下,该函数在缺失值处执行线性插值。这个方法是利用数学方法来估计缺失点的值,对于较大的数据非常有用。

DataFrame.interpolate(self, method='linear', axis=0, limit=None, inplace=False, limit_direction='forward', limit_area=None, downcast=None, **kwargs)

参数说明:

  • method :str,默认为linear,差值方法可选参数:
    • linear:忽略索引,并将值等距地对待。这是MultiIndexes支持的唯一方法。
    • time:处理每日和更高分辨率的数据,以内插给定的时间间隔长度。
    • index,values:索引,值,使用索引的实际数值
    • pad:使用现有值填写NaN。
    • nearest, zero, slinear, quadratic, cubic, spline, barycentric, polynomial:传递给interpolate.interp1d,这些方法使用索引的数值。polynomial 和 spline都要求您还指定一个顺序(int),例如 df.interpolate(method=’polynomial’,order=5)
      • nearest:最近
      • zero:零
      • slinear:线性
      • quadratic:二次方
      • cubic:立方
      • spline:花键,样条插值
      • barycentric:重心插值
      • polynomial:多项式
    • krogh, piecewise_polynomial, spline, pchip, akima: SciPy 类似名称的插值方法。
      • krogh: 克罗格插值
      • piecewise_polynomial: 分段多项式
      • spline: 样条插值
      • pchip: 立方插值
      • akima: 阿克玛插值
    • from_derivatives:指interpolate.BPoly.from_derivatives,它替换了 scipy 0.18 中的 piecewise_polynomial 插值方法。
    • axis: 插值应用的轴方向,可选择 {0 or index, 1 or columns, None}, 默认为 None
    • limitint: 要填充的连续 NaN 的最大数量, 必须大于 0。
    • inplace: 是否将最终结果替换原数据,默认为 False
    • limit_direction: 限制方向,可传入 {forward, backward, both}, 默认forward,如果指定了限制,则将沿该方向填充连续的 NaN
    • limit_area: 限制区域,可传入 {None, inside, outside}, 默认 None,如果指定了限制,则连续的NaN将被此限制填充
    • None: 没有填充限制
      • inside: 仅填充有效值包围的NaN(内插)
      • outside: 仅将NaN填充到有效值之外(外推)
    • downcast: 可传入‘infer’ 或者 None, 默认是 None,如果可以向下转换 dtypes
    • **kwargs: 传递给插值函数的关键字参数

常用的有以下几种方法:

  • 邻近点插值(method=’nearest’)。
  • 线性插值(method=’linear’):在两个数据点之间连接直线,计算给定的插值点在直线上的值作为插值结果,该方法是interp1函数的默认方法。
  • 三次样条插值(method=’spline’):通过数据点拟合出三次样条曲线,计算给定的插值点在曲线上的值作为插值结果。
  • 立方插值(method=’pchip’ or ‘cubic’):通过分段立方Hermite插值方法计算插值结果。

选择一种插值方法时,考虑的因素包括运算时间、占用计算机内存和插值的光滑程度。一般来说:

  • 邻近点插值方法的速度最快,但平滑性最差;
  • 线性插值方法占用的内存较邻近点插值方法多,运算时间也稍长,与邻近点插值不同,其结果是连续的,但顶点处的斜率会改变;
  • 三次样条插值方法的运算时间最长,但内存的占用较立方插值法要少,但其插值数据和导数都是连续的。在这4种方法中,三次样条插值结果的平滑性最好,但如果输入数据不一致或数据点过近,就可能出现很差的插值效果。

随机森林填补法

任何回归都是从特征矩阵中学习,然后求解连续性标签y的值,之所以能实现这个过程,是因为回归算法认为,特征矩阵和标签之前存在着某种关系,实际上特征和标签是可以相互转化的,比如说用地区,环境,附近学校数量预测房价的问题,我们既可以用地区,环境,附近学校数量的数据来预测房价,也可以反过来,用环境,附近学校数量和房价来预测地区,而回归填补缺失值,正式利用了这种情况。

def fill_dependents_missing(data, to_fill):
    df = data.copy()
    columns = [*df.columns]
    columns.remove(to_fill)
    
    X = df.loc[:, columns]
    y = df.loc[:, to_fill]
    X_train = X.loc[df[to_fill].notnull()]
    y_train = y.loc[df[to_fill].notnull()]
    X_pred = X.loc[df[to_fill].isnull()]
    rfr = RandomForestRegressor(random_state=22, n_estimators=200, max_depth=3, n_jobs=-1)
    rfr.fit(X_train, y_train)
    y_pred = rfr.predict(X_pred).round()
    df.loc[df[to_fill].isnull(), to_fill] = y_pred
    return df

不处理缺失值

在数据预处理阶段,对于具有缺失值的数据记录不做任何处理,也是一种思路。这种思路主要看后期的数据分析和建模应用,很多模型对于缺失值有容忍度或灵活的处理方法,因此在预处理阶段可以不做处理。

常见的能够自动处理缺失值的模型包括:KNN、决策树和随机森林、神经网络和朴素贝叶斯、DBSCAN(基于密度的带有噪声的空间聚类)等。这些模型对于缺失值的处理思路是:

  • 忽略,缺失值不参与距离计算,例如KNN。
  • 将缺失值作为分布的一种状态,并参与到建模过程,例如各种决策树及其变体。
  • 不基于距离做计算,因此基于值的距离做计算本身的影响就消除了,例如DBSCAN。

在数据建模前的数据归约阶段,有一种归约的思路是降维,降维中有一种直接选择特征的方法。假如我们通过一定方法确定带有缺失值(无论缺少字段的值缺失数量有多少)的字段对于模型的影响非常小,那么我们根本就不需要对缺失值进行处理。

因此,后期建模时的字段或特征的重要性判断也是决定是否处理字段缺失值的重要参考因素之一。

对于缺失值的处理思路是先通过一定方法找到缺失值,接着分析缺失值在整体样本中的分布占比,以及缺失值是否具有显著的无规律分布特征,然后考虑后续要使用的模型中是否能满足缺失值的自动处理,最后决定采用哪种缺失值处理方法。

参考链接:

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注