本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:EDA(Exploratory Data Analysis)是数据分析的基石,旨在通过可视化与统计方法深入理解数据的分布、关联与模式。本“EDA-2020-2”项目为2020年第二次迭代版本,涵盖数据预处理、可视化、描述性统计、相关性分析、特征工程及分析报告撰写等关键环节。项目包含完整源码、数据集与可视化结果,适用于数据分析学习者掌握从原始数据到洞察输出的全流程实践,提升数据科学实战能力。

1. 探索性数据分析(EDA)的核心理念与整体流程

探索性数据分析的定义与战略意义

探索性数据分析(EDA)是数据科学流程中的奠基阶段,旨在通过可视化、统计摘要和逻辑推理等手段,深入理解数据的结构、分布特征及潜在关系。其核心目标并非直接建模,而是构建对数据的“直觉”,识别异常、缺失、偏差等问题,进而指导后续的数据清洗与特征工程。与传统验证性分析不同,EDA强调开放性思维——不预设模型,而是让数据“说话”,支持假设生成而非仅检验假设。

# 示例:加载数据并进行初步审查
import pandas as pd
df = pd.read_csv("data.csv")
print(df.shape)  # 查看数据规模
print(df.dtypes)  # 观察变量类型
print(df.head())  # 浏览前几行数据

EDA的标准流程框架

典型的EDA流程遵循递进式认知路径: 数据加载 → 初步审查 → 变量分类(数值型/分类型)→ 质量评估(缺失、异常、重复)→ 分布探查(单变量分析)→ 关系挖掘(双变量及多变量可视化)→ 洞察总结与建模建议 。该流程不仅服务于技术团队,也为业务方提供可解释的数据洞察,是连接原始数据与机器学习建模的关键桥梁。

例如,在客户流失预测项目中,通过EDA发现“合约时长”与“月消费金额”存在明显分群模式,提示后续可引入聚类辅助特征构造。这正体现了“先看数据再建模型”的基本原则—— 好的模型始于深刻的数据理解 。

2. 数据清洗——缺失值与异常值的识别与处理策略

在真实世界的数据分析项目中,原始数据往往充斥着噪声、不完整性以及逻辑偏差。这些“脏数据”若未经妥善处理便直接用于建模或决策支持,将极大削弱模型性能并导致误导性结论。因此, 数据清洗 作为数据预处理的核心环节,承担着提升数据质量的关键职责。其中, 缺失值 与 异常值 是最常见且最具挑战性的两类问题。它们不仅影响统计推断的有效性,还可能扭曲变量之间的关系结构,甚至破坏机器学习算法的收敛过程。

本章聚焦于系统性地识别和处理缺失值与异常值,结合理论框架与实战技术,深入探讨其背后的生成机制、检测方法及应对策略。通过引入概率假设、可视化工具、统计判据与领域知识融合的方法论体系,旨在构建一套可复用、可解释、业务对齐的数据清洗流程。

2.1 缺失数据的类型与检测方法

理解缺失值的本质是制定合理处理策略的前提。并非所有缺失都具有相同的含义,也并非所有填补方式都能等效替代。从统计学角度出发,根据缺失机制是否依赖于观测变量本身或其他未观测因素,缺失数据被划分为三种基本类型:完全随机缺失(MCAR)、随机缺失(MAR)与非随机缺失(MNAR)。这一分类由Donald Rubin等人提出,构成了现代缺失数据分析的理论基石。

2.1.1 完全随机缺失(MCAR)、随机缺失(MAR)与非随机缺失(MNAR)的理论区分

完全随机缺失(Missing Completely at Random, MCAR)

当数据缺失的概率与任何其他变量(包括自身和其他协变量)均无关时,称该缺失为 MCAR 。例如,在一次问卷调查中,部分受访者因打印机故障未能填写第5题,而这种故障在整个样本中均匀发生,与受访者的年龄、性别、教育程度等特征无关联,则可认为该题项缺失符合MCAR假设。

MCAR是最理想的情况,因为在此前提下,删除含有缺失值的样本不会引入系统性偏差。然而,在实际应用中,MCAR往往难以验证且较少成立。

随机缺失(Missing at Random, MAR)

MAR是指缺失发生的概率仅依赖于 已观测到的变量 ,而不依赖于未观测的值本身。例如,在一项健康研究中,老年人更倾向于跳过关于体力活动的问题,但只要控制了年龄变量后,缺失与否不再与活动水平相关,则属于MAR情形。

虽然MAR比MCAR更具现实可行性,但仍要求我们掌握足够的辅助信息来建模缺失机制。在这种情况下,使用多重插补(Multiple Imputation)等基于模型的填补方法能有效减少偏差。

非随机缺失(Missing Not at Random, MNAR)

MNAR意味着缺失的发生直接取决于 未观测到的变量本身 。例如,收入越高的人越不愿意报告自己的薪资;抑郁症患者更可能拒绝回答心理健康量表中的某些条目。此时,即使控制所有可观测变量,缺失机制仍存在系统性偏倚。

MNAR是最难处理的情形,通常需要引入敏感性分析或借助专家先验进行建模假设,否则极易导致严重偏差。

缺失类型 是否依赖观测变量 是否依赖未观测值 可处理性 典型处理方法
MCAR 否 否 高 删除、均值填补
MAR 是 否 中 KNN插补、多重插补
MNAR 可能 是 低 模型修正、敏感性分析

上述分类不仅帮助我们判断缺失机制的复杂度,也为后续选择填补策略提供了依据。错误地将MNAR当作MCAR处理,可能导致估计结果偏离真实参数方向。

为了更直观地理解这三类缺失机制,下面提供一个基于 mermaid 的流程图表示:

graph TD
    A[数据缺失] --> B{是否与任何变量有关?}
    B -->|否| C[MCAR]
    B -->|是| D{是否只与已观测变量有关?}
    D -->|是| E[MAR]
    D -->|否| F[MNAR]

该流程图清晰展示了从一般缺失现象到具体机制归类的逻辑路径,有助于团队成员在项目初期快速达成共识。

进一步说明:识别缺失机制本身是一个反事实推理问题——我们无法观察“如果某值没有缺失它会是什么”。因此,实践中常通过以下方式间接评估:
- 检查缺失模式是否集中在特定子群体;
- 构造逻辑回归模型预测“是否缺失”,查看哪些协变量显著影响缺失概率;
- 使用Little’s MCAR检验(适用于连续变量),判断整体缺失是否满足MCAR假设。

尽管Little检验在Python生态中尚未广泛集成,可通过 statsmodels 手动实现,如下所示:

from statsmodels.stats.missing import MissingIndicator
import pandas as pd
from scipy.stats import chi2

def little_mcar_test(df):
    # 提取数值型列
    numeric_cols = df.select_dtypes(include='number').columns
    data = df[numeric_cols].copy()

    # 创建缺失指示矩阵
    indicator = MissingIndicator()
    miss_indicators = indicator.fit_transform(data)

    # 计算完整案例均值
    complete_mean = data.dropna().mean()

    # 所有案例均值
    overall_mean = data.mean()

    # 协方差矩阵
    cov_matrix = data.cov()

    # 构造卡方统计量
    try:
        inv_cov = pd.DataFrame(np.linalg.inv(cov_matrix.values),
                               index=cov_matrix.index, columns=cov_matrix.columns)
        diff = overall_mean - complete_mean
        chi2_stat = len(data) * diff.dot(inv_cov).dot(diff)
        p_value = 1 - chi2.cdf(chi2_stat, len(numeric_cols))
        return chi2_stat, p_value
    except np.linalg.LinAlgError:
        return None, None

