14 缺失数据处理

核心问题:为何“数据清洗”是分析中最耗时也最关键的一步?

在理想世界中,我们拥有完美、完整的数据集。但在现实中,几乎所有真实世界的经济和金融数据集都存在缺失值 (Missing Values)

  • 一个交易策略的回测表现,可能因为错误处理了财报数据中的一个缺失值而天差地别。
  • 一个宏观经济预测模型,可能因为无法处理调查数据中的缺失项而产生巨大偏差。

今天,我们将系统学习如何科学地应对这个无处不在的挑战。

数据科学的冰山

一个成功的量化分析项目,其工作量分布往往像一座冰山。

数据科学工作流冰山模型 此图用冰山比喻数据科学项目的工作量,其中模型构建只是水面上的尖端,而数据清洗和准备是水下巨大的基础。 工作量的 80% 模型构建 & 分析 (你所看到的) 数据清洗 & 准备 (你没看到的) (缺失值处理)

缺失值处理,正是这座冰山水下最庞大、最关键的部分。

本章的学习路线图

我们的目标是让大家成为处理缺失数据的专家。

本章学习路线图 一个包含四个步骤的路线图:1. 理解为何缺失, 2. 掌握简单方法, 3. 精通高级方法, 4. 动手实践。 1. 理解“为何” 剖析三种核心 缺失机制 2. 掌握“简单方法” 学习删除法、均值填充 及其严重缺陷 3. 精通“高级方法” 深入学习MICE 和KNN填充 4. 动手“实践” 在Python中 比较不同方法

我们的工具箱

我们将使用Python生态系统中的黄金标准库来完成今天的任务。

pandas - 核心数据结构(DataFrame) - 数据读取、操作、清洗

numpy - 高性能数值计算 - 科学计算的基础

matplotlib - 数据可视化 - 创建高质量图表

scikit-learn - 机器学习瑞士军刀 - 提供简单和高级的数据填充工具 (imputers)

对这些库的基本了解将非常有帮助。

一切始于一个不完美的数据集

想象一下,我们正在为一家投资公司分析几家上市公司的财务指标,数据如 Table 1 所示。

Table 1: 我们的初始数据存在明显的“洞”
数据 每股股价 (pps) 市净率 (bm) 资产回报率 (roa)
1 20.170 0.358 0.015
2 NaN 0.220 0.036
3 14.400 0.296 0.046
4 10.090 0.290 0.036
5 17.900 NaN 0.024

数据2的股价和数据5的市净率是缺失的。这在现实中极为常见,原因多种多样:数据录入错误、公司未披露、数据库合并失败等。

数据缺失的“谱系”:并非所有缺失都生而平等

在统计学上,我们根据数据缺失的产生机制,将其严格分为三类。理解你面对的是哪一类,至关重要。

数据缺失的三种机制 展示三种数据缺失机制:MCAR, MAR, 和 MNAR,并用箭头表示它们之间的关系和复杂程度。 MCAR 完全随机缺失 (最理想,最罕见) MAR 随机缺失 (最常见,可处理) MNAR 非随机缺失 (最棘手,需警惕) 复杂性与处理难度增加 ⟶

机制一:完全随机缺失 (MCAR)

完全随机缺失 (Missing Completely at Random, MCAR) 指的是一个值的缺失概率与任何变量(包括它自身)都无关。

  • 直观理解:缺失的发生是纯粹的“坏运气”,就像随机抛硬币决定是否删除一个数据点。
  • 金融例子:一位研究助理在手动录入公司财报数据时,不小心打翻了咖啡,随机污染了表格中的几个单元格,导致这些数据无法读取。这些数据的缺失与公司的任何基本面都毫无关系。
MCAR 概念图 一个表格数据,其中一些单元格被随机的“污渍”覆盖,表示数据的缺失与数据内容无关。 MCAR: 缺失是纯粹的偶然事件 股票A股票B 股票C股票D 价格成交量 波动率市盈率 10.5150.2 1.2M 0.8M 0.250.31 1522

MCAR是统计推断的理想情况

如果数据确实是MCAR,那么处理起来相对简单。

  • 核心特性:剩余的观测数据仍然是原始数据的无偏代表
  • 有效方法:在这种理想情况下,直接删除含有缺失值的行(即“完整案例分析”)虽然会损失样本量,但不会引入系统性偏差
  • 警示:然而,在经济和金融领域,真正的MCAR非常罕见。我们绝不能轻易假设数据是MCAR。

机制二:随机缺失 (MAR)

