数据缺失处理的一些技术

本文参考自:Kaggle
数据亦来源该页

样本中的数据缺失是ML界非常常见的问题,本文就这问题进行讨论。

使用 MissingNo 查看数据

import pandas as pd
import numpy as np
import tensorflow as tfimport missingno as msno

df = pd.read_csv('../../datasets/recipeData.csv', encoding='latin-1').set_index("BeerID")

MissingNo是常用的观察缺失数据的库,包含了多种图可以观测缺失数据情况。

msno.matrix(df)

image

msno.bar(df)

image

drop

处理缺失数据的一个最简单思路就是把缺失的行drop掉,对于非常小样本的缺失可以这么做。
可以用dropna()丢掉缺失行

len(df), len(df.dropna())

(73861, 757)

可以看到对于本数据,dropna会丢掉绝大多数行,并不适用。
还有一种思路,对于数据缺失非常严重的feature,往往不再具有参考意义,可以drop掉

df.drop(['PrimingMethod', 'PrimingAmount'], axis='columns')

简单插补

简单的数据插补有均值插补,众数插补,pandas自带的方法就能很简单地实现。

df['MashThickness'].isnull().sum(), df['MashThickness'].fillna(df['MashThickness'].mean()).isnull().sum()

df['MashThickness'].mean(), df['MashThickness'].fillna(df['MashThickness'].mean()).mean()

除此之外,sklearn.preprocessing也带了简单的插补方法。

from sklearn.preprocessing import Imputer 
imputer = Imputer(strategy="median")
imputer.fit(df)

sklearn提供了SimpleImputer,impute.IterativeImputer,MissingIndicator(用于二进制),KNNImputer4种插补模型

模型插补

数据用于机器学习,而机器学习同样可以用于数据填补。
!如果我们以缺失数据的列作为目标变量,以完整数据的现有列作为预测变量,那么我们可以以完整记录作为训练和测试数据集,以不完整数据的记录作为泛化目标,构建一个机器学习模型。这是一个完全确定的机器学习问题。

用Linear Regression做该数据的插补:

# Format the data for applying ML to it.
popular_beer_styles = (pd.get_dummies(df['Style']).sum(axis='rows') > (len(df) / 100)).where(lambda v: v).dropna().index.values

dfc = (df
       .drop(['PrimingMethod', 'PrimingAmount', 'UserId', 'PitchRate', 'PrimaryTemp', 'StyleID', 'Name', 'URL'], axis='columns')
       .dropna(subset=['BoilGravity'])
       .pipe(lambda df: df.join(pd.get_dummies(df['BrewMethod'], prefix='BrewMethod')))
       .pipe(lambda df: df.join(pd.get_dummies(df['SugarScale'], prefix='SugarScale')))       
       .pipe(lambda df: df.assign(Style=df['Style'].map(lambda s: s if s in popular_beer_styles else 'Other')))
       .pipe(lambda df: df.join(pd.get_dummies(df['Style'], prefix='Style')))       
       .drop(['BrewMethod', 'SugarScale', 'Style'], axis='columns')
      )

c = [c for c in dfc.columns if c != 'MashThickness']
X = dfc[dfc['MashThickness'].notnull()].loc[:, c].values
y = dfc[dfc['MashThickness'].notnull()]['MashThickness'].values
yy = dfc[dfc['MashThickness'].isnull()]['MashThickness'].values

# Apply a regression approach to imputing the mash thickness.
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold
from sklearn.metrics import r2_score

import numpy as np
np.random.seed(42)
kf = KFold(n_splits=4)
scores = []
for train_index, test_index in kf.split(X):
    X_train, X_test = X[train_index], X[test_index]
    y_train, y_test = y[train_index], y[test_index]
    
    clf = LinearRegression()
    clf.fit(X_train, y_train)
    y_test_pred = clf.predict(X_test)
    
    scores.append(r2_score(y_test, y_test_pred))

print(scores)

[0.012942533393265787, 0.009359944248067964, 0.00924054218885928, -0.00039579117668386843]

其中r2_score是一个可以评价Linear Regression较baseline的拟合效果的评价指标,其绝对值约接近于0代表拟合效果更好。

Maximum Likelihood Estimator(MLE)

简单的方法容易实现,但可能导致高偏差。模型imputation方法更有挑战性,但它仍然是现成的,它在向数据集引入偏差方面仍然存在问题。

在统计学中,Maximum Likelihood Estimator是对感兴趣的分布的任何统计估计器,其特性是使该数据的“似然函数”最大化。

回想一下,统计估计值取随机样本数据,并试图通过从样本中一般化来解释总体分布。例如,∑(y)/len(y)是一组数据y平均值的估计值。它是MLE,因为它没有任何偏差:它收敛于分布的真实均值(给定足够多的样本)。对于大多数问题,MLE估计器是构建最简单的估计器。但有时MLE估计器是不可能的,在其他情况下,估计器中的一些偏差是有用的(如果你知道一些模型不知道的东西;见如正则化)。

最大似然估计是对缺失数据进行的最大似然估计。首先,用数据集中的完整记录作为预测变量,用包含缺失值的变量作为目标,构建一个最大似然估计器。然后,对于每个包含缺失数据的记录,从您生成的分布中提取一个值,该值使用数据的已知依赖值进行参数化。