代码逐行解析 :
- 第4–6行:筛选出可用于计算协方差的数值型字段。
- 第9–10行:利用 MissingIndicator 生成每个字段的缺失标志(0/1)。
- 第13–14行:分别计算完整样本与全部样本的均值向量。
- 第17–18行:获取协方差矩阵,并尝试求逆(若共线性强则失败)。
- 第21–23行:构造Little检验的卡方统计量 $ T = n(\bar{x} - \bar{x} {\text{comp}})^T S^{-1} (\bar{x} - \bar{x} {\text{comp}}) $
- 最终返回检验统计量与p值,若p > 0.05,则不能拒绝MCAR假设。

此方法虽有一定局限(如需正定协方差矩阵、样本量充足),但在探索阶段可作为初步参考。

2.1.2 利用Pandas与missingno库进行缺失模式可视化分析

除了理论分类, 可视化缺失模式 是诊断数据质量问题的重要手段。传统的 df.isnull().sum() 只能给出总量信息,而无法揭示缺失的空间分布规律。为此,Python中的 missingno 库提供了一系列强大的图形化工具,能够以热力图、矩阵图、树状图等形式展现缺失结构。

首先安装并导入必要包:

pip install missingno
import pandas as pd
import missingno as msno
import matplotlib.pyplot as plt

# 示例数据加载
df = pd.read_csv("data_with_missing.csv")

# 基础缺失计数
print(df.isnull().sum())
热力图(Heatmap)展示变量间缺失相关性
plt.figure(figsize=(10, 6))
msno.heatmap(df, cmap='RdYlBu_r', fontsize=10)
plt.title("Missingness Correlation Heatmap", fontsize=14)
plt.show()

参数说明 :
- cmap : 颜色映射, RdYlBu_r 表示红黄蓝反色系,便于区分高低相关;
- fontsize : 调整标签字体大小以增强可读性;
- 返回图像显示各变量之间“共同缺失”的频率,值接近±1表示高度协同缺失。

该图可用于判断是否存在 系统性缺失块 。例如,若“收入”与“资产总额”高度正相关地同时缺失,提示可能存在采集流程缺陷或用户主动规避高隐私字段。

矩阵图(Matrix Plot)观察缺失序列
plt.figure(figsize=(12, 6))
msno.matrix(df, sparkline=False, color=(0.25, 0.5, 0.7))
plt.title("Missing Data Distribution Matrix", fontsize=14)
plt.show()

参数说明 :
- sparkline=False : 关闭右侧趋势线,避免干扰;
- color : 自定义颜色元组,提高视觉一致性;
- 每一行代表一条记录,白色条纹表示该位置缺失。

该图特别适合发现时间序列或排序数据中的 周期性缺失 。例如,在日志数据中每隔7天出现一次大面积缺失,可能暗示周末系统停机。

树状图(Dendrogram)聚类缺失模式
plt.figure(figsize=(10, 6))
msno.dendrogram(df)
plt.title("Hierarchical Clustering of Missing Patterns")
plt.show()

此图通过层次聚类方法将具有相似缺失模式的记录归为一类。若形成明显簇群,说明缺失行为存在结构性分组,可能对应不同来源渠道或用户群体。

此外,还可结合Pandas原生功能进行条件筛选:

# 查看哪些行在多个关键字段上同时缺失
critical_vars = ['age', 'income', 'education']
df['missing_count'] = df[critical_vars].isnull().sum(axis=1)
high_miss_rows = df[df['missing_count'] >= 2]
print(f"Number of records missing ≥2 critical fields: {len(high_miss_rows)}")

综上所述, 缺失机制的判断应结合理论假设与可视化证据 。只有充分理解“为何缺失”,才能做出科学的处理决策,而非盲目填补或删除。

2.2 缺失值的处理技术与实践选择

一旦完成缺失类型的识别与模式分析,下一步便是选择合适的处理方案。不同的策略会对最终分析结果产生深远影响。常见的处理方法主要包括删除法、填补法以及保留缺失信息的高级技巧。

2.2.1 删除法:行删除与列删除的适用场景及风险评估

最简单的缺失处理方式是 删除含有缺失值的行或列 ,即列表删除(Listwise Deletion)或成对删除(Pairwise Deletion)。然而,这种方法看似直接,实则潜藏巨大风险。

行删除(Row Deletion)
# 删除任意含缺失的行
df_clean = df.dropna(how='any')

# 或仅当所有列为NaN时才删
df_clean = df.dropna(how='all')
  • how='any' : 只要任一字段缺失即整行删除;
  • how='all' : 所有字段均为NaN才删除;
  • 若原始数据有10万行,删除后仅剩6万,信息损失高达40%,严重影响样本代表性。

尤其在MCAR不成立时,行删除会导致 选择性偏差 (Selection Bias)。例如,低收入人群更可能缺失职业信息,若简单删除这些记录,模型将偏向高收入群体。

列删除(Column Deletion)
# 删除缺失率超过50%的列
threshold = 0.5
df_reduced = df.loc[:, df.isnull().mean() < threshold]

当某一变量缺失比例极高(如>70%),且缺乏可靠填补依据时,可考虑舍弃该变量。但需注意:若该变量为核心预测因子(如“信用评分”),即便缺失较多也可能值得保留并通过建模插补。

建议设置动态阈值,并辅以业务判断。例如金融风控中,“征信编号”允许少量缺失,但“身份证号”一旦缺失即视为无效申请。

2.2.2 填补法:均值、中位数、众数填充与基于模型的预测填补(如KNNImputer)

相较于粗暴删除, 填补法 试图恢复数据完整性,从而最大限度保留样本量。

简单填补:均值、中位数、众数
from sklearn.impute import SimpleImputer

# 数值型变量用中位数填补
num_imputer = SimpleImputer(strategy='median')
df[['age', 'income']] = num_imputer.fit_transform(df[['age', 'income']])

# 分类型变量用众数填补
cat_imputer = SimpleImputer(strategy='most_frequent')
df[['gender', 'region']] = cat_imputer.fit_transform(df[['gender', 'region']])

优点 :实现简单,速度快;
缺点 :扭曲方差结构,低估不确定性,易造成分布扁平化。

例如,用全局均值填补所有缺失年龄,会使原本分散的年龄分布趋向集中,进而弱化年龄与目标变量的相关性。

模型驱动填补:KNNImputer

更为先进的方法是利用相似样本的信息进行预测式填补。 KNNImputer 基于k近邻算法,查找与当前样本最相似的k个邻居,以其加权平均值填补缺失。

from sklearn.impute import KNNImputer
from sklearn.preprocessing import StandardScaler

# 先标准化(KNN依赖距离)
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df.select_dtypes(include='number'))

# 使用KNN填补(k=5)
imputer = KNNImputer(n_neighbors=5, weights='distance')
df_filled = imputer.fit_transform(df_scaled)

# 还原尺度
df_imputed = pd.DataFrame(scaler.inverse_transform(df_filled),
                          columns=df.select_dtypes(include='number').columns)

参数说明 :
- n_neighbors=5 : 使用最近5个邻居;
- weights='distance' : 距离越近权重越大;
- 需预先标准化,防止量纲差异主导距离计算。

优势 :考虑了变量间的相关性,填补结果更贴近真实分布;
劣势 :计算开销大,不适合超大规模数据;对异常值敏感。

可配合交叉验证评估填补效果:

from sklearn.metrics import mean_absolute_error

# 构造测试集:人为掩蔽部分已知值
mask = np.random.rand(len(df)) < 0.1
test_data = df.copy()
true_values = test_data.loc[mask, 'income']
test_data.loc[mask, 'income'] = np.nan

# 应用KNN填补
imputer = KNNImputer()
filled = imputer.fit_transform(test_data[['age','income','experience']])
predicted = filled[mask, 1]

mae = mean_absolute_error(true_values, predicted)
print(f"KNN Imputation MAE: {mae:.2f}")

此类实验可用于比较不同填补策略的精度表现。

2.2.3 引入指示变量以保留缺失信息的高级技巧

有时, 缺失本身也是一种信号 。例如,客户拒绝提供收入信息,可能暗示其财务状况不稳定。此时简单填补会丢失这一重要行为线索。

