数据集很少是完整的。缺失值,通常在pandas DataFrames等数据结构中表示为NaN(非数字)或类似占位符,是一种常见情况。它们可能源于多种原因:数据录入错误、传感器故障、问卷未响应或数据整合中的问题。忽略缺失数据通常是不可行的,因为大多数机器学习算法无法直接处理它们,需要完整的数据集。此外,我们如何处理这些空缺,会显著影响我们分析的质量和预测模型的性能。虽然用列的均值、中位数或众数填充缺失值这样的简单方法很直接(并且可能在入门学习中已经熟悉),但它们通常会过度简化数据结构,可能扭曲变量之间的关系或减少方差。本节考察处理缺失数据的更复杂策略,超越基本填充,探讨更能保持数据集完整性的方法。理解数据缺失的原因在选择策略之前,思考数据为何缺失是有益的,尽管这通常有难度。统计学家将数据缺失机制分为三种主要类型:完全随机缺失 (MCAR): 某个值缺失的概率与其它特征的观测值以及未观测的缺失值本身都无关。这是一种理想情况,但通常不切实际。如果数据确实是MCAR,删除含有缺失值的行可能不会引入偏差,尽管会减小数据集的大小。随机缺失 (MAR): 某个值缺失的概率仅取决于其他特征的观测值,而与缺失值本身无关。例如,如果男性比女性更少填写某个特定的调查问题,那么缺失情况就取决于观测到的“性别”特征。许多高级填充技术在MAR假设下表现良好。非随机缺失 (MNAR): 某个值缺失的概率取决于缺失值本身或其它未观测到的因素。例如,收入非常高的人可能不太愿意透露他们的收入。MNAR是最具挑战性的情况,因为简单地删除或填充数据可能引入明显偏差。处理MNAR通常需要专业知识或专门的模型方法。虽然明确判断数据缺失机制有难度,但思考潜在原因可以指导你选择处理策略。删除方法最简单的方法是移除含有缺失值的数据点。行删除法(完整案例分析)这涉及丢弃含有任何缺失值的整行(观测值)。优点: 易于实现。如果数据是MCAR,剩余数据集是完整案例的无偏样本。许多统计过程依赖于完整数据。缺点: 可能导致大量数据丢失,特别是当缺失值分散在许多列或数据集较小时。这会降低统计效力,并可能使模型效果不佳。如果数据不是MCAR,行删除法可能给分析引入偏差。谨慎使用行删除法,或许只在缺失数据比例很小(例如,<5%)并且你有充分理由相信它是MCAR时,或者当特定算法要求如此,且填充在该情境下被认为过于复杂或不可靠时才使用。# 使用pandas的例子 import pandas as pd import numpy as np data = {'A': [1, 2, np.nan, 4, 5], 'B': [6, np.nan, 8, 9, 10], 'C': [11, 12, 13, 14, 15]} df = pd.DataFrame(data) # 原始数据框: print("原始数据框:") print(df) # 行删除后的DataFrame: df_dropped = df.dropna() print("\n行删除后:") print(df_dropped)对删除法与删除整行不同,对删除法仅使用可用的数据进行特定计算(例如相关或协方差矩阵)。对于列X和Y之间的相关性,它使用X和Y都有非缺失值的所有行。这意味着不同的计算可能使用不同的数据子集。优点: 与行删除法相比,保留了更多数据。缺点: 可能导致不一致性(例如,非正半定的相关矩阵)。不直接适用于训练大多数需要固定行和列集的机器学习模型。主要用于特定的统计分析,而非典型的机器学习预处理流程。单值填充法单值填充法用一个估计值替换每个缺失值。均值/中位数/众数填充用列的均值或中位数替换数值型缺失值,用众数替换类别型缺失值,这是一个常见的起始方法。优点: 简单、快速、易于实现。缺点: 降低特征的方差。扭曲变量之间的关系(相关性、协方差)。忽略来自其他特征的信息。均值填充对异常值敏感(中位数更稳定)。回归填充在此,我们将具有缺失值的特征视为目标变量,并使用其他特征作为回归模型(例如,线性回归)中的预测变量。模型对缺失项的预测结果被用作填充值。优点: 使用来自其他特征的信息进行更明智的预测。与均值/中位数填充相比,能更好地保留变量之间的关系。缺点: 假设变量之间的关系可以被选定的回归模型(例如线性模型)很好地捕获。可能人为地夸大相关性并降低数据的自然方差,因为填充值直接位于回归线上。计算上比简单填充更复杂。随机回归填充这是对标准回归填充的改进。在使用回归预测缺失值后,将一个从回归误差分布中抽取的随机误差项(残差)添加到预测结果中。$$ \text{填充值} = \text{回归预测值} + \text{随机误差} $$优点: 通过添加噪声解决了标准回归填充的方差减少问题,从而得到更真实的填充值。通常优于确定性回归填充。缺点: 实现起来更复杂。仍然依赖于基础回归模型的假设。高级填充技术这些方法通常更复杂,并且通常能提供更好的结果,特别是当简单方法的假设不成立时。K近邻 (KNN) 填充这种方法根据其“邻居”的值来填充缺失值。对于某个特征中存在缺失值的数据点,KNN填充基于其他可用特征(使用欧几里得距离等距离度量)识别特征空间中K个最近的数据点(邻居)。然后,利用这K个邻居中该特征的平均值(对于数值型)或众数(对于类别型)来填充缺失值。优点: 可以处理数值型和类别型数据。不要求对数据分布进行假设(非参数)。可以通过邻域隐式捕获复杂的非线性关系。使用来自多个特征的信息。缺点: 计算成本高,特别是对于大型数据集,因为它需要计算数据点之间的距离。性能对K的选择和距离度量很敏感。如果特征很多,可能会受到维度灾难的影响。要求特征进行适当缩放以进行距离计算。# 使用scikit-learn的例子 from sklearn.impute import KNNImputer import numpy as np X = [[1, 2, np.nan], [3, 4, 3], [np.nan, 6, 5], [8, 8, 7]] # 在使用KNNImputer之前,通常应对特征进行缩放 # 初始化KNNImputer(例如,n_neighbors=2) imputer = KNNImputer(n_neighbors=2) # 拟合并转换数据 X_imputed = imputer.fit_transform(X) print("原始数据:") print(np.array(X)) print("\n填充数据 (KNN):") print(X_imputed)多重链式方程填充 (MICE) / 迭代填充多重填充 (MI) 被认为是一种高效方法。MI不只是为每个缺失项填充一个值,而是创建m个(例如5或10个)完整数据集。每个数据集的创建方式是,使用一种包含随机性的方法填充缺失值,以承认真实值的不确定性。一种常用的MI算法是MICE(多重链式方程填充),通常通过迭代填充来实现:首先对所有缺失值进行简单填充(例如,均值填充)。选取一个有缺失值的特征(“变量A”)。暂时将其填充值恢复为缺失状态。训练一个回归模型,使用所有其他特征(使用其当前观测值或填充值)来预测“变量A”。使用此模型预测“变量A”中的缺失值。在预测过程中加入随机性(如随机回归中)。移到下一个有缺失值的特征(“变量B”),重复步骤2-4,使用“变量A”新填充的值。循环所有有缺失值的特征多次(迭代),直到填充值稳定下来。这就完成了一个填充数据集的创建。重复步骤1-6 m次,使用不同的随机种子或抽取方式,生成m个不同的填充数据集。创建m个数据集后,你分别对每个数据集进行分析(例如,训练你的机器学习模型)。最后,你使用特定的公式(如鲁宾规则)汇总结果(例如,模型系数、预测值、评估指标),以获得一个考虑了填充所引入不确定性的最终结果。Scikit-learn的IterativeImputer提供了一个基于这种迭代方法的实现,它将每个具有缺失值的特征建模为其他特征的函数。虽然它默认通常生成一个填充数据集,但它构成了MICE的根本原理。优点: 考虑了与填充相关的不确定性,从而获得更准确的标准误差和置信区间。通常比单值填充方法提供更少偏差的估计。可以处理各种数据类型和复杂的数据缺失模式。被许多统计学家认为是当前最好的方法。缺点: 比单值填充实现和解释起来要复杂得多。需要专门的软件或库。结果汇总步骤增加了分析流程的复杂性。计算量大。# 使用scikit-learn的IterativeImputer的例子 from sklearn.experimental import enable_iterative_imputer # 启用实验性功能 from sklearn.impute import IterativeImputer import numpy as np X = [[1, 2, np.nan], [3, 4, 3], [np.nan, 6, 5], [8, 8, 7]] # 初始化IterativeImputer # 它将每个有缺失值的特征建模为其他特征的函数 # 并迭代直到收敛。添加随机性以实现类似MI的行为。 imputer = IterativeImputer(max_iter=10, random_state=0) # 拟合并转换数据 X_imputed_iterative = imputer.fit_transform(X) print("原始数据:") print(np.array(X)) print("\n填充数据 (IterativeImputer):") print(X_imputed_iterative) # 注意: 对于真正的多重填充,此过程将重复 # 多次,使用不同的随机状态以生成多个数据集。创建缺失值指示器有时,某个值缺失的事实本身就包含信息。在进行填充之前,你可以创建额外的二元指示器(虚拟)特征,如果相应特征中的原始值缺失,则为1,否则为0。$$ x_{\text{指示器}} = \begin{cases} 1 & \text{如果 } x \text{ 缺失} \ 0 & \text{如果 } x \text{ 存在} \end{cases} $$然后,这些指示器特征可以与填充后的数据一起包含在你的模型中。这使得模型可能学习与缺失本身相关的模式(捕获一些MNAR情况)。# 使用pandas的例子 import pandas as pd import numpy as np data = {'Age': [25, 30, np.nan, 35], 'Income': [50000, np.nan, 75000, 80000]} df = pd.DataFrame(data) # 创建指示器列 for col in df.columns: if df[col].isnull().any(): df[f'{col}_missing_indicator'] = df[col].isnull().astype(int) print("包含缺失指示器的DataFrame:") print(df) # 现在你可以继续填充'Age'和'Income'中的np.nan # 例如,使用SimpleImputer: # from sklearn.impute import SimpleImputer # imputer = SimpleImputer(strategy='mean') # df[['Age', 'Income']] = imputer.fit_transform(df[['Age', 'Income']]) # print("\n填充后的DataFrame(保留指示器):") # print(df)选择合适的策略和实施注意事项没有普遍最佳的缺失数据处理方法。最佳选择取决于:缺失数据量: 如果只有极少量缺失,删除更可行。大量缺失数据则需要更谨慎的填充。缺失数据模式/机制: 是集中在特定列还是分散?你能否对MCAR/MAR/MNAR做出有根据的猜测?高级方法通常假定MAR。数据和变量的性质: 变量之间的关系可能偏向于回归或KNN填充。计算成本可能使得非常大型数据集不适用复杂方法。任务要求: 特定的机器学习算法或分析目标可能影响选择。有些模型比其他模型对填充引入的伪影更敏感。重要实施实践:分割后填充为防止数据泄露,始终在将数据分割为训练集和测试集之后执行填充。仅使用训练数据拟合你的填充器(例如,SimpleImputer、KNNImputer、IterativeImputer)。然后,使用已拟合的填充器转换训练数据和测试数据。这确保了测试集中的信息不会影响填充过程,模拟了模型在生产环境中遇到新、未见数据的情况。强烈建议使用scikit-learn管道来简化此过程并避免错误。digraph G { rankdir=LR; node [shape=box, style=rounded, fontname="sans-serif", fontsize=10]; edge [fontname="sans-serif", fontsize=10]; subgraph cluster_pipeline { label = "预处理流程"; bgcolor="#e9ecef"; raw_data [label="原始数据\n(训练或测试)"]; imputer [label="填充器\n(例如,KNNImputer)\n在训练数据上拟合"]; scaler [label="缩放器\n(例如,StandardScaler)\n在训练数据上拟合"]; model [label="模型\n(例如,RandomForest)"]; raw_data -> imputer [label="转换"]; imputer -> scaler [label="转换"]; scaler -> model [label="输入特征"]; } train_data [label="训练数据分割", shape=cylinder, style=filled, fillcolor="#a5d8ff"]; test_data [label="测试数据分割", shape=cylinder, style=filled, fillcolor="#ffec99"]; train_data -> imputer [label="拟合并转换", style=dashed, constraint=false]; test_data -> imputer [label="转换", style=dashed, constraint=false]; }流程图,展示了填充器如何融入典型的机器学习流程,并强调仅在训练数据上进行拟合,然后转换训练集和测试集。周全地处理缺失数据是数据准备中的重要一步。通常需要尝试不同的策略并评估它们对特定建模任务的影响,以找出最适合你的数据集的方法。