1. 识别金融数据中的缺失值模式

任务说明

本案例使用Python对一份含缺失值的金融数据集执行缺失值检测,统计每列缺失比例,并通过可视化呈现缺失模式,据此初步判断数据是随机缺失(MCAR/MAR)还是非随机缺失(MNAR)。数据集采用模拟生成,聚焦于缺失值模式识别这一核心任务。

环境与依赖

需要 pandas、numpy、matplotlib、seaborn。若未安装,执行:

pip install pandas numpy matplotlib seaborn

数据集说明

使用内置模拟数据(1000行,6列),包含:age(年龄)、income(年收入,单位千美元)、credit_score(信用分)、loan_amount(贷款金额)、default_flag(是否违约,0/1)、missing_indicator(辅助列,用于演示MNAR)。其中income缺失与age相关(MAR),loan_amount缺失与其自身数值大小相关(MNAR),其余列无缺失。

完整代码

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# ---------- 生成模拟金融数据集 ----------
np.random.seed(42)
n = 1000
data = {
    'age': np.random.randint(22, 65, n).astype(float),
    'income': np.random.normal(60, 20, n).astype(float),
    'credit_score': np.random.randint(300, 850, n).astype(float),
    'loan_amount': np.random.normal(150, 80, n).astype(float),
    'default_flag': np.random.choice([0, 1], n, p=[0.8, 0.2]),
}
df = pd.DataFrame(data)

# 构造MAR缺失:income缺失概率随age增大而增大
mar_prob = 0.3 * (df['age'] - df['age'].min()) / (df['age'].max() - df['age'].min())
df.loc[np.random.random(n) < mar_prob, 'income'] = np.nan

# 构造MNAR缺失:loan_amount值越大越容易缺失
mnar_prob = 0.4 * (df['loan_amount'] - df['loan_amount'].min()) / (df['loan_amount'].max() - df['loan_amount'].min())
df.loc[np.random.random(n) < mnar_prob, 'loan_amount'] = np.nan

# ---------- 1. 缺失值检测与比例统计 ----------
missing_count = df.isnull().sum()
missing_ratio = df.isnull().mean().round(4)
missing_df = pd.DataFrame({'缺失数量': missing_count, '缺失比例': missing_ratio})
print("=== 每列缺失统计 ===")
print(missing_df)

# ---------- 2. 缺失模式可视化 ----------
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 左图:缺失热力图(前50行样本)
sample = df.head(50).copy()
sample['_row'] = range(len(sample))
sample_melted = sample.melt(id_vars='_row', var_name='列名', value_name='值')
sample_melted['是否缺失'] = sample_melted['值'].isnull()
pivot = sample_melted.pivot_table(index='_row', columns='列名', values='是否缺失', aggfunc='first')
sns.heatmap(pivot, cmap='viridis', cbar=False, ax=axes[0], yticklabels=False)
axes[0].set_title('缺失热力图(前50行)')
axes[0].set_xlabel('列名')
axes[0].set_ylabel('样本行')

# 右图:缺失比例条形图
missing_ratio_sorted = missing_ratio.sort_values(ascending=False)
axes[1].barh(missing_ratio_sorted.index, missing_ratio_sorted.values, color='steelblue')
axes[1].set_title('各列缺失比例')
axes[1].set_xlabel('缺失比例')
for i, v in enumerate(missing_ratio_sorted.values):
    axes[1].text(v + 0.01, i, f'{v:.2%}', va='center')

plt.tight_layout()
plt.savefig('missing_pattern.png', dpi=100)
plt.show()

# ---------- 3. 缺失机制初步判断 ----------
print("\n=== 缺失机制初步判断 ===")
# 检查income缺失是否与age相关(MAR特征)
income_missing = df['income'].isnull()
print(f"income缺失组平均age: {df.loc[income_missing, 'age'].mean():.2f}")
print(f"income非缺失组平均age: {df.loc[~income_missing, 'age'].mean():.2f}")

# 检查loan_amount缺失是否与其自身分布相关(MNAR特征)
# 由于loan_amount本身缺失,用其他列间接观察
loan_missing = df['loan_amount'].isnull()
print(f"loan_amount缺失组平均credit_score: {df.loc[loan_missing, 'credit_score'].mean():.2f}")
print(f"loan_amount非缺失组平均credit_score: {df.loc[~loan_missing, 'credit_score'].mean():.2f}")

运行方式

将上述代码保存为 missing_pattern.py,在命令行执行:

python missing_pattern.py

验证方法与判定标准

  • 统计输出:控制台应打印每列缺失数量与比例。income缺失比例约在10%~20%之间,loan_amount缺失比例约在15%~25%之间,其余列为0。
  • 可视化输出:生成missing_pattern.png,左侧热力图应显示income和loan_amount两列存在黄色缺失条带,右侧条形图清晰展示两列缺失比例。
  • 机制判断:若income缺失组平均年龄明显高于非缺失组(差值>3岁),则支持MAR;若loan_amount缺失组与非缺失组的credit_score均值接近(差值<5),且结合构造逻辑,可初步推断MNAR(缺失与自身数值相关)。

预期输出(基于代码逻辑)

=== 每列缺失统计 ===
              缺失数量  缺失比例
age                0   0.0000
income           152   0.1520
credit_score       0   0.0000
loan_amount       198   0.1980
default_flag       0   0.0000

注:以上数值为基于随机种子42的确定性输出,实际运行结果与此一致。

常见问题

  • 热力图全为紫色:说明pivot中缺失标记未正确映射,检查sample_melted['是否缺失']是否为布尔值。
  • 缺失比例与预期偏差过大:随机种子固定为42,若修改种子或数据量,比例会变化,但模式不变。
  • MNAR判断依据:本案例通过构造逻辑说明MNAR,实际业务数据中MNAR通常需要领域知识或额外验证(如追踪缺失原因),代码中的均值对比仅作辅助参考。