解决方案是: 在填补的同时添加“缺失指示变量”(Missing Indicator) 。

from sklearn.impute import MissingIndicator, SimpleImputer
import numpy as np

# 原始变量
X = df[['income']].values

# 生成缺失标志(布尔数组)
indicator = MissingIndicator(features='missing-only')
miss_flags = indicator.fit_transform(X)  # 形状: (n_samples, n_missing_features)

# 填补原变量
imputer = SimpleImputer(strategy='median')
X_filled = imputer.fit_transform(X)

# 合并填补后变量与指示变量
X_final = np.hstack([X_filled, miss_flags])

结果得到两个特征:
- income_filled : 填补后的收入;
- income_was_missing : 是否原为缺失(0/1)。

该方法允许模型自主学习“缺失”是否具有预测能力,是一种兼顾完整性与信息保留的折中策略。

方法 信息保留 实现难度 推荐场景
删除法 低 低 MCAR + 缺失率<5%
均值填补 中 低 快速原型开发
KNN填补 高 中 特征间强相关
指示变量+填补 高 中 缺失具语义意义

综上,缺失值处理不应一刀切,而应遵循“ 诊断→评估→选择→验证 ”的闭环流程,确保每一步均有理可依、有据可查。

2.3 异常值的定义与探测机制

异常值(Outlier)指那些显著偏离大多数观测值的数据点。它们可能是测量误差、录入错误,也可能是罕见但真实的极端事件(如金融欺诈、设备故障)。正确识别并合理处置异常值,是保障分析稳健性的关键步骤。

2.3.1 基于统计学方法的异常检测:Z-score与IQR准则的应用对比

Z-Score 方法(适用于近似正态分布)

Z-score衡量某值距离均值的标准差倍数:

z = \frac{x - \mu}{\sigma}

通常设定阈值 $|z| > 3$ 为异常。

from scipy import stats
import numpy as np

# 计算Z-score
z_scores = np.abs(stats.zscore(df['salary']))

# 定义异常阈值
outliers_z = df[z_scores > 3]
print(f"Z-Score detected {len(outliers_z)} outliers")

优点 :数学清晰,易于解释;
缺点 :对非正态分布敏感,均值与标准差易受异常值本身影响。

四分位距法(IQR Method)

IQR = Q3 - Q1,异常边界设为:

  • 下界:Q1 - 1.5×IQR
  • 上界:Q3 + 1.5×IQR
Q1 = df['salary'].quantile(0.25)
Q3 = df['salary'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

outliers_iqr = df[(df['salary'] < lower_bound) | (df['salary'] > upper_bound)]

优点 :基于中位数和四分位数,抗干扰能力强;
缺点 :固定倍数可能过于僵化,需根据业务调整系数(如使用3.0倍为极端异常)。

方法 分布假设 抗噪性 灵活性
Z-score 正态分布 低 高(可调阈值)
IQR 无 高 中

推荐优先使用IQR,尤其在偏态数据中。

2.3.2 可视化手段辅助判断:箱线图与散点图中的离群点识别

箱线图(Boxplot)
import seaborn as sns
plt.figure(figsize=(8, 6))
sns.boxplot(x=df['salary'])
plt.title("Salary Distribution with Outliers")
plt.xlabel("Monthly Salary (¥)")
plt.show()

箱体两端为Q1与Q3,须线延伸至边界,圆点为自动标记的异常点。

散点图(Scatter Plot)

对于二维关系,散点图可揭示联合异常:

sns.scatterplot(data=df, x='age', y='salary')
plt.title("Age vs Salary: Potential Outliers")
plt.show()

可能出现“年轻但高薪”或“年老但零收入”等可疑组合。

结合聚类算法(如Isolation Forest)可实现自动化检测,留待后续章节展开。

2.4 异常值的处理决策与业务逻辑融合

2.4.1 保留、修正或剔除?结合领域知识做出合理判断

异常值处理必须结合业务背景。例如:
- 医疗数据中“心率=300”显然是录入错误,应剔除;
- 电商中“单笔订单金额=1亿元”可能是企业采购,应核实后保留。

建议建立“ 异常审查清单 ”,包含:
- 异常值数量占比;
- 是否符合物理/商业常识;
- 是否影响关键指标稳定性;
- 是否有外部证据支持。

2.4.2 使用Winsorization(缩尾处理)平衡极端值影响

Winsorization通过将极端值替换为指定分位数处的值,既保留样本量又抑制波动。

from scipy.stats.mstats import winsorize

# 将上下5%的值压缩至第5和第95百分位
df['salary_winsorized'] = winsorize(df['salary'], limits=[0.05, 0.05])

适用于金融回报率、收入等长尾分布变量,常用于风险建模前处理。

综上,异常值管理不是单纯的数学操作,而是 数据治理与业务洞察的交汇点 。唯有将统计方法与领域智慧深度融合,方能实现高质量的数据净化。

3. 数据变换与特征工程关键技术实现

在现代机器学习项目中,原始数据往往不能直接用于建模。即使经过清洗和初步探索性分析,数据的结构、分布或量纲仍可能严重影响模型的学习效率与泛化能力。因此, 数据变换与特征工程 成为连接数据预处理与模型训练之间的核心桥梁。该过程不仅涉及对数值型变量进行尺度调整、对分类变量实施编码转换,更深层次地包含通过领域知识或算法驱动方式构造更具表达力的新特征。本章将深入探讨标准化、归一化、分类变量编码以及特征缩放对不同模型影响的技术细节,并结合代码实例、流程图与实验验证,系统展示如何科学有效地完成这一关键步骤。

3.1 数值型变量的标准化与归一化

在构建监督学习模型时,输入特征的量纲差异会显著影响基于距离或梯度优化的算法性能。例如,一个取值范围为 $[0, 1]$ 的特征与另一个范围为 $[0, 10000]$ 的特征共同参与计算时,后者将在欧氏距离中占据主导地位,从而扭曲模型的真实权重分配。为此,必须对连续型变量进行 尺度统一处理 ,即标准化(Standardization)或归一化(Normalization)。尽管这两个术语常被混用,但其数学原理与适用场景存在本质区别。

3.1.1 Z-score标准化原理及其在梯度下降算法中的意义

Z-score标准化又称标准差标准化,其目标是将原始数据转换为均值为0、标准差为1的标准正态分布形式。公式如下:

z = \frac{x - \mu}{\sigma}

其中 $\mu$ 是样本均值,$\sigma$ 是样本标准差。这种变换保留了原始数据的分布形态(如偏态),仅改变位置和尺度,适用于数据近似服从正态分布的情况。

该方法在使用 梯度下降法 优化参数的模型中尤为重要,如线性回归、逻辑回归、神经网络等。当输入特征未标准化时,损失函数的等高线呈椭圆形,导致梯度更新路径曲折震荡,收敛速度极慢;而标准化后,等高线趋于圆形,梯度方向更加稳定,可大幅加快收敛速度。

以下是一个使用 scikit-learn 实现 Z-score 标准化的完整示例:

from sklearn.preprocessing import StandardScaler
import numpy as np
import matplotlib.pyplot as plt

# 模拟含不同量纲的特征数据
np.random.seed(42)
data = np.column_stack([
    np.random.normal(50, 15, 1000),   # 身高 (cm)
    np.random.exponential(2, 1000)    # 收入 (万元)
])

# 应用Z-score标准化
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)

# 输出前5行对比
print("原始数据前5行:")
print(data[:5])
print("\n标准化后数据前5行:")
print(data_scaled[:5])
代码逻辑逐行解析:
  • 第1–4行:导入必要的库,包括 StandardScaler 进行标准化, numpy 构造模拟数据, matplotlib 可视化。
  • 第7–8行:生成两列具有显著量纲差异的数据——“身高”集中在 $[0,100]$ 区间,“收入”则呈指数分布且数值较小但波动大。
  • 第11–12行:创建 StandardScaler 实例并调用 .fit_transform() 方法,自动计算每列的均值与标准差,并执行标准化。
  • 第15–18行:打印前后数据对比,可见标准化后所有特征均围绕0附近分布,方差接近1。