随机缺失 (Missing at Random, MAR) 指的是一个值的缺失概率与观测到的其他变量有关,但与它自身的未观测值无关。

  • 直观理解:缺失不是完全随机的,我们可以从其他已知信息中找到一些关于“为什么会缺失”的线索。
  • 金融例子:在调查问卷中,高收入人群可能更不愿意透露自己的具体收入,但他们愿意透露自己的学历职业。因此,“收入”这个变量的缺失与“学历”、“职业”等已观测变量相关。
MAR 概念图 一张调查表,其中“学历”字段影响了“年收入”字段是否被填写。 MAR: 缺失依赖于其他已知信息 姓名:张三 学历:博士 年收入:[未填写] 高学历导致 更可能不填

MAR是大多数高级插补方法的基础假设

MAR是比MCAR更现实也更常见的假设。

  • 核心特性:直接删除数据会引入偏差,因为被删除的样本(如高收入人群)在某些特征上与保留的样本系统性地不同。
  • 有效方法:高级插补方法,如我们稍后将讲到的MICE,正是利用了缺失变量与其他观测变量之间的关系来进行有根据的猜测。这些方法在MAR假设下是无偏的。

机制三:非随机缺失 (MNAR)

非随机缺失 (Missing Not at Random, MNAR) 是最棘手的情况,指的是一个值的缺失概率与它自身的值有关

  • 直观理解:数据之所以缺失,恰恰是因为它本应具有的那个值。这个值本身就是缺失的原因。
  • 金融例子
    1. 表现最差的基金选择不向数据库报告其月度回报,以掩盖其糟糕的业绩。
    2. 负债率极高的公司可能选择不披露某些关键的债务指标。
MNAR 概念图 一张基金业绩报告,其中表现最差的基金回报率数据被隐藏,表示缺失与值本身相关。 MNAR: 缺失本身就是信息 基金回报率 基金A+15% 基金B+8% 基金C+2% 基金D ? (因业绩太差 选择不报告)

MNAR是统计建模的“禁区”

如果数据是MNAR,任何标准的缺失数据处理方法都可能失效并产生严重误导。

  • 核心挑战:缺失本身就是信息。我们无法从其他变量中推断出缺失值,因为驱动因素是缺失值本身。
  • 处理思路:处理MNAR需要更复杂的模型,通常需要对缺失机制本身进行建模(例如,Heckman选择模型),或者将“数据是否缺失”作为一个新的指示变量(dummy variable)纳入模型。

总结:缺失机制决定处理策略

机制 缺失概率依赖于… 金融例子 合适的处理方法
MCAR 数据录入员的随机失误 删除行、简单填充、高级填充
MAR 其他观测变量 高学历者不填收入 高级填充(MICE, KNN)
MNAR 缺失值本身 差基金不报告回报 特定模型(如选择模型),创建指示变量

简单方法 1:直接删除 (Listwise Deletion)

这是最简单粗暴的方法:只要一行数据中任何一个变量是缺失的,就将整行数据(整个观测样本)从数据集中删除。

直接删除法图示 一个数据表格,其中包含缺失值的两行被红色叉号标记并变暗,表示它们被删除。 IDVar AVar BVar C 120.10.350.01 2NaN0.220.03 314.40.290.04 417.9NaN0.02

直接删除法的优缺点

优点: * 实现极其简单。 * 如果数据是MCAR,不会引入偏差。

缺点: * 严重损失信息:如果多个变量都有一些缺失,可能会删除大部分数据。 * 引入偏差:如果数据不是MCAR(例如是MAR),这种方法会引入严重的系统性偏差。

结论:除非你确信数据是MCAR且缺失比例极低(< 5%),否则永远不要轻易使用这种方法。

简单方法 2:单变量填充 (Simple Imputation)

这种方法试图保留样本量,用一个“合理”的估计值来填补空缺。

  • 对于连续变量:用该变量所有非缺失值均值中位数来填充。
  • 对于分类变量:用该变量所有非缺失值众数(出现频率最高的类别)来填充。
  • 对于时间序列数据:用上一个时间点的值来填充(Last Observation Carried Forward, LOCF)。

均值填充的机制

均值填充法图示 一个数列,其中的缺失值被该数列的平均值所替代。 原始数据列 10 20 NaN 30 均值 = (10+20+30)/3 = 20 填充后数据列 10 20 20 30

均值/中位数填充是“美丽的毒药”

