2. 数据探索与理解:数据质量检查、描述性统计分析、数据分布可视化、相关性分析

好,咱们进入第二章。说实话,很多同学拿到数据就急着建模,恨不得马上跑出个漂亮结果。我刚开始做项目时也这样,结果呢?模型跑出来一塌糊涂,调参调到怀疑人生。后来我学乖了——数据探索这一步,省不得

你想想看,数据就像食材。食材不新鲜,再好的厨子也做不出好菜。数据探索,就是帮你检查食材、了解食材的过程。我个人习惯,花在探索上的时间至少占整个项目的30%。

2.1 数据质量检查

拿到数据第一件事,不是看分布,不是画图——先看数据质量。我见过太多项目,跑了大半天模型,最后发现是数据本身有问题。

2.1.1 缺失值检查

缺失值是最常见的问题。怎么查?很简单:

import pandas as pd
import numpy as np

# 加载数据
df = pd.read_csv('your_data.csv')

# 检查缺失值
print(df.isnull().sum())

# 缺失比例
print(df.isnull().mean() * 100)

嗯,这里要注意:缺失比例超过50%的特征,我一般直接扔掉。为什么?因为信息量太少,补全的意义不大。我在一个电商项目中遇到过,有个特征叫「用户上次投诉时间」,缺失率高达80%。当时团队有人想用均值填充,我说别费劲了,直接删掉。后来证明这个决定是对的。

避坑指南: 我曾经犯过一个错——用0填充缺失值。结果模型学到的全是「0代表缺失」这个模式,而不是真实的业务规律。记住:缺失值处理要谨慎,不是所有缺失都能用0代替。

2.1.2 异常值检测

异常值,说白了就是那些「离谱」的数据点。比如用户年龄填了200岁,或者交易金额是负数。这些数据会严重干扰模型训练。

我常用的方法:

# 使用IQR方法检测异常值
def detect_outliers_iqr(data, column):
    Q1 = data[column].quantile(0.25)
    Q3 = data[column].quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    outliers = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
    return outliers

# 举个例子
outliers = detect_outliers_iqr(df, 'age')
print(f'发现 {len(outliers)} 个异常值')

但注意,异常值不一定是错误。我在金融风控项目中遇到过,有些大额交易看起来像异常值,其实是正常的业务行为。所以,先理解业务,再决定怎么处理

2.2 描述性统计分析

数据质量没问题了,接下来就是「认识」你的数据。描述性统计就是帮你快速了解数据全貌的工具。

2.2.1 数值型特征

对于数值型特征,我一般看这几个指标:

指标 含义 我关注什么
均值 数据的平均水平 是否和业务预期一致
中位数 数据的中间值 和均值对比,判断数据是否偏斜
标准差 数据的离散程度 数据是否过于集中或分散
偏度 分布的不对称性 是否需要做对数变换
峰度 分布的尖峭程度 是否存在极端值
# 快速获取描述性统计
print(df.describe())

# 更详细的统计信息
print(df.describe(percentiles=[0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99]))

我个人习惯,会特别关注均值和中位数的差异。如果两者相差很大,说明数据分布偏斜严重。比如收入数据,均值往往远大于中位数,因为少数高收入者拉高了平均值。这时候,我通常会考虑做对数变换。

2.2.2 类别型特征

类别型特征怎么看?看频数和占比:

# 类别型特征的频数统计
for col in df.select_dtypes(include=['object']).columns:
    print(f'\n{col} 的分布:')
    print(df[col].value_counts(normalize=True).head(10))

这里有个坑:类别太多怎么办? 比如「城市」这个特征,可能有几百个取值。我一般会做两件事:一是把出现频率低于1%的类别合并为「其他」;二是看业务上能不能做层级合并,比如把城市归到省份。

小技巧: 对于类别型特征,我还会看「缺失值是否本身有意义」。比如「婚姻状况」缺失,可能意味着用户不愿意透露,这本身就是一个信息。我会把缺失单独作为一个类别。

2.3 数据分布可视化