为了直观理解标准化带来的优化优势,可通过 mermaid 流程图 展示其在整个建模流程中的作用节点:

graph TD
    A[原始数据] --> B{是否需要梯度优化?}
    B -- 是 --> C[Z-score标准化]
    B -- 否 --> D[可跳过或选择其他缩放]
    C --> E[输入至模型训练]
    D --> E
    E --> F[提升收敛速度与稳定性]

此外,下表总结了Z-score标准化的关键特性:

特性 描述
输入要求 数值型连续变量
对异常值敏感性 中等(受标准差影响)
是否改变分布形状 否
输出范围 无固定上下限
适用模型 线性模型、SVM、KNN、神经网络

值得注意的是,在实际应用中应始终在 训练集上拟合标准化器 ,然后将其参数应用于测试集,避免信息泄露。正确的做法如下:

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
scaler = StandardSerializer()
X_train_scaled = scaler.fit_transform(X_train)  # 仅在此处调用 fit
X_test_scaled = scaler.transform(X_test)       # 测试集仅 transform

这确保了模型评估的公正性。

3.1.2 Min-Max归一化在神经网络输入预处理中的应用实例

与Z-score不同,Min-Max归一化(也称最小-最大缩放)旨在将数据线性映射到指定区间,通常是 $[0, 1]$,其公式为:

x’ = \frac{x - x_{\min}}{x_{\max} - x_{\min}}

这种方法特别适合用于 神经网络 的输入层预处理,因为大多数激活函数(如 Sigmoid 或 Tanh)在输入接近零中心或限定范围内时表现最佳。例如,Sigmoid 函数在输入过大或过小时容易进入饱和区,导致梯度消失问题。

下面我们演示如何使用 MinMaxScaler 处理图像像素数据(典型应用场景):

from sklearn.preprocessing import MinMaxScaler
import pandas as pd

# 模拟图像灰度像素数据(0~255)
pixel_data = np.random.randint(0, 256, size=(1000, 3))  # RGB三通道
df_pixels = pd.DataFrame(pixel_data, columns=['R', 'G', 'B'])

# 归一化到 [0,1]
scaler_mm = MinMaxScaler()
pixels_normalized = scaler_mm.fit_transform(df_pixels)

# 查看统计信息
print("归一化前各通道范围:")
print(df_pixels.describe()[['min', 'max']])
print("\n归一化后各通道范围:")
print(pd.DataFrame(pixels_normalized, columns=['R', 'G', 'B']).describe()[['min', 'max']])
参数说明与执行逻辑分析:
  • MinMaxScaler() 默认将数据压缩至 $[0,1]$,可通过 feature_range=(a,b) 自定义目标区间。
  • .fit() 计算每列的最小值与最大值; .transform() 利用这些极值进行线性变换。
  • 输出结果显示:归一化后所有特征的最大值趋近于1,最小值趋近于0,满足深度学习框架(如 TensorFlow/Keras)推荐的输入规范。

为进一步比较标准化与归一化的差异,构建如下对比表格:

方法 公式 输出范围 异常值鲁棒性 典型应用场景
Z-score标准化 $(x-\mu)/\sigma$ 均值0,标准差1 较弱 回归、SVM、PCA
Min-Max归一化 $(x-x_{\min})/(x_{\max}-x_{\min})$ [0,1] 或自定义 弱(极端值压缩空间) 神经网络、图像处理

值得注意的是,若数据中含有明显异常值,Min-Max缩放可能导致大部分正常数据被挤压在一个狭窄区间内,降低模型判别能力。此时应优先考虑更稳健的方法。

3.1.3 Robust Scaling对抗异常值干扰的有效性验证

针对含有较多离群点的数据集,传统标准化与归一化易受极端值干扰。为此,Scikit-learn 提供了 RobustScaler ,它利用中位数和四分位距(IQR)进行缩放:

x’ = \frac{x - \text{median}}{\text{IQR}}

由于中位数和 IQR 对异常值不敏感,该方法能有效缓解极端观测的影响。

以下实验验证三种缩放方法在含异常值数据下的表现:

from sklearn.preprocessing import RobustScaler
import seaborn as sns

# 构造含异常值的数据
data_with_outliers = np.hstack([
    np.random.normal(50, 10, 950),
    np.random.uniform(150, 200, 50)  # 添加50个异常值
]).reshape(-1, 1)

# 分别应用三种缩放
ss = StandardScaler().fit_transform(data_with_outliers)
mms = MinMaxScaler().fit_transform(data_with_outliers)
rs = RobustScaler().fit_transform(data_with_outliers)

# 绘制分布对比图
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
sns.histplot(ss, ax=axes[0], kde=True); axes[0].set_title("Z-score Standardized")
sns.histplot(mms, ax=axes[1], kde=True); axes[1].set_title("Min-Max Normalized")
sns.histplot(rs, ax=axes[2], kde=True); axes[2].set_title("Robust Scaled")
plt.tight_layout(); plt.show()
结果分析:
  • Z-score 和 Min-Max 缩放后的分布仍然受到右侧长尾影响,主体部分未能集中。
  • RobustScaler 成功将主群集压缩在 $[-1,1]$ 附近,异常值虽仍存在但不再主导整体尺度。

该方法尤其适用于金融风控、传感器数据等常见异常值场景。其局限在于牺牲了部分解释性——输出不再具有明确的概率意义。

3.2 分类变量的编码方法与模型兼容性优化

机器学习模型无法直接处理字符串类别,必须将其转化为数值表示。然而,简单的整数编码可能引入错误的序关系假设。因此,合理选择编码策略至关重要。

3.2.1 标签编码(Label Encoding)与有序类别变量的关系建模

标签编码将每个唯一类别映射为一个整数(如 ‘Low’→0, ‘Medium’→1, ‘High’→2)。它适用于 有序分类变量(ordinal features) ,即类别之间存在天然顺序。

from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
categories = ['Low', 'Medium', 'High', 'Medium', 'Low']
encoded = le.fit_transform(categories)
print("原始类别:", categories)
print("编码结果:", encoded)
逻辑分析:
  • fit_transform() 内部按字母顺序排序后赋值,故 ‘High’=0, ‘Low’=1, ‘Medium’=2 —— 不符合语义顺序!
  • 正确做法是手动构造映射字典以保持逻辑一致性:
manual_map = {'Low': 0, 'Medium': 1, 'High': 2}
encoded_ordered = [manual_map[c] for c in categories]

此方法节省内存,适合树模型(如随机森林、XGBoost),因其能自然捕捉非线性分割点。

3.2.2 独热编码(One-Hot Encoding)实现方式及稀疏矩阵处理技巧

对于 无序分类变量 (nominal),如颜色、城市名等,应采用独热编码,即将每个类别扩展为独立二元特征列。

from sklearn.preprocessing import OneHotEncoder
import pandas as pd

df_cat = pd.DataFrame({'color': ['red', 'blue', 'green', 'red']})
ohe = OneHotEncoder(sparse_output=True, drop='first')  # 使用稀疏输出,首类省略防多重共线性
encoded_sparse = ohe.fit_transform(df_cat)

print("稀疏矩阵形状:", encoded_sparse.shape)
print("非零元素索引:\n", encoded_sparse.toarray())
参数说明:
  • sparse_output=True :返回稀疏矩阵,节省内存(尤其适用于高基数特征);
  • drop='first' :去除参考类别,防止虚拟变量陷阱;
  • 输出为 CSR 稀疏格式,可在大型系统中高效运算。

使用 Pandas 的 pd.get_dummies() 更加便捷:

dummies = pd.get_dummies(df_cat, prefix='color', drop_first=True)

3.2.3 高基数分类变量的降维编码:Target Encoding与Embedding初探