虽然均值填充保留了样本量,但它引入了新的、更隐蔽的问题。

  1. 人为制造了尖峰:大量原本分散的缺失值被强制设定为同一个值(均值),在数据分布的中心制造出一个不自然的尖峰。
  2. 低估了真实方差:填充进去的值没有任何变异性,这会人为地降低整个变量的方差。
  3. 扭曲了变量关系:由于方差被低估,该变量与其他变量之间的协方差和相关性也会被系统性地低估

可视化警告:均值填充如何扭曲数据分布

Figure 1 直观地展示了均值填充的破坏性。

Code
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# --- 设置绘图风格 ---
sns.set_style("whitegrid")
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号

# --- 1. 生成原始数据 ---
np.random.seed(42)
original_data = np.random.normal(loc=100, scale=20, size=1000)

# --- 2. 引入 MCAR 缺失值 ---
data_with_missing = original_data.copy()
missing_indices = np.random.choice(1000, 300, replace=False)
data_with_missing[missing_indices] = np.nan

# --- 3. 用均值填充 ---
mean_val = np.nanmean(data_with_missing)
imputed_data = np.nan_to_num(data_with_missing, nan=mean_val)

# --- 4. 可视化 ---
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5), sharey=True)

# 绘制原始数据
sns.histplot(original_data, bins=30, kde=True, color='skyblue', ax=ax1)
ax1.axvline(np.mean(original_data), color='red', linestyle='--', label=f'真实均值: {np.mean(original_data):.2f}')
ax1.set_title(f'真实数据分布\n(标准差 = {np.std(original_data):.2f})', fontsize=14)
ax1.set_ylabel('频数')
ax1.legend()

# 绘制填充后数据
sns.histplot(imputed_data, bins=30, kde=True, color='salmon', ax=ax2)
ax2.axvline(mean_val, color='red', linestyle='--', label=f'填充均值: {mean_val:.2f}')
ax2.set_title(f'均值填充后的数据分布\n(标准差 = {np.std(imputed_data):.2f})', fontsize=14)
ax2.legend()

plt.suptitle('均值填充会系统性地低估方差并扭曲分布', fontsize=16)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
Figure 1: 均值填充(右图)严重低估了数据的真实方差,并在均值处制造了一个虚假的尖峰。

注意右图标普的显著下降,这是金融风险模型中的致命伤。

进阶方法:利用变量间的关系进行“智能”填充

简单填充的核心缺陷在于它只利用了缺失变量自身的信息(即它的均值),而忽略了数据集中其他变量提供的丰富信息。

核心思想:如果市净率(bm)缺失,但我们知道这家公司的市盈率(pe)、资产回报率(roa)和行业类别,我们或许可以预测出它最可能的市净率。

这就引出了基于模型的填充方法

高级方法 1:多重插补链式方程 (MICE)

MICE (Multivariate Imputation by Chained Equations) 是目前最流行、最稳健的缺失数据填充方法之一。

核心思想: 将缺失值填充问题,转化为一系列的回归预测任务。它不是一次性完成填充,而是在一个迭代的“链式”过程中不断优化填充值。

MICE 算法的直观步骤 (1/4)

假设变量 \(X_1\)\(X_2\) 有缺失值,而 \(X_3\) 是完整的。

第1步:初步猜测

首先,用最简单的均值填充法,为 \(X_1\)\(X_2\) 的缺失位置提供一个临时的初始值。现在我们有了一个“完整”但质量不高的数据集。

MICE 步骤1: 初步猜测 一个数据矩阵,其中有缺失的单元格被均值填充。 MICE 步骤 1: 初始均值填充 原始数据 X1X2X3 ? ? 均值填充 临时完整数据 X1X2X3 X̄1 X̄2

MICE 算法的直观步骤 (2/4)

  1. \(X_1\) 中刚刚用均值填充的值重新设为“缺失”。
  2. \(X_1\) 为因变量,\(X_2\)(临时填充的)和 \(X_3\) 为自变量,建立一个回归模型:\(X_1 \sim f(X_2, X_3)\)
  3. 使用这个模型预测\(X_1\) 的缺失值,并用这些新的、更优的预测值来更新数据集。
MICE 步骤2: 更新X1 图示X2和X3被用作预测变量来填充X1的缺失值。 MICE 步骤 2: 将 X1 作为目标进行回归 回归模型 X1 ~ f(X2, X3) X2, X3 新 X1 值