光看数字不够,还得画图。一张好图胜过千言万语。我常用的可视化工具:

2.3.1 直方图与密度图

看单个特征的分布:

import matplotlib.pyplot as plt
import seaborn as sns

# 直方图
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
df['age'].hist(bins=50)
plt.title('年龄分布直方图')

# 密度图
plt.subplot(1, 2, 2)
sns.kdeplot(df['age'], shade=True)
plt.title('年龄分布密度图')
plt.show()

为什么要看分布?举个例子,我在做信用评分模型时,发现「收入」特征的分布严重右偏。如果不处理,模型会对高收入群体过度敏感。后来做了对数变换,模型表现明显提升。

2.3.2 箱线图

箱线图能同时展示分布和异常值:

# 箱线图
plt.figure(figsize=(10, 6))
sns.boxplot(data=df[['age', 'income', 'credit_score']])
plt.title('关键特征箱线图')
plt.show()

嗯,这里要注意:箱线图里的「异常值」不一定要删除。我记得在医疗项目中,某些生理指标的「异常值」恰恰是疾病的表现。所以,先问业务专家,再决定。

2.3.3 类别特征的柱状图

# 类别特征分布
plt.figure(figsize=(10, 6))
df['education'].value_counts().plot(kind='bar')
plt.title('教育程度分布')
plt.xticks(rotation=45)
plt.show()

我一般会关注类别是否均衡。如果某个类别占比超过90%,这个特征对模型贡献可能不大。但也不绝对——比如「是否欺诈」这个特征,正样本可能只有1%,但恰恰是最重要的。

2.4 相关性分析

最后一步,看特征之间的关系。这能帮你发现冗余特征、潜在的多重共线性问题。

2.4.1 皮尔逊相关系数

最常用的方法:

# 计算相关系数矩阵
corr_matrix = df.corr()

# 可视化
plt.figure(figsize=(12, 10))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0)
plt.title('特征相关性热力图')
plt.show()

我一般关注相关系数绝对值大于0.8的特征对。比如「房屋面积」和「房间数量」高度相关,我会考虑只保留一个,或者做特征融合。

避坑指南: 我曾经犯过一个错——只看皮尔逊相关系数。但皮尔逊只能捕捉线性关系。有一次,两个特征明明有很强的非线性关系,相关系数却接近0。后来我学会了同时使用斯皮尔曼相关系数,它能捕捉单调关系。

2.4.2 斯皮尔曼秩相关系数

# 斯皮尔曼相关系数
spearman_corr = df.corr(method='spearman')

# 对比两种方法
linear_vs_rank = pd.DataFrame({
    'pearson': corr_matrix['target'],
    'spearman': spearman_corr['target']
})
print(linear_vs_rank)

如果皮尔逊和斯皮尔曼结果差异很大,说明特征之间的关系不是线性的。这时候,我会考虑做多项式特征或者使用树模型。

2.4.3 类别特征与数值特征的相关性

对于类别特征和数值特征,不能用相关系数。我一般用方差分析(ANOVA)或者信息价值(IV):

from scipy import stats

# 方差分析:类别特征对数值特征的影响
for cat_col in ['education', 'marital_status']:
    groups = [df[df[cat_col] == val]['income'] for val in df[cat_col].unique()]
    f_stat, p_val = stats.f_oneway(*groups)
    print(f'{cat_col} 对收入的影响:F值={f_stat:.2f}, p值={p_val:.4f}')

p值小于0.05,说明这个类别特征对数值特征有显著影响。嗯,这个信息对特征筛选很有用。

小结

数据探索不是走过场,它是整个特征工程的基石。我个人习惯,每次做完探索都会写一份报告,记录:

  • 哪些特征质量差,需要清洗
  • 哪些特征分布偏斜,需要变换
  • 哪些特征高度相关,需要合并或删除
  • 哪些特征和目标变量关系强,值得深入挖掘

下一章,我们会讲特征变换的具体方法。但记住:没有充分的数据探索,再高级的特征工程都是空中楼阁。别急着建模,先把数据摸透了再说。