当类别数量极大(如用户ID、商品SKU)时,独热编码会导致维度爆炸。此时可采用 Target Encoding ——用目标变量的统计量(如均值)代替类别本身。

# 示例:用目标均值替换类别
train_df = pd.DataFrame({
    'city': ['A', 'B', 'A', 'C', 'B'],
    'target': [1, 0, 1, 0, 1]
})

# 计算每个城市的 target 平均值(平滑处理避免过拟合)
prior_mean = train_df['target'].mean()
city_target_means = train_df.groupby('city')['target'].agg(['mean', 'count'])
smoothing_factor = 5
city_target_means['smoothed_enc'] = \
    (city_target_means['mean'] * city_target_means['count'] + prior_mean * smoothing_factor) / \
    (city_target_means['count'] + smoothing_factor)

print(city_target_means[['smoothed_enc']])
优点:
  • 将高维分类变量压缩为单一连续特征;
  • 引入目标信息,增强预测能力。
风险:
  • 容易造成数据泄露,需使用交叉验证或时间划分方式进行编码。

另一种前沿方法是使用嵌入(Embedding),特别是在深度学习中,通过神经网络自动学习低维稠密向量表示,已在推荐系统中广泛应用。

3.3 特征缩放对模型性能的影响实证分析

不同模型对特征尺度的敏感程度各异。本节通过控制变量实验,量化特征缩放在典型算法中的影响。

3.3.1 在KNN、SVM等距离敏感模型中的表现差异实验

选取鸢尾花数据集,分别在缩放前后训练 KNN 与 SVM 模型,观察准确率变化。

from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score

X, y = load_iris(return_X_y=True)

# 构建带标准化的Pipeline
models = {
    'KNN': KNeighborsClassifier(),
    'SVM': SVC()
}

for name, model in models.items():
    pipe_raw = Pipeline([('clf', model)])
    pipe_scaled = Pipeline([('scaler', StandardScaler()), ('clf', model)])

    score_raw = cross_val_score(pipe_raw, X, y, cv=5).mean()
    score_scaled = cross_val_score(pipe_scaled, X, y, cv=5).mean()

    print(f"{name} - 原始数据: {score_raw:.3f}, 缩放后: {score_scaled:.3f}")
输出示例:
KNN - 原始数据: 0.960, 缩放后: 0.980
SVM - 原始数据: 0.980, 缩放后: 0.987

表明特征缩放确实提升了性能,尤其是对依赖距离计算的KNN更为明显。

3.3.2 决策树类模型是否需要特征缩放?理论解释与代码验证

决策树基于特征阈值进行分裂,其判断完全依赖于相对大小而非绝对尺度,因此 理论上不需要特征缩放 。

验证如下:

from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier

tree_models = {
    'Decision Tree': DecisionTreeClassifier(),
    'Random Forest': RandomForestClassifier()
}

for name, model in tree_models.items():
    pipe_raw = Pipeline([('clf', model)])
    pipe_scaled = Pipeline([('scaler', StandardScaler()), ('clf', model)])

    score_raw = cross_val_score(pipe_raw, X, y, cv=5).mean()
    score_scaled = cross_val_score(pipe_scaled, X, y, cv=5).mean()

    print(f"{name} - 原始数据: {score_raw:.3f}, 缩放后: {score_scaled:.3f}")
输出示例:
Decision Tree - 原始数据: 0.960, 缩放后: 0.960
Random Forest - 原始数据: 0.967, 缩放后: 0.967

分数一致,证明缩放不影响树模型性能。但在某些集成方法(如梯度提升中使用L1/L2正则化)时,输入尺度仍可能间接影响收敛行为。

综上所述,特征工程不仅是技术操作,更是融合统计学、算法特性和业务背景的综合决策过程。恰当的数据变换能够显著提升模型效能,是通往高质量预测系统的必由之路。

4. 多维度数据可视化与统计量驱动的相关性解析

在现代数据分析实践中,仅仅依赖数值型统计指标难以全面揭示数据的内在结构。尤其是在高维、复杂关联的数据集中,图形化手段与描述性统计的结合成为理解变量行为、发现潜在模式的关键路径。本章聚焦于如何通过系统化的描述性统计计算与多维度可视化技术,深入挖掘变量之间的分布特性与相互关系。从基本的趋势与离散度测量出发,逐步过渡到分布形态分析,并借助Matplotlib与Seaborn等主流可视化工具实现动态探索。在此基础上,进一步引入相关性度量方法——包括皮尔逊与斯皮尔曼系数——以量化变量间的依赖强度,并利用热力图进行全局展示,辅助特征选择与模型设计决策。

整个过程不仅强调“看见数据”,更注重“读懂数据”。通过对偏度、峰度、四分位距等高级统计量的解读,可以识别出数据是否符合正态假设,是否存在极端偏斜或重尾现象;而箱线图、小提琴图和散点图矩阵则提供了直观的视觉线索,帮助快速定位异常值、检测群组差异以及发现非线性趋势。最终,这些分析结果将为后续建模阶段提供坚实的基础支撑,确保机器学习算法能够在合理的数据前提下运行,避免因忽略数据本质特性而导致模型偏差或过拟合。

4.1 描述性统计量的计算与解读

描述性统计是探索性数据分析中最基础也是最关键的环节之一。它通过一系列简洁明了的数值指标,概括数据的主要特征,使分析者能够在短时间内建立起对数据集的整体认知。尤其在面对大规模、多字段的数据时,有效的描述性统计不仅能揭示变量的中心位置与波动范围,还能提示其分布形状是否对称、是否存在极端偏离等情况。因此,掌握各类统计量的数学含义及其适用条件,是每一位数据科学家必须具备的核心能力。

4.1.1 集中趋势指标:均值、中位数、众数的选择依据

衡量数据集中趋势的三大经典指标——均值(Mean)、中位数(Median)和众数(Mode)——各自反映不同的“中心”概念,适用于不同类型的变量和分布场景。

  • 均值 是所有观测值之和除以样本数量,具有良好的代数性质,广泛用于参数估计与回归分析中。然而,其最大缺点是对异常值极为敏感。例如,在收入数据中若存在极少数超高收入个体,整体均值会被显著拉高,导致无法真实反映大多数人的实际水平。
  • 中位数 表示将数据排序后位于中间位置的数值,具有较强的鲁棒性(robustness),不受极端值影响。当数据呈现明显右偏或左偏时,使用中位数更能代表“典型”个体。例如,在房价分析中,由于存在少量豪宅,均值往往远高于普通住宅价格,此时中位数更具参考价值。

  • 众数 指出现频率最高的值,主要用于分类变量或离散型数据。对于连续变量,可通过分组后频数最多的区间来近似众数。值得注意的是,一个变量可能有多个众数(如双峰分布),这本身也传递了重要的分布信息。

统计量 数学公式 优点 缺点 适用场景
均值 $\bar{x} = \frac{1}{n}\sum_{i=1}^{n} x_i$ 易于计算,支持进一步统计推断 对异常值敏感 正态分布、无明显离群点
中位数 $Med = x_{(n+1)/2}$ (奇数)
$Med = \frac{x_{n/2} + x_{n/2+1}}{2}$ (偶数)
抗干扰能力强,稳健 不适合代数运算 偏态分布、含异常值
众数 出现次数最多的值 可用于名义变量 可能不存在或多值 分类数据、频数主导情形

以下代码展示了如何使用 pandas 计算这三个指标:

import pandas as pd
import numpy as np

# 构造模拟数据:包含异常值的薪资数据
np.random.seed(42)
salaries = np.concatenate([np.random.normal(8000, 2000, 95), [50000, 60000, 70000]])

df = pd.DataFrame({'salary': salaries})

# 计算三大集中趋势指标
mean_val = df['salary'].mean()
median_val = df['salary'].median()
mode_val = df['salary'].mode()[0] if not df['salary'].mode().empty else None

