第2章 数据概览与质量报告生成
拿到一批原始数据,别急着做合并。我个人的习惯是——先让数据“自报家门”。
什么意思?就是跑一遍数据概览,生成一份质量报告。说白了,你要知道手里这张牌到底有几张缺失、几处异常、哪些字段藏着猫腻。本章咱们就聊怎么用 Pandas Profiling 和自定义函数,快速摸清数据的底细。
2.1 为什么需要数据质量报告
你想想看,如果连数据长什么样都不清楚,后面跑出来的合并结果你敢信?我在项目里遇过太多次:明明两边的 ID 字段看起来一样,实际一个存的是字符串 "001",另一个存的是整数 1,一 merge 就全丢了。这种坑,早发现早解决。
一份好的质量报告至少应该回答这几个问题:
- 字段完整性:哪些列有缺失?缺失比例是多少?
- 数值分布:均值、中位数、极端值是否合理?
- 数据类型:有没有本该是数字的列被读成了字符串?
- 重复行:是否存在完全重复的记录?
- 关联性:字段之间是否有明显的强相关或矛盾?
下面这张图就是整个质量报告生成的流程,你可以对照着来理解每一步的输出。
2.2 使用 Pandas Profiling 快速生成报告
Pandas Profiling 是一个宝藏库。装好后一行代码就能生成交互式 HTML 报告,字段分布、缺失、相关性、高频值一目了然。
安装也很简单:
pip install pandas-profiling
然后这样用:
import pandas as pd
from pandas_profiling import ProfileReport
df = pd.read_csv("sales_data.csv")
profile = ProfileReport(df, title="销售数据质量报告")
profile.to_file("sales_quality_report.html")
跑完之后打开 HTML 文件,你会看到这样的结构:
| 报告模块 | 能发现什么 |
|---|---|
| 概览 | 总行数、总列数、缺失单元格数、重复行数、内存占用 |
| 变量 | 每列的统计描述(均值/中位数/极值/缺失比例/唯一值数) |
| 相关性 | Pearson/Spearman 相关系数热力图,发现共线性 |
| 样本 | 前几条和后几条数据,快速瞄一眼 |
| 缺失矩阵 | 缺失值分布模式,哪些行同时缺失多个字段 |
💡 我的小技巧:
如果数据量很大(超过10万行),Profile 生成会变慢。这时候可以设置 minimal=True,只算基本统计量,速度能快好几倍。
profile = ProfileReport(df, minimal=True)
2.3 自定义质量检查函数
Pandas Profiling 很强,但它不是万能的。比如它不知道“年龄”字段出现负值是合理的吗?也不清楚“性别”字段里混入的 “Male”、“男”、“M” 是否需要统一。这些业务逻辑得我们自己写函数来检。
我通常会写一个 quality_check 模块,里面放几个常用的检查函数。下面给你看几个典型例子。
2.3.1 缺失值检查(带业务阈值)
def check_missing(df, threshold=0.8):
"""
检查缺失比例超过 threshold 的列
threshold: 默认0.8,即缺失80%以上就报警
"""
missing_ratio = df.isnull().mean()
problematic = missing_ratio[missing_ratio > threshold]
if not problematic.empty:
print("⚠️ 以下字段缺失比例过高:")
for col, ratio in problematic.items():
print(f" {col}: {ratio:.1%}")
return problematic
2.3.2 异常值检测(IQR方法)
def check_outliers(df, columns=None, factor=1.5):
"""
使用IQR检测数值列的异常值
columns: 指定要检查的列,默认所有数值列
factor: IQR倍数,默认1.5(标准箱线图规则)
"""
if columns is None:
columns = df.select_dtypes(include='number').columns
outlier_report = {}
for col in columns:
Q1 = df[col].quantile(0.25)
Q3 = df[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - factor * IQR
upper = Q3 + factor * IQR
outliers = df[(df[col] < lower) | (df[col] > upper)][col]
if len(outliers) > 0:
outlier_report[col] = {
'count': len(outliers),
'ratio': len(outliers) / len(df),
'range': (lower, upper)
}
print(f"🔴 {col}: {len(outliers)} 个异常值 ({outlier_report[col]['ratio']:.1%})")
return outlier_report
2.3.3 重复行与近似重复
def check_duplicates(df, subset=None):
"""
检查完全重复行及关键字段重复
subset: 指定用于判断重复的列子集
"""
full_dups = df[df.duplicated(keep=False)]
print(f"完全重复行(含首次出现): {len(full_dups)} 行")
if subset:
key_dups = df[df.duplicated(subset=subset, keep=False)]
print(f"关键字段重复({subset}): {len(key_dups)} 行")
return {
'full_duplicates': len(full_dups),
'key_duplicates': len(key_dups) if subset else 0
}
2.4 识别潜在问题:避坑指南
有了 Profile 报告和自定义检查,你基本能摸清数据的脾气了。但我还是要提醒几个容易踩的坑。
⚠️ 我曾经遇到过这样的问题:
Profile 报告显示所有字段“无缺失”,但在后续合并时发现 customer_id 字段全是空字符串 ""。原来上游系统写的是空串,Pandas 不把它算作 NaN。从那以后,我每次都会加一步检查:(df == '').sum() 。
还有几个常见的问题场景:
- 数据类型伪装:数字列里混了个 "N/A" 字符串,整列都变成了 object。我会用
pd.to_numeric(..., errors='coerce')先转换,再看转换前后的行数差异。 - 日期格式不一致:有的写 "2024-01-01",有的写 "01/01/2024"。Profile 报告会把这些当成普通字符串。用
pd.to_datetime(..., infer_datetime_format=True)批量尝试。 - 重复值的“水分”:两行看起来一样,但有一个空格差异。Pandas 的
duplicated()默认不会忽略空格。先df['col'] = df['col'].str.strip()再检查。
📌 实战小结:
我自己做合并前的质控,一般分三步走:
- 跑 Pandas Profiling 拿全局视角
- 写 3-5 个自定义检查函数,覆盖缺失、异常、重复
- 把两个结果合并到一张
quality_issues.csv表格里,方便后面逐一修复
比如下面这种格式:
字段名,问题类型,严重程度,建议动作
age,异常值(负数),高,剔除或填充中位数
customer_id,缺失(空串),高,转换为NaN并填充
order_date,格式不一致,中,统一为yyyy-mm-dd
这一步做扎实了,后面的合并才会稳。记住一句话:数据质量报告不是给别人看的,是给你自己的预处理流程指路的。