MICE 算法的直观步骤 (3/4)

  1. \(X_2\) 中最初用均值填充的值重新设为“缺失”。
  2. \(X_2\) 为因变量,更新后\(X_1\)\(X_3\) 为自变量,建立回归模型:\(X_2 \sim g(X_1, X_3)\)
  3. 使用这个新模型预测更新 \(X_2\) 的缺失值。
MICE 步骤3: 更新X2 图示更新后的X1和X3被用作预测变量来填充X2的缺失值。 MICE 步骤 3: 将 X2 作为目标进行回归 回归模型 X2 ~ g(X1, X3) 新 X1, X3 新 X2 值

MICE 算法的直观步骤 (4/4)

上述过程(更新 \(X_1\),然后更新 \(X_2\))完成了一个循环

MICE会重复这个循环多次(例如10次)。在每一次新的循环中,它都会使用上一轮循环得到的、质量更好的填充值来构建回归模型,从而得到更精确的预测。

这个迭代过程会持续进行,直到填充值趋于稳定,不再有大的变化。

高级方法 2:K-近邻填充 (KNN Imputation)

KNN Imputation 的思想非常直观:物以类聚,人以群分

要填充一个样本A的缺失值,可以这样做:

  1. 寻找邻居:在数据集中,根据所有未缺失的变量,找到与样本A最相似的K个样本(即它的“邻居”)。
  2. 取长补短:用这K个邻居在该缺失变量上的平均值(或中位数),来填充样本A的缺失值。

KNN填充的可视化解释

假设我们想填充红色数据点在“资产回报率”上的缺失值。

Code
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import NearestNeighbors
import seaborn as sns

# --- 设置绘图风格 ---
sns.set_style("whitegrid")
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号

# --- 1. 生成合成数据 ---
np.random.seed(0)
X = np.random.rand(50, 2) * 10
X[:, 1] = X[:, 1] + 0.5 * X[:, 0] + np.random.randn(50) * 0.5

# --- 2. 设定一个有缺失值的点 ---
point_with_missing = np.array([[5, np.nan]])
all_other_points = X

# --- 3. 基于非缺失特征寻找邻居 ---
k = 5
nn = NearestNeighbors(n_neighbors=k)
nn.fit(all_other_points[:, 0].reshape(-1, 1))
distances, indices = nn.kneighbors(point_with_missing[:, 0].reshape(-1, 1))

neighbors = all_other_points[indices.flatten()]
imputed_value = np.mean(neighbors[:, 1])
imputed_point = np.array([[5, imputed_value]])

# --- 4. 可视化 ---
plt.figure(figsize=(10, 7))

# 绘制所有点
plt.scatter(all_other_points[:, 0], all_other_points[:, 1], c='lightblue', label='完整数据点', s=60, alpha=0.8, edgecolors='black')
# 突出邻居
plt.scatter(neighbors[:, 0], neighbors[:, 1], c='orange', s=120, label=f'K={k}个最近邻', marker='*')
# 绘制填充后的点
plt.scatter(imputed_point[:, 0], imputed_point[:, 1], c='red', s=200, label='填充后的数据点', marker='X', edgecolors='black', linewidth=2)

# 连接到邻居
for neighbor in neighbors:
    plt.plot([imputed_point[0,0], neighbor[0]], [imputed_point[0,1], neighbor[1]], 'k--', alpha=0.5)

plt.title('K-近邻 (KNN) 填充的直观解释', fontsize=16)
plt.xlabel('市净率 (已知)', fontsize=12)
plt.ylabel('资产回报率 (部分缺失)', fontsize=12)
plt.legend(fontsize=12)
plt.grid(True)
plt.show()
Figure 2: KNN填充:利用“邻居”的信息来填补空缺。

我们基于已知的“市净率”,找到5个最相似的公司,然后用它们的资产回报率均值来填充红色点的缺失值。

MICE vs. KNN: 如何选择?

特性 MICE (IterativeImputer) KNN Imputer
基本原理 基于回归模型预测 基于样本相似度
假设 假设变量间存在(线性或非线性)关系 假设相似的样本有相似的值
计算成本 较高(需要多次迭代训练模型) 中等(需要计算距离矩阵)
适用数据 对各种数据类型都较稳健 对有清晰聚类结构的数据效果好
优点 能捕捉复杂的变量间关系,通常更准确 原理直观,非参数方法
缺点 像一个“黑箱”,过程不易解释 对K值的选择敏感,对高维数据敏感

实战演练:在Python中处理缺失金融数据

理论讲完了,现在我们进入实战环节。我们将使用scikit-learn库来演示简单和高级的填充方法,并直观地比较它们的效果。