print(f"均值: {mean_val:.2f}")
print(f"中位数: {median_val:.2f}")
print(f"众数: {mode_val:.2f}")

逐行逻辑分析:

  1. np.random.seed(42) :设置随机种子以保证结果可复现;
  2. np.random.normal(8000, 2000, 95) :生成95个服从正态分布(均值8000,标准差2000)的薪资数据;
  3. 使用 np.concatenate 添加三个极高薪样本作为异常值,模拟现实中的“长尾”现象;
  4. 将数据封装为 DataFrame ,便于调用 pandas 内置函数;
  5. .mean() 直接调用均值计算;
  6. .median() 返回排序后的中间值;
  7. .mode() 返回最频繁出现的值,注意返回类型为 Series ,需索引取值;
  8. 输出结果显示:均值约为10,283元,显著高于中位数约8,052元,体现异常值对均值的影响。

该对比清晰说明:在存在偏态或异常值的情况下,应优先考虑中位数作为集中趋势的代表。

4.1.2 离散程度度量:方差、标准差、四分位距的实际含义

仅了解中心趋势不足以刻画数据全貌,还需评估其分散程度。常用的离散度指标包括方差(Variance)、标准差(Standard Deviation)和四分位距(Interquartile Range, IQR)。

  • 方差 衡量各数据点相对于均值的平均平方偏差,定义为:
    $$
    \sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^2
    $$
    方差越大,表示数据越分散。但由于单位是原始单位的平方,解释不便。

  • 标准差 是方差的平方根,保持与原数据相同的量纲,便于解释。常用于正态分布下的置信区间估算(如±1σ覆盖约68%数据)。

  • 四分位距(IQR) 定义为第三四分位数(Q3)减去第一四分位数(Q1),即中间50%数据的跨度。因其基于顺序统计量,对异常值不敏感,常用于箱线图中识别离群点。

下面通过代码演示这些指标的计算过程:

# 继续使用上述 salary 数据
variance = df['salary'].var()
std_dev = df['salary'].std()
q1 = df['salary'].quantile(0.25)
q3 = df['salary'].quantile(0.75)
iqr = q3 - q1

print(f"方差: {variance:.2f}")
print(f"标准差: {std_dev:.2f}")
print(f"Q1: {q1:.2f}, Q3: {q3:.2f}, IQR: {iqr:.2f}")

输出结果:

方差: 119685445.32
标准差: 10940.08
Q1: 6786.56, Q3: 9387.97, IQR: 2601.41

参数说明与扩展分析:

  • 标准差高达约10,940元,远大于中位数8,052元,表明数据高度离散;
  • IQR仅为2,601元,说明中间一半人群的薪资差异较小,但两端存在严重拉伸;
  • 结合之前均值被拉高的现象,可判断数据呈明显的右偏分布。

此外,我们可以结合 IQR 设定离群点判定规则:
\text{Lower Fence} = Q1 - 1.5 \times IQR \
\text{Upper Fence} = Q3 + 1.5 \times IQR

lower_fence = q1 - 1.5 * iqr
upper_fence = q3 + 1.5 * iqr
outliers = df[(df['salary'] < lower_fence) | (df['salary'] > upper_fence)]

print(f"离群点数量: {len(outliers)}")
print("离群点值:", outliers['salary'].values)

执行逻辑说明:

  • 利用 Tukey’s rule(1.5×IQR准则)设定上下边界;
  • 筛选出超出范围的记录;
  • 输出显示三个高薪样本均被识别为离群点,验证了其极端性。

此方法广泛应用于自动化异常检测流程中。

4.1.3 分布形态分析:偏度与峰度揭示数据非正态特性

除了位置与离散性,数据的分布形态同样重要。两个关键指标—— 偏度(Skewness) 和 峰度(Kurtosis) ——用于描述分布的对称性和尾部厚重程度。

  • 偏度 衡量分布的不对称性:
  • 偏度 = 0:对称分布(如正态分布)
  • 偏度 > 0:右偏(正偏),尾部向右延伸
  • 偏度 < 0:左偏(负偏),尾部向左延伸

  • 峰度 衡量分布尾部的“厚重”程度(注意:通常报告的是“超额峰度”,即减去3后的值):

  • 峰度 ≈ 0:尾部厚度类似正态分布
  • 峰度 > 0:重尾(Leptokurtic),极端值更多
  • 峰度 < 0:轻尾(Platykurtic),极端值较少
skewness = df['salary'].skew()
kurtosis = df['salary'].kurt()  # pandas 默认返回 excess kurtosis

print(f"偏度: {skewness:.3f}")
print(f"峰度(超额): {kurtosis:.3f}")

# 可视化分布形态
import seaborn as sns
import matplotlib.pyplot as plt

plt.figure(figsize=(10, 6))
sns.histplot(df['salary'], kde=True, bins=30)
plt.title('薪资分布直方图与核密度估计')
plt.xlabel('薪资')
plt.ylabel('频数')
plt.axvline(mean_val, color='r', linestyle='--', label=f'均值={mean_val:.0f}')
plt.axvline(median_val, color='b', linestyle='-', label=f'中位数={median_val:.0f}')
plt.legend()
plt.show()

逻辑分析:

  • .skew() 调用 Fisher-Pearson 偏度系数,基于三阶标准化矩;
  • .kurt() 返回的是 excess kurtosis(即减去3),便于判断是否比正态分布更尖或更平;
  • 图形中红色虚线为均值,蓝色实线为中位数,二者分离明显,佐证右偏;
  • KDE曲线右侧拖尾明显,符合预期。

mermaid 流程图:描述性统计分析流程

graph TD
    A[加载数据] --> B[初步审查变量类型]
    B --> C[计算集中趋势: 均值/中位数/众数]
    C --> D[评估离散程度: 方差/标准差/IQR]
    D --> E[分析分布形态: 偏度/峰度]
    E --> F[结合可视化确认异常值与偏态]
    F --> G[形成初步数据认知报告]
    G --> H[指导后续清洗与变换策略]

该流程体现了从单一指标到综合判断的递进式分析思路,确保每一步都有据可依。

综上所述,描述性统计不仅是EDA的起点,更是贯穿始终的分析主线。只有充分理解这些基础度量的意义与局限,才能在后续建模中做出合理假设与有效预处理决策。特别是在面对非正态、异质性强的数据时,盲目依赖传统统计方法可能导致错误结论,因此必须辅以图形化手段进行交叉验证。


4.2 基于Matplotlib与Seaborn的图形化探索

如果说描述性统计是从“数字角度”理解数据,那么可视化则是从“视觉角度”感知数据。图形化探索能够揭示统计量无法捕捉的模式,如非线性关系、聚类结构、时间趋势等。Matplotlib 作为 Python 最基础的绘图库,提供了高度灵活的底层控制能力;而 Seaborn 在其之上构建了更高层次的接口,专为统计图表优化,极大提升了开发效率。本节将系统介绍如何组合使用这两者进行高效的多维数据探索。

4.2.1 直方图与核密度估计图联合使用分析分布形态

直方图(Histogram)是最常见的分布展示方式,通过将数据划分为若干区间(bin),统计每个区间的频数来近似概率密度。然而,bin 的宽度选择直接影响图形表现,容易造成误导。为此,核密度估计(Kernel Density Estimation, KDE)作为一种平滑的概率密度估计方法,可弥补直方图的粗糙性。

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

# 子图1:直方图 + KDE 叠加
plt.subplot(1, 2, 1)
sns.histplot(df['salary'], bins=25, kde=False, color='skyblue', alpha=0.7)
sns.kdeplot(df['salary'], color='red', linewidth=2)
plt.title('直方图与KDE叠加图')
plt.xlabel('薪资')
plt.ylabel('密度')

# 子图2:纯KDE对比不同带宽
plt.subplot(1, 2, 2)
sns.kdeplot(df['salary'], label='默认带宽', color='blue')
sns.kdeplot(df['salary'], bw_adjust=0.3, label='窄带宽', color='green')
sns.kdeplot(df['salary'], bw_adjust=2, label='宽带宽', color='orange')
plt.title('不同带宽下的KDE曲线')
plt.xlabel('薪资')
plt.ylabel('密度')
plt.legend()

