在数据科学和机器学习的日常工作中,我们经常遇到一个令人头疼的问题——数据缺失。无论是因为传感器故障、人工录入错误,还是用户在某些问卷中跳过了敏感问题,缺失数据几乎是不可避免的。当我们面对这些“不完美”的数据集时,如何在不引入过多偏差的情况下,最大限度地利用现有信息进行相关性分析或协方差估计呢?这正是我们今天要探讨的核心话题——成对删除。
在本文中,我们将深入探讨这种处理缺失数据的策略。我们会从基础概念入手,分析它的优缺点,并通过多个实战代码示例,看看如何在实际项目中应用它。最后,我们还会对比它与另一种常用方法(列表删除)的区别,帮助你在未来的项目中做出最佳选择。
目录
缺失数据的机制与挑战
在正式介绍成对删除之前,我们首先需要理解缺失数据产生的背景。统计学上,我们通常将缺失数据的机制分为以下三类,理解它们对于决定是否使用成对删除至关重要:
- MCAR(完全随机缺失): 这是最理想的情况。数据缺失是完全随机的,与观测数据或未观测数据都无关。例如,实验室样本在运输途中随机损坏。
- MAR(随机缺失): 缺失的概率仅依赖于已观测到的数据。例如,高收入人群更倾向于在问卷中隐藏收入,但如果我们知道了他们的职业和年龄(已观测),收入缺失的概率是可以解释的。
- MNAR(非随机缺失): 这是最棘手的情况。缺失本身依赖于未观测到的数据。例如,患有严重抑郁症的人更可能不回答心理健康调查的相关问题。
缺失数据带来的核心挑战
当我们面对带有缺失值的数据集时,直接忽略它们并不是一个好主意,这会带来以下挑战:
- 估计偏差: 如果数据不是 MCAR,简单地忽略缺失值可能会导致统计量(如均值、方差)的估计出现系统性偏差。
- 效率降低: 传统的“一刀切”删除方法会浪费大量包含部分有效信息的行,导致样本量减少,统计效能下降。
- 模型复杂性: 为了保留数据,我们有时不得不使用复杂的插补算法,这增加了计算成本和模型实现的复杂度。
什么是成对删除?
成对删除是一种在计算统计量(如相关系数或协方差)时动态处理缺失值的技术。它的核心思想非常简单:在计算两个特定变量之间的关系时,只使用这两个变量中均有有效数据的那些行。
它是如何工作的?
想象一下,你有一个包含 $A$、$B$、$C$ 三列的数据框,每列都有一些缺失值。
- 当你计算 $A$ 和 $B$ 的相关性时,成对删除会自动忽略那些 $A$ 或 $B$ 中任何一个是空值的行。
- 紧接着,当你计算 $B$ 和 $C$ 的相关性时,它会再次扫描数据,这次忽略的是 $B$ 或 $C$ 中有空值的行。
这意味着,计算不同变量对时,所使用的样本数量($n$)可能是不同的。这与列表删除形成了鲜明对比,后者只要一行中有任何缺失,就会把整行数据从所有分析中剔除。
数学原理
让我们回顾一下皮尔逊相关系数的计算公式,这对理解成对删除很有帮助:
$$ r = \frac{\sum{i=1}^{n}(xi – \bar{X})(yi – \bar{Y})}{\sqrt{\sum{i=1}^{n}(xi – \bar{X})^2 \cdot \sum{i=1}^{n}(y_i – \bar{Y})^2}} $$
在成对删除的逻辑中,对于每一对变量 $(X, Y)$,公式中的 $n$ 并不一定是数据集的总行数,而是 $X$ 和 $Y$ 同时非空的行数。均值 $\bar{X}$ 和 $\bar{Y}$ 也是基于这些特定的行计算出来的,而不是全局均值。
实战代码示例 1:基础相关性计算
让我们通过一个直观的 Python 示例来看看成对删除是如何运作的。我们将手动实现这一逻辑,以便你能看到其内部机制。
import numpy as np
import pandas as pd
# 构造一个包含缺失值的示例数据集
data = {
‘X‘: [1, 2, np.nan, 4, 5],
‘Y‘: [5, np.nan, 2, 4, 3],
‘Z‘: [2, 3, 4, 5, np.nan]
}
df = pd.DataFrame(data)
print("原始数据集:")
print(df)
print("-" * 20)
def manual_pairwise_correlation(df):
"""手动实现成对删除相关性计算"""
correlations = {}
cols = df.columns
# 遍历所有可能的变量对组合
for i in range(len(cols)):
for j in range(i + 1, len(cols)):
col1 = cols[i]
col2 = cols[j]
# 关键步骤:仅选取当前这对变量都有效的行
# 这就是成对删除的核心:dropna() 默认剔除任何包含 NaN 的行
valid_data = df[[col1, col2]].dropna()
if len(valid_data) >= 2: # 至少需要两个点才能计算相关性
# 计算相关系数
corr = valid_data.corr().iloc[0, 1]
correlations[f‘{col1} vs {col2}‘] = (corr, len(valid_data))
else:
correlations[f‘{col1} vs {col2}‘] = (np.nan, 0)
return correlations
# 执行计算
results = manual_pairwise_correlation(df)
print("
成对删除计算结果 (相关系数, 有效样本数):")
for pair, (corr, n) in results.items():
print(f"{pair}: r = {corr:.4f} (基于 n = {n})")
代码解析:
在这个例子中,注意 valid_data = df[[col1, col2]].dropna() 这一行。这就是成对删除的精髓所在。当计算 X 和 Y 的相关性时,Pandas 会忽略第 3 行(X缺失)和第 2 行(Y缺失),只利用剩下的行来计算。而计算 X 和 Z 时,又会利用另一组不同的行。
Pandas 内置的成对删除
好消息是,你不需要每次都手写循环。Pandas 的 .corr() 方法默认就支持成对删除。让我们验证一下。
# 使用 Pandas 内置方法计算相关矩阵
corr_matrix = df.corr()
print("
Pandas 自动生成的相关矩阵 (默认使用成对删除):")
print(corr_matrix)
你会发现,Pandas 的输出与我们手动计算的结果是一致的。它是懒惰但高效的:只有在需要计算某两个变量的相关性时,它才去关心那两个变量的数据完整性。
实战代码示例 2:协方差矩阵与异常值处理
除了相关性,协方差矩阵也是机器学习中(特别是 PCA 等降维算法)非常重要的统计量。让我们看看如何计算成对删除的协方差矩阵,并添加一些数据清洗的逻辑。
import pandas as pd
import numpy as np
# 模拟一个更真实的数据集,包含一些潜在的异常值
np.random.seed(42)
data_size = 100
df_real = pd.DataFrame({
‘Feature_A‘: np.random.normal(0, 1, data_size),
‘Feature_B‘: np.random.normal(5, 2, data_size),
‘Feature_C‘: np.random.normal(-2, 3, data_size)
})
# 人为引入缺失值 (MCAR)
mask = np.random.random(df_real.shape) < 0.1 # 10% 的数据缺失
df_real[mask] = np.nan
# 引入几个极端的异常值
df_real.loc[0, 'Feature_A'] = 100
def robust_pairwise_cov(df, drop_outliers=False):
"""计算成对删除的协方差矩阵,可选择是否剔除异常值"""
cols = df.columns
cov_matrix = pd.DataFrame(np.zeros((len(cols), len(cols))), index=cols, columns=cols)
for col1 in cols:
for col2 in cols:
# 1. 选择这对变量
subset = df[[col1, col2]].copy()
# 2. 成对删除:移除任一变量为 NaN 的行
subset.dropna(inplace=True)
if drop_outliers:
# 实用见解:使用 IQR 方法剔除这对变量中的异常值
# 这在预处理阶段非常有用,可以防止异常值扭曲相关性
Q1 = subset.quantile(0.25)
Q3 = subset.quantile(0.75)
IQR = Q3 - Q1
# 只保留在上下界内的数据
subset = subset[~((subset (Q3 + 1.5 * IQR))).any(axis=1)]
# 3. 计算协方差
# 注意:这里我们显式计算,当然 pandas 也有 cov()
if len(subset) > 0:
cov_val = subset.cov().iloc[0, 1]
cov_matrix.loc[col1, col2] = cov_val
else:
cov_matrix.loc[col1, col2] = np.nan
return cov_matrix
print("标准成对删除协方差矩阵:")
print(robust_pairwise_cov(df_real).round(2))
print("
剔除异常值后的成对删除协方差矩阵:")
print(robust_pairwise_cov(df_real, drop_outliers=True).round(2))
实用见解:
在这个示例中,我们展示了如何将成对删除与异常值检测结合起来。这是一种高级的数据清洗策略。通过在每一对变量的计算中单独应用 IQR 过滤,我们可以防止某一对变量中的一个极端值破坏它们之间的协方差估计,同时又不影响其他变量对的计算。
实战代码示例 3:可视化与热力图
在探索性数据分析(EDA)中,我们经常使用热力图来观察特征之间的关系。当数据有缺失时,直接绘制热力图可能会有误导性。我们可以利用成对删除来生成准确的相关矩阵,并进行可视化。
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
# 创建一个具有特定相关模式的示例数据
np.random.seed(10)
data = {
"A": [1, 2, 3, 4, 5, np.nan, 7, 8, 9, 10],
"B": [2, 4, 6, 8, 10, 12, 14, np.nan, 18, 20], # B 与 A 强相关
"C": [5, 4, 3, 2, 1, 5, 4, 3, 2, 1], # C 与 A 负相关
"D": [3, 3, 3, 3, 3, np.nan, 3, 3, 3, 3] # D 是常数
}
df_vis = pd.DataFrame(data)
# 计算相关矩阵
# min_periods=1 表示只要有一个有效观测值就尝试计算(虽然通常需要更多才有意义)
# 但为了演示,我们主要依赖 dropna 的逻辑
pairwise_corr = df_vis.corr(method=‘pearson‘)
# 设置绘图风格
sns.set_theme(style="white")
# 绘制热力图
plt.figure(figsize=(8, 6))
sns.heatmap(pairwise_corr,
annot=True, # 显示数值
cmap="coolwarm", # 配色方案
vmin=-1, vmax=1, # 固定颜色范围在 -1 到 1 之间
center=0, # 颜色中心值为 0
square=True, # 单元格为正方形
linewidths=.5, # 线宽
cbar_kws={"shrink": .5}) # 颜色条大小
plt.title("成对删除相关性矩阵热力图")
plt.show()
解读图表:
当你观察生成的热力图时,你可以确信每一个单元格中的数字都是基于该对变量所能获得的最大信息量计算出来的。例如,A 和 B 的相关性可能高达 1.0,因为尽管某些行缺失了 A 或 B,但剩下的有效数据完美保留了 $2A=B$ 的关系。
常见错误与最佳实践
虽然成对删除听起来很棒(因为它保留了数据),但在使用时我们必须非常小心。
1. 样本量不一致的陷阱
由于每一对变量使用的样本数量 $n$ 不同,你可能会遇到一些统计上的怪象。
- 问题: 如果变量 X 和 Y 的相关性是基于 100 个样本计算的,而 X 和 Z 的相关性是基于 10 个样本计算的,那么这两个相关系数本身是不具备直接可比性的。小样本的相关系数通常波动更大。
- 解决方案: 始终检查有效样本数(N)。在 Pandas 中,可以通过 INLINECODEf2a7f81c 结合 INLINECODEb5f822ae 来检查。
# 检查每对变量的有效样本数量
def count_valid_pairs(df):
cols = df.columns
count_df = pd.DataFrame(index=cols, columns=cols)
for col1 in cols:
for col2 in cols:
count_df.loc[col1, col2] = df[[col1, col2]].dropna().shape[0]
return count_df.astype(int)
print("每对变量的有效样本数量矩阵:")
print(count_valid_pairs(df))
2. 非正定矩阵问题
在机器学习(如高斯判别分析、马氏距离计算)中,我们通常需要计算协方差矩阵的逆。
- 问题: 使用成对删除得到的协方差矩阵可能不再是半正定的。这意味着该矩阵可能存在负的特征值,这在数学上对于协方差矩阵是不合法的(真实的协方差矩阵必须是半正定的)。如果你尝试对这个矩阵求逆,程序会报错或得到荒谬的结果。
- 解决方案: 如果你的下游任务涉及矩阵求逆(例如 LDA),请避免直接使用成对删除得到的协方差矩阵。这种情况下,建议先对数据进行插补,或者使用列表删除。
3. 数据结构隐藏的偏差
- 问题: 如果数据不是 MCAR,而是 MAR 或 MNAR,成对删除可能会人为地制造出相关性。例如,如果老年人倾向于不回答“收入”问题,那么“年龄”和“收入”的相关性计算结果会完全由年轻人决定,从而产生误导。
- 最佳实践: 在使用成对删除之前,先进行缺失模式分析。使用
missingno库可视化缺失值的分布。
深度学习中的预处理思考
在深度学习领域,我们通常倾向于对缺失值进行插补(用均值、0 填充或使用 KNN),因为神经网络在处理规整的张量时效率最高。但是,成对删除在特征工程阶段依然非常有用。
例如,在构建循环神经网络(RNN)或时间序列模型之前,你可能想要选择最具预测能力的特征。你可以先用成对删除计算目标变量与各特征之间的相关性,以此作为特征筛选的依据,而不必因为某些特征有少量缺失就丢弃整个特征或整个样本。
总结与下一步
今天,我们深入探讨了成对删除这一强大的数据处理工具。它通过利用所有可用的信息对来计算统计量,避免了列表删除中常见的严重数据浪费问题。我们看到了它在相关性分析、协方差估计以及特征工程中的实际应用,并学习了如何通过 Python 代码高效实现它。
然而,我们也强调了它的局限性——特别是样本量不一致和可能产生的非正定矩阵问题。最重要的是,你必须时刻警惕缺失数据的机制(MCAR/MAR/MNAR),以确保你的统计推断是有效的。
关键要点:
- 成对删除能最大化数据利用率,特别是在 EDA 阶段。
- 它默认存在于 Pandas 的 INLINECODEfec95012 和 INLINECODEe5252cfc 方法中。
- 在涉及矩阵运算的复杂模型中需谨慎使用,注意检查协方差矩阵的数学性质。
- 结合缺失值可视化工具(如
missingno)来辅助决策。
希望这篇文章能帮助你在实际工作中更自信地处理缺失数据。下次当你面对布满 NaN 的数据框时,不妨试试成对删除,也许会有新的发现!