这种解决这个问题的纯统计方法在更普遍的情况下具有统计模型的缺点,因为它依赖于你在估计器中使用的概率分布。如果你期望数据是正态分布,你可能会拟合一个正态分布的数据。如果是伯努利分布,你可以拟合伯努利分布。

MultiImpute - MICE

上述讨论的插补都是"SimpleImpute",而Impute可以用Multiple Impute的思路,融合多个插补器达到更好的Impute效果。
其中一个广泛使用的方法称为MICE,以下是它的执行方法:

  • 对数据集中每一个缺失的值进行简单的估算,例如估算平均值。这些平均imputation可以被认为是“占位符”。

  • “占位符”对一个变量(“var”)的平均imputation设置为"Missing"。

  • 将步骤2中变量var的观测值对归算模型中的其他变量进行回归,该模型可能包含也可能不包含数据集中的所有变量。也就是说,“var”是回归模型中的因变量,其他变量都是回归模型中的自变量。

  • 然后,“var”的缺失值被回归模型的预测(imputation)所取代。当“var”随后被用作其他变量回归模型中的自变量时,观察值和这些估算值都将被使用。

  • 然后对每个缺少数据的变量重复步骤2-4。通过每个变量的循环构成了一次迭代或“循环”。在一个周期结束时,所有缺失的值都被反映数据中观察到的关系的回归预测所取代。

  • 步骤2到步骤4重复若干个周期,每一个周期都更新估算。在这些循环结束时,保留最后的imputation,得到一个imputation数据集。一般来说10个周期;但对于不同条件下的数据输入,如何确定最优循环数还需要研究。这个想法是,在周期结束时,控制imputations的参数分布(例如,回归模型中的系数)应该在趋于稳定的意义上趋于收敛。

fancyimpute

fancyimpute是一个可以用于插补的库,包含了均值,众数,KNN, MICE等多种插补方法。

from fancyimpute import KNN, NuclearNormMinimization, SoftImpute, BiScaler

# X is the complete data matrix
# X_incomplete has the same values as X except a subset have been replace with NaN

# Use 3 nearest rows which have a feature to fill in each row's missing features
X_filled_knn = KNN(k=3).fit_transform(X_incomplete)

# matrix completion using convex optimization to find low-rank solution
# that still matches observed values. Slow!
X_filled_nnm = NuclearNormMinimization().fit_transform(X_incomplete)

# Instead of solving the nuclear norm objective directly, instead
# induce sparsity using singular value thresholding
X_incomplete_normalized = BiScaler().fit_transform(X_incomplete)
X_filled_softimpute = SoftImpute().fit_transform(X_incomplete_normalized)

# print mean squared error for the  imputation methods above
nnm_mse = ((X_filled_nnm[missing_mask] - X[missing_mask]) ** 2).mean()
print("Nuclear norm minimization MSE: %f" % nnm_mse)

softImpute_mse = ((X_filled_softimpute[missing_mask] - X[missing_mask]) ** 2).mean()
print("SoftImpute MSE: %f" % softImpute_mse)

knn_mse = ((X_filled_knn[missing_mask] - X[missing_mask]) ** 2).mean()
print("knnImpute MSE: %f" % knn_mse)

在原文中,作者提到fancyimpute含有MICE这个classfrom fancyimpute import MICE,而在实际使用中发现fancyimpute并没有这个功能,查找资料后发现用IterativeImputer即实现了MICE

# Import IterativeImputer from fancyimpute
from fancyimpute import IterativeImputer

# Copy diabetes to diabetes_mice_imputed
diabetes_mice_imputed = diabetes.copy(deep=True)

# Initialize IterativeImputer
mice_imputer = IterativeImputer()

# Impute using fit_tranform on diabetes
diabetes_mice_imputed.iloc[:, :] = mice_imputer.fit_transform(diabetes)

fancyimpute中包含的插补算法:
(参考:https://blog.csdn.net/jingyi130705008/article/details/82796283)

  • KNN
    最近邻估算,使用两行都具有观测数据的特征的均方差来对样本进行加权。然后用加权的结果进行特征值填充

  • NuclearNormMinimization
    通过SVD分解的迭代软阈值处理来填充矩阵

  • SoftImpute
    通过迭代低秩SVD分解来填充矩阵。 原理类似于DNA微阵列缺失值估算方法中的SVDimpute。

  • IterativeImputer
    MICE,参考sklearn中的详解https://scikit-learn.org/stable/modules/impute.html#iterative-imputer

  • MatrixFactorization
    将不完全矩阵直接分解为低秩“U”和“V”,对“U”元素进行L1稀疏性惩罚,对“V”元素进行L2惩罚。 通过梯度下降解决。

  • NuclearNormMinimization
    矩阵填充使用凸优化来找到仍然匹配观测值的低秩解。对于大型矩阵来说非常缓慢。

  • BiScaler
    通过行/列均值和标准偏差的迭代估计得到双重归一化矩阵。 不保证收敛但在实践中运作良好。 基本原理请参考矩阵完成和低秩SVD通过快速交替最小二乘。

posted @ 2022-06-13 20:59  Asp1rant  阅读(402)  评论(0编辑  收藏  举报