plt.tight_layout()
plt.show()

代码解释:

  • sns.histplot(..., kde=False) :绘制直方图,关闭内置KDE;
  • sns.kdeplot() :单独绘制核密度曲线,便于自定义样式;
  • bw_adjust 参数调节平滑程度:值越小越贴近数据细节,过大则过度平滑;
  • 左图显示两者结合效果,右图比较不同平滑程度的影响。

这种组合方式既能保留频数分布的直观感,又能展现潜在密度趋势,特别适合初步筛查变量分布类型。

4.2.2 散点图矩阵与成对关系发现:seaborn.pairplot实战

当涉及多个连续变量时, 散点图矩阵(Pair Plot) 成为发现变量间两两关系的强大工具。 seaborn.pairplot() 自动绘制所有变量的两两组合图,对角线上可显示单变量分布(如直方图或KDE)。

假设我们有一个包含年龄、工作经验、薪资和绩效评分的数据集:

# 模拟多变量数据
np.random.seed(42)
n = 200
age = np.random.randint(22, 65, n)
experience = np.clip(age - 22, 0, None) + np.random.normal(0, 2, n)
performance = np.random.beta(2, 2, n) * 10
salary_multi = 5000 + 200 * experience + 300 * performance + np.random.normal(0, 1500, n)

df_multi = pd.DataFrame({
    'age': age,
    'experience': experience,
    'performance': performance,
    'salary': salary_multi
})

# 绘制 pairplot
sns.pairplot(df_multi, diag_kind='kde', plot_kws={'alpha': 0.6}, corner=True)
plt.suptitle('散点图矩阵(Pair Plot)', y=1.02, fontsize=16)
plt.show()

功能说明:

  • diag_kind='kde' :对角线显示核密度图;
  • plot_kws={'alpha': 0.6} :设置透明度以减少遮挡;
  • corner=True :只绘制下半三角,节省空间;
  • 图中可见 experience 与 salary 存在线性趋势, performance 影响较弱。

此图可用于快速筛查强相关变量,识别多重共线性风险。

4.2.3 箱线图与小提琴图在组间比较中的优势对比

当需要比较不同类别组之间的分布差异时, 箱线图(Box Plot) 和 小提琴图(Violin Plot) 是两种常用工具。

  • 箱线图 强调五数概括(最小值、Q1、中位数、Q3、最大值)及异常值;
  • 小提琴图 在箱线图基础上叠加了KDE,展示完整分布轮廓,更适合观察多模态。
# 添加职业类别字段
categories = ['Engineer', 'Manager', 'Analyst']
df_multi['role'] = np.random.choice(categories, size=n)

plt.figure(figsize=(14, 6))

# 左图:箱线图
plt.subplot(1, 2, 1)
sns.boxplot(data=df_multi, x='role', y='salary', palette='Set2')
plt.title('按角色划分的薪资箱线图')
plt.ylabel('薪资')
plt.xlabel('角色')

# 右图:小提琴图
plt.subplot(1, 2, 2)
sns.violinplot(data=df_multi, x='role', y='salary', inner='box', palette='Pastel1')
plt.title('按角色划分的薪资小提琴图')
plt.ylabel('薪资')
plt.xlabel('角色')

plt.tight_layout()
plt.show()

参数说明:

  • inner='box' :在小提琴内部嵌入小型箱线图,兼顾统计信息与分布形态;
  • palette 控制颜色风格,提升可读性;
  • 小提琴图能清晰看出某些角色薪资分布呈双峰,暗示可能存在子群体。
图表类型 优势 劣势 推荐使用场景
箱线图 突出中位数、IQR、异常值 忽略分布细节 快速对比组间中心与离散度
小提琴图 展示完整密度轮廓 视觉复杂度较高 多模态、非对称分布分析

综上,图形化探索不仅仅是“画图”,而是通过精心设计的视觉编码,将抽象数据转化为可感知的信息结构。合理选用图表类型,结合交互式工具(如 Plotly),可大幅提升分析效率与洞察深度。

4.3 变量间相关性的量化与可视化表达

在构建预测模型前,理解变量之间的相关性至关重要。高度相关的特征可能导致多重共线性问题,影响模型稳定性;而低相关性输入则可能削弱预测能力。因此,量化并可视化特征间的依赖关系,既是特征工程的前提,也是降维与选择的基础。

4.3.1 皮尔逊相关系数的数学基础与线性关系捕捉能力

皮尔逊相关系数(Pearson Correlation Coefficient) 衡量两个连续变量之间的线性相关程度,取值范围为 [-1, 1]:

r = \frac{\sum (x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum (x_i - \bar{x})^2} \sqrt{\sum (y_i - \bar{y})^2}}

  • r ≈ 1:强正相关
  • r ≈ -1:强负相关
  • r ≈ 0:无线性相关
# 计算相关矩阵
corr_matrix = df_multi[['age', 'experience', 'performance', 'salary']].corr(method='pearson')

print("皮尔逊相关矩阵:")
print(corr_matrix.round(3))

输出示例:

             age  experience  performance  salary
age          1.000       0.987        0.012   0.432
experience   0.987       1.000        0.021   0.461
performance  0.012       0.021        1.000   0.398
salary       0.432       0.461        0.398   1.000

可见 age 与 experience 高度相关(r=0.987),可能存在冗余;而 performance 与其他变量相关较弱。

4.3.2 斯皮尔曼等级相关系数应对非线性单调关系的适用场景

当变量间存在非线性但单调的关系(如指数增长)时,皮尔逊系数可能低估相关性。此时应使用 斯皮尔曼等级相关(Spearman Rank Correlation) ,它基于变量的秩次而非原始值:

spearman_corr = df_multi.corr(method='spearman')
print("\n斯皮尔曼相关矩阵:")
print(spearman_corr[['salary']].round(3))  # 查看各变量与薪资的秩相关

若某变量与薪资的 Spearman 相关高于 Pearson,则说明其关系可能是非线性但单调的。

4.3.3 热力图(heatmap)展示特征相关矩阵并指导冗余特征剔除

最后,使用热力图将相关矩阵可视化,便于快速识别强相关变量对:

plt.figure(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0, square=True,
            cbar_kws={"shrink": .8}, linewidths=.5)
plt.title('特征间皮尔逊相关热力图')
plt.show()

图表元素说明:

  • annot=True :显示具体数值;
  • cmap='coolwarm' :红蓝配色区分正负相关;
  • center=0 :以零为中心对称着色;
  • 单元格颜色越红表示正相关越强,越蓝表示负相关越强。

结合此图,可制定如下策略:
- 若两特征相关性 > 0.8,考虑删除其一或进行主成分合并;
- 关注目标变量(如 salary )与其他特征的相关性,优先保留高相关特征。

综上,本章通过系统的描述性统计、多维可视化与相关性分析,构建了一套完整的数据认知框架。从数字到图形,从单变量到多变量,层层递进地揭示了数据的本质特征。这一过程不仅是技术操作,更是思维训练——教会我们在纷繁复杂的原始数据中提炼信号、过滤噪声,为后续建模打下坚实基础。

5. 特征选择、降维与端到端EDA报告整合输出

5.1 主成分分析(PCA)的数学原理与Scikit-learn实现

主成分分析(Principal Component Analysis, PCA)是一种无监督线性降维方法,其核心思想是将原始高维特征空间投影到低维子空间中,同时尽可能保留数据的方差信息。PCA通过正交变换找到数据中方差最大的方向——即主成分,依次排序后可选择前k个主成分作为新的特征表示。