步骤1:创建并观察我们的数据集

首先,我们创建一个模拟的金融数据集,并引入一些MAR类型的缺失值。

Code
import pandas as pd
import numpy as np

# --- 1. 创建一个有关联的模拟数据集 ---
np.random.seed(42)
data = {
    'pps': np.random.normal(20, 5, 1000),      # 每股股价
    'bm': np.random.uniform(0.1, 1.5, 1000),   # 市净率
    'roa': np.random.normal(0.05, 0.02, 1000)  # 资产回报率
}
df_orig = pd.DataFrame(data)
# 创造变量间的关联:bm不仅是随机的,还与pps和roa相关
df_orig['bm'] = df_orig['bm'] + df_orig['pps'] * 0.01 + df_orig['roa'] * 2

# --- 2. 引入 MAR 类型的缺失值 ---
# 股价(pps)越高的公司,其市净率(bm)越可能缺失
# 我们使用sigmoid函数来创建一个平滑的概率
missing_prob = 1 / (1 + np.exp(-(df_orig['pps'] - 25))) 
is_missing = np.random.binomial(1, missing_prob.values, size=1000).astype(bool)
df = df_orig.copy()
df.loc[is_missing, 'bm'] = np.nan

# --- 3. 再引入一些 MCAR 类型的缺失值,使情况更复杂 ---
mc_missing_indices = np.random.choice(df.index, 50, replace=False)
df.loc[mc_missing_indices, 'pps'] = np.nan


print('数据集的前5行 (包含缺失值NaN):')
print(df.head())
print(f'\n市净率(bm)的缺失比例: {df["bm"].isnull().sum() / len(df):.2%}')
print(f'每股股价(pps)的缺失比例: {df["pps"].isnull().sum() / len(df):.2%}')

# 保存原始的bm列,用于后续比较
original_bm_for_plot = df_orig.loc[df['bm'].isnull(), 'bm']
数据集的前5行 (包含缺失值NaN):
         pps        bm       roa
0  22.483571  0.646943  0.043816
1  19.308678  0.509396  0.034957
2  23.238443  1.336154  0.056383
3  27.615149       NaN  0.076809
4  18.829233  0.357506  0.012497

市净率(bm)的缺失比例: 16.20%
每股股价(pps)的缺失比例: 5.00%

我们特意制造了一些MAR类型的缺失,这更贴近现实。

步骤2:使用简单均值填充

现在,我们使用 SimpleImputer 来执行均值填充。这个过程分为两步:.fit()(学习)和 .transform()(应用)。

from sklearn.impute import SimpleImputer

# 1. 创建一个均值填充器
mean_imputer = SimpleImputer(strategy='mean')

# 2. Fit: 填充器学习每一列的均值
#    Transform: 学习到的均值被用来填充NaN
df_mean_imputed = pd.DataFrame(mean_imputer.fit_transform(df), columns=df.columns)

print('使用均值填充后的数据集的前5行:')
print(df_mean_imputed.head())
print(f'\n填充后市净率(bm)是否还有缺失值? {df_mean_imputed["bm"].isnull().sum() == 0}')
使用均值填充后的数据集的前5行:
         pps        bm       roa
0  22.483571  0.646943  0.043816
1  19.308678  0.509396  0.034957
2  23.238443  1.336154  0.056383
3  27.615149  1.096417  0.076809
4  18.829233  0.357506  0.012497

填充后市净率(bm)是否还有缺失值? True

步骤3:使用MICE进行高级填充

接下来,我们使用 IterativeImputer (scikit-learn中MICE的实现) 来进行更智能的填充。

from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer

# 1. 创建一个MICE填充器
#    max_iter=10: 迭代10轮
#    random_state=0: 保证结果可以复现
mice_imputer = IterativeImputer(max_iter=10, random_state=0)

# 2. Fit and Transform: 填充器在迭代中为每个变量建立模型并预测缺失值
df_mice_imputed = pd.DataFrame(mice_imputer.fit_transform(df), columns=df.columns)

print('使用MICE填充后的数据集的前5行:')
print(df_mice_imputed.head())
print(f'\n填充后市净率(bm)是否还有缺失值? {df_mice_imputed["bm"].isnull().sum() == 0}')
使用MICE填充后的数据集的前5行:
         pps        bm       roa
0  22.483571  0.646943  0.043816
1  19.308678  0.509396  0.034957
2  23.238443  1.336154  0.056383
3  27.615149  1.324827  0.076809
4  18.829233  0.357506  0.012497

