2. 数据探索与质量评估:数据概览、描述性统计、缺失值/异常值/重复值初步诊断
拿到一份新数据,别急着往上怼模型。我个人的习惯是,先花20%的时间做数据探索。为什么?因为数据不会告诉你它有问题,你得自己去“审问”它。说白了,这一步就是给你的数据做个体检,看看它有没有“发烧”、“咳嗽”或者“缺胳膊少腿”。
核心目标:在正式清洗之前,快速摸清数据的“脾气”和“底细”。
2.1 数据概览:先看全貌,再看细节
我一般会先问三个问题:数据长什么样?有多少行多少列?每列是干嘛的?
在Python里,我习惯用Pandas。你想想看,如果连数据长啥样都不知道,后面怎么干活?
import pandas as pd
# 加载数据
df = pd.read_csv('sales_data.csv')
# 看一眼前5行
print(df.head())
# 看看数据规模
print(f"数据集形状: {df.shape}")
# 看看每列的类型和非空数量
print(df.info())
df.head() 能让你快速看到数据的“长相”。df.info() 则告诉你每列的数据类型,以及有没有缺失值。嗯,这里要注意,info() 里显示的 Non-Null Count 就是非空值的数量,如果它比总行数少,那就有缺失值了。
我曾经接手过一个项目,数据有200多列,info() 一打出来,发现有一半的列全是 object 类型。我当时就意识到,这数据肯定有大量的文本或者脏数据,清洗工作量不小。
2.2 描述性统计:用数字说话
光看几行数据不够,你得用统计指标来量化。对于数值型列,describe() 是神器。
# 数值列的统计摘要
print(df.describe())
它会输出:计数、均值、标准差、最小值、四分位数、最大值。这些数字能告诉你什么?
- 均值 vs 中位数:如果均值远大于中位数(比如均值100,中位数50),说明数据右偏,可能有很大的异常值在拉高均值。
- 标准差:标准差太大,说明数据波动剧烈;太小,说明数据很集中。
- 最小值/最大值:看看有没有明显不合理的值。比如年龄列最小值是-1,那肯定有问题。
我的小技巧:对于分类列(比如城市、性别),我会用 df['column'].value_counts() 看看分布。如果某个类别的占比超过99%,那这个特征基本没什么区分度,可以考虑删掉。
2.3 缺失值初步诊断:别慌,先摸清规律
缺失值很常见,但处理方式取决于它为什么缺失。我一般先统计缺失率。
# 统计每列的缺失值数量和比例
missing_count = df.isnull().sum()
missing_ratio = df.isnull().mean()
# 合并成一个DataFrame
missing_df = pd.DataFrame({
'缺失数量': missing_count,
'缺失比例': missing_ratio
})
print(missing_df[missing_df['缺失数量'] > 0])
为什么要看比例?如果一个列缺失了90%,我个人建议直接删掉这列,因为补全的意义不大,反而可能引入噪声。
我曾经遇到过一个用户画像数据,其中“收入”列缺失了30%。我一开始想用均值填充,但后来发现,高收入人群更倾向于不填收入。如果简单用均值填充,反而会拉低高收入人群的收入水平,导致模型偏差。所以,缺失值的处理一定要结合业务逻辑。
避坑指南:千万不要一上来就用 df.dropna() 删掉所有含缺失值的行。我曾经这么干过,结果把80%的数据都删了,模型直接没法训练。先分析,再动手。
2.4 异常值初步诊断:用“三西格玛”和“箱线图”
异常值就是那些“格格不入”的数据点。怎么找?我常用两种方法。
方法一:3σ 原则(适用于正态分布)
如果数据近似正态分布,那么99.7%的数据会落在均值±3个标准差之内。超出这个范围,就可以视为异常值。
import numpy as np
def detect_outliers_zscore(data, threshold=3):
z_scores = np.abs((data - data.mean()) / data.std())
return data[z_scores > threshold]
# 示例:检测'age'列的异常值
outliers = detect_outliers_zscore(df['age'])
print(f"检测到 {len(outliers)} 个异常值")
方法二:箱线图(IQR,适用于偏态分布)
箱线图不依赖正态分布假设。它用四分位距(IQR)来定义异常值:小于 Q1 - 1.5*IQR 或大于 Q3 + 1.5*IQR 的点。
def detect_outliers_iqr(data):
Q1 = data.quantile(0.25)
Q3 = data.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
return data[(data < lower_bound) | (data > upper_bound)]
outliers_iqr = detect_outliers_iqr(df['age'])
print(f"IQR方法检测到 {len(outliers_iqr)} 个异常值")
我个人更倾向于用箱线图,因为现实中的数据很少是完美的正态分布。比如电商订单金额,往往就是长尾分布,用3σ会误杀很多正常的高消费用户。
2.5 重复值初步诊断:别让“复制粘贴”坑了你
重复值通常是因为数据采集时的错误或者系统bug。比如用户不小心提交了两次表单。
# 检查完全重复的行
duplicate_rows = df.duplicated()
print(f"完全重复的行数: {duplicate_rows.sum()}")
# 查看重复的行
print(df[duplicate_rows].head())
这里要注意,duplicated() 默认是检查所有列是否完全一样。但有时候,我们只关心某些关键列是否重复。比如,同一个用户ID出现了两次,但其他信息不同,那可能是数据更新导致的,不一定是真正的重复。
我的经验:对于用户ID或订单ID这种唯一标识列,我会用 df['user_id'].duplicated() 来检查。如果发现重复,我会保留最新的一条记录(比如根据时间戳排序),或者直接删除。
2.6 知识体系总览
下面这张图,是我自己总结的数据探索与质量评估的流程。你可以把它当作一个检查清单。
这张图把整个流程串起来了。从原始数据出发,经过数据概览、描述性统计、缺失值、异常值、重复值这五个维度的诊断,最终输出一份数据质量报告,并指导后续的清洗策略。
2.7 实战小贴士:把诊断结果汇总
我建议你把每一步的诊断结果汇总到一个表格里,方便后续决策。
| 列名 | 数据类型 | 缺失比例 | 异常值数量 | 重复值数量 | 初步处理建议 |
|---|---|---|---|---|---|
| user_id | int64 | 0% | 0 | 5 | 删除重复行 |
| age | float64 | 5% | 3 | 0 | 中位数填充缺失值,删除异常值 |
| income | float64 | 30% | 10 | 0 | 根据业务分组填充,或保留缺失作为特征 |
| city | object | 1% | N/A | 0 | 众数填充 |
有了这个表格,你就能清晰地知道每列该怎么处理。记住,数据探索不是一次性工作,它应该贯穿整个数据清洗流程。你清洗完一轮,可能还需要再回来看看效果。
总结一下:数据探索与质量评估,就是给数据做“体检”。用 head()、info()、describe() 看全貌,用 isnull() 找缺失,用 3σ 或 IQR 找异常,用 duplicated() 找重复。每一步都别偷懒,因为数据质量决定了模型的天花板。