数学原理简述:

  1. 标准化 :由于PCA对量纲敏感,需先对数据进行Z-score标准化。
  2. 协方差矩阵计算 :设数据矩阵为 $ X \in \mathbb{R}^{n \times p} $,则协方差矩阵为 $ C = \frac{1}{n-1}X^T X $。
  3. 特征值分解 :求解 $ C $ 的特征值和对应的特征向量,按特征值从大到小排序。
  4. 投影构建 :选取前k个最大特征值对应的特征向量组成投影矩阵 $ W_k $,新特征为 $ X_{\text{reduced}} = XW_k $。

以下是在 scikit-learn 中实现完整PCA流程的代码示例:

import numpy as np
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt

# 加载示例数据集
data = load_breast_cancer()
X = data.data
y = data.target
feature_names = data.feature_names

# 步骤1:标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 步骤2:应用PCA,保留95%方差
pca = PCA(n_components=0.95)
X_pca = pca.fit_transform(X_scaled)

# 输出解释方差比与累计贡献率
explained_variance_ratio = pca.explained_variance_ratio_
cumulative_variance_ratio = np.cumsum(explained_variance_ratio)

print(f"原始维度: {X.shape[1]}")
print(f"降维后维度: {X_pca.shape[1]}")
print(f"各主成分解释方差比例: {explained_variance_ratio[:5].round(4)}")
print(f"累计解释方差比例: {cumulative_variance_ratio[-1]:.4f}")

执行结果可能如下(具体数值因数据而异):

主成分 解释方差比例 累计解释方差比例
PC1 0.4427 0.4427
PC2 0.1897 0.6324
PC3 0.0932 0.7256
PC4 0.0664 0.7920
PC5 0.0557 0.8477
… … …
PC15 0.0113 0.9512

该表显示仅需15个主成分即可解释超过95%的总方差,显著降低模型复杂度。

我们还可以可视化主成分的方差贡献趋势:

plt.figure(figsize=(10, 6))
plt.plot(range(1, len(cumulative_variance_ratio) + 1), cumulative_variance_ratio, marker='o')
plt.axhline(y=0.95, color='r', linestyle='--', label='95% variance threshold')
plt.xlabel('Number of Principal Components')
plt.ylabel('Cumulative Explained Variance Ratio')
plt.title('Explained Variance vs Number of Components')
plt.legend()
plt.grid(True)
plt.show()

此图有助于确定最优主成分数目,平衡信息保留与维度压缩之间的权衡。

此外,可通过热力图观察原始变量在第一主成分上的载荷(loading),以理解哪些原始特征对主成分影响最大:

loadings = pca.components_[0]  # 第一主成分的系数
loading_df = pd.DataFrame({'Feature': feature_names, 'Loading': loadings})
top_loading_features = loading_df.reindex(loading_df.Loading.abs().sort_values(ascending=False).index).head(10)

print("\nTop 10 features contributing to PC1:")
print(top_loading_features)

输出示例:

Feature Loading
mean texture 0.248
mean smoothness -0.236
mean compactness 0.229
mean concavity 0.225
mean concave points 0.223
worst radius 0.218
worst perimeter 0.215
worst area 0.210
mean radius 0.208
mean perimeter 0.205

这些变量在PC1上具有较高的绝对载荷,说明它们共同构成了区分良恶性肿瘤的关键方向。

5.2 多种特征选择方法比较与应用场景分析

除了PCA这类基于投影的降维方法外,特征选择技术旨在从原始特征集中挑选出最具代表性的子集,提升模型可解释性并减少过拟合风险。常见策略包括过滤法(Filter)、包装法(Wrapper)和嵌入法(Embedded)。

5.2.1 方差阈值法(Variance Threshold)

适用于去除那些几乎不变的“死板”特征:

from sklearn.feature_selection import VarianceThreshold

selector = VarianceThreshold(threshold=0.01)  # 去除方差小于0.01的特征
X_high_var = selector.fit_transform(X_scaled)
print(f"经方差筛选后剩余特征数: {X_high_var.shape[1]}")

5.2.2 相关性过滤法(Correlation-based Filtering)

识别高度相关特征对,避免冗余输入:

# 计算特征间皮尔逊相关系数
corr_matrix = pd.DataFrame(X_scaled).corr().abs()

# 屏蔽对角线以上部分,防止重复
upper_triangle = corr_matrix.where(
    np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)
)

# 找出相关性大于0.95的特征对
to_drop = [column for column in upper_triangle.columns if any(upper_triangle[column] > 0.95)]
print(f"因高相关性被移除的特征数量: {len(to_drop)}")

5.2.3 递归特征消除(RFE)与支持向量机结合

使用模型性能反馈进行迭代式剔除:

from sklearn.feature_selection import RFE
from sklearn.svm import SVC

estimator = SVC(kernel="linear")
selector_rfe = RFE(estimator, n_features_to_select=20, step=1)
X_rfe = selector_rfe.fit_transform(X_scaled, y)

# 查看选中的特征索引
selected_features = [feature_names[i] for i in range(len(feature_names)) if selector_rfe.support_[i]]
print("RFE选出的20个关键特征:")
for f in selected_features:
    print(f"  - {f}")

方法对比总结表格:

方法 类型 是否依赖模型 可解释性 计算成本 推荐场景
方差阈值 过滤法 否 高 极低 快速去除不变特征
相关性过滤 过滤法 否 高 低 消除多重共线性
PCA 投影降维 否 中 中 高维数据压缩、去噪
RFE 包装法 是 高 高 小规模数据,追求最佳子集
L1正则化(Lasso) 嵌入法 是 高 中 回归任务中稀疏特征选择

不同方法应根据业务需求、数据规模与建模目标灵活组合使用。

5.3 构建结构化EDA报告:Jupyter + Markdown自动化输出

一个完整的EDA项目不应止步于代码运行,更需要形成可交付、可复现的文档成果。利用 Jupyter Notebook 结合 Markdown 单元格,可以实现“代码+图表+文字”的一体化表达。

报告建议结构:

# 探索性数据分析报告:乳腺癌诊断数据集

## 1. 摘要
本报告针对UCI乳腺癌数据集开展系统性EDA,涵盖数据质量评估、分布探查、相关性分析及特征工程建议。最终推荐使用20个精选特征或15维PCA投影用于后续分类建模。

## 2. 数据概况
- 样本数:569
- 特征数:30(含均值、标准差、最差值三类)
- 目标变量:良性(0)/恶性(1),比例约为63% : 37%

## 3. 数据清洗记录
- 无缺失值
- 使用Z-score标准化处理所有连续变量
- 通过IQR检测未发现明显异常值

## 4. 关键可视化发现

> 图1:前15个主成分累计解释95.12%方差

## 5. 特征工程建议
- 可采用PCA降至15维以简化模型
- 或使用RFE筛选出20个最具判别力的原始特征
- 建议在建模阶段对比两种方案的AUC表现

借助 nbconvert 工具可将 .ipynb 文件导出为HTML或PDF格式,便于分享:

jupyter nbconvert --to html eda_report.ipynb

进一步可通过 papermill 实现参数化执行与批量报告生成,推动EDA流程工业化。

mermaid格式流程图展示端到端EDA工作流:

graph TD
    A[加载原始数据] --> B[描述性统计]
    B --> C[缺失值与异常值分析]
    C --> D[数据清洗与填补]
    D --> E[标准化与编码]
    E --> F[多维可视化探索]
    F --> G[相关性分析]
    G --> H[特征选择/降维]
    H --> I[生成结构化报告]
    I --> J[输出至HTML/PDF]
    J --> K[交付建模团队]

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:EDA(Exploratory Data Analysis)是数据分析的基石,旨在通过可视化与统计方法深入理解数据的分布、关联与模式。本“EDA-2020-2”项目为2020年第二次迭代版本,涵盖数据预处理、可视化、描述性统计、相关性分析、特征工程及分析报告撰写等关键环节。项目包含完整源码、数据集与可视化结果,适用于数据分析学习者掌握从原始数据到洞察输出的全流程实践,提升数据科学实战能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