填充后市净率(bm)是否还有缺失值? True

最终裁决:可视化比较填充效果

哪种方法更好?眼见为实。我们将比较原始真实值、均值填充值和MICE填充值的分布。

Code
# 提取被填充的值
mean_imputed_values = df_mean_imputed.loc[is_missing, 'bm']
mice_imputed_values = df_mice_imputed.loc[is_missing, 'bm']
original_values = df_orig.loc[is_missing, 'bm']

plt.figure(figsize=(12, 7))

# 绘制分布图
sns.kdeplot(original_values, label='原始真实值分布', color='skyblue', linewidth=3, fill=True)
sns.kdeplot(mean_imputed_values, label='均值填充值分布', color='salmon', linewidth=3, fill=True)
sns.kdeplot(mice_imputed_values, label='MICE填充值分布', color='lightgreen', linewidth=3, fill=True)

plt.title('不同方法填充值的分布对比', fontsize=16)
plt.xlabel('市净率 (bm) 值', fontsize=12)
plt.ylabel('密度', fontsize=12)
plt.legend(fontsize=12)
plt.tight_layout()
plt.show()
Figure 3: MICE填充(绿色)显著优于均值填充(红色),它更好地还原了数据的原始分布形态。

结果分析:MICE完胜

Figure 3 中我们可以清晰地看到:

  • 均值填充(红色):正如理论所预测的,它在数据均值附近制造了一个巨大的、不自然的尖峰,完全没有捕捉到数据的真实分布形态。
  • MICE填充(绿色):其分布形状与原始真实值(蓝色)的分布惊人地相似。它成功地利用 ppsroa 的信息,预测出了与真实情况非常接近的值。

量化分析:标准差的对比

视觉效果很震撼,我们再用数字来证实。

# --- 提取整个bm列 ---
bm_original_full = df_orig['bm']
bm_mean_imputed_full = df_mean_imputed['bm']
bm_mice_imputed_full = df_mice_imputed['bm']

# --- 计算标准差 ---
std_original = bm_original_full.std()
std_mean = bm_mean_imputed_full.std()
std_mice = bm_mice_imputed_full.std()

# --- 打印结果 ---
print(f"原始数据的标准差: {std_original:.4f}")
print(f"均值填充后标准差: {std_mean:.4f} (被人为降低了 { (1 - std_mean/std_original):.2%} )")
print(f"MICE填充后标准差: {std_mice:.4f} (非常接近原始值)")
原始数据的标准差: 0.4115
均值填充后标准差: 0.3808 (被人为降低了 7.44% )
MICE填充后标准差: 0.3865 (非常接近原始值)

结论:在任何严肃的经济或金融建模中,MICE等高级填充方法都应该是你的首选。

课堂讨论:什么时候不应该填充数据?

我们已经学习了如何填充数据,但一个更深刻的问题是:是否应该填充?

请思考一个具体的例子,在什么情况下,对缺失数据进行任何形式的填充都可能是一个错误,甚至会误导我们的分析?

你的缺失数据处理清单

  1. [侦察] 检查每个变量的缺失比例。df.isnull().sum() / len(df)
  2. [思考] 深入思考缺失的可能机制。是MCAR, MAR, 还是MNAR?这是最重要的一步。
  3. [决策]
    • 如果是MNAR:停止!不要用标准方法填充。考虑将“是否缺失”作为一个新特征,或者使用专门的模型。
    • 如果是MCAR且比例很低(<5%):可以考虑直接删除。
    • 如果是MAR或不确定:优先选择高级填充方法。
  4. [执行] 使用 IterativeImputer (MICE) 或 KNNImputer
  5. [验证] 通过可视化(如本例中的KDE图)检查填充后数据的分布是否合理。

本章总结与核心要点

  1. 缺失机制是第一性原理:处理缺失数据的第一步永远是思考其背后的机制(MCAR, MAR, MNAR)。这将指导你所有后续的选择。
  2. 告别简单方法:除非有极好的理由,否则应避免直接删除和简单的均值/中位数填充。它们看似简单,实则会系统性地扭曲你的数据。
  3. 拥抱高级模型MICE (IterativeImputer) 和 KNN (KNNImputer) 是现代数据科学中处理缺失值的标准工具,它们提供了远比简单方法更准确、偏差更小的填充。
  4. 实践出真知:工具的选择最终要服务于你的研究目的。始终要通过可视化等手段,检查填充后的数据是否保留了合理的分布形态。