第2章 数据概览与质量报告生成

拿到一批原始数据,别急着做合并。我个人的习惯是——先让数据“自报家门”。

什么意思?就是跑一遍数据概览,生成一份质量报告。说白了,你要知道手里这张牌到底有几张缺失、几处异常、哪些字段藏着猫腻。本章咱们就聊怎么用 Pandas Profiling 和自定义函数,快速摸清数据的底细。

2.1 为什么需要数据质量报告

你想想看,如果连数据长什么样都不清楚,后面跑出来的合并结果你敢信?我在项目里遇过太多次:明明两边的 ID 字段看起来一样,实际一个存的是字符串 "001",另一个存的是整数 1,一 merge 就全丢了。这种坑,早发现早解决。

一份好的质量报告至少应该回答这几个问题:

  • 字段完整性:哪些列有缺失?缺失比例是多少?
  • 数值分布:均值、中位数、极端值是否合理?
  • 数据类型:有没有本该是数字的列被读成了字符串?
  • 重复行:是否存在完全重复的记录?
  • 关联性:字段之间是否有明显的强相关或矛盾?

下面这张图就是整个质量报告生成的流程,你可以对照着来理解每一步的输出。

数据质量报告生成流程 原始数据 Pandas Profiling 交互式HTML报告 (概述/变量/相关性/样本) 自定义质量检查 问题清单(缺失/异常/重复) 预处理决策(剔除/填充/转换) (可选:绕过Profile直接检查) 💡 流程说明: 先跑Pandas Profiling获得全局概览,再针对可疑字段写自定义检查函数。最终输出一份问题清单,指导后续预处理动作。 两部分结果要交叉验证:Profile报告显示无缺失,不代表空字符串被正确识别。

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() 再检查。

📌 实战小结:

我自己做合并前的质控,一般分三步走:

  1. 跑 Pandas Profiling 拿全局视角
  2. 写 3-5 个自定义检查函数,覆盖缺失、异常、重复
  3. 把两个结果合并到一张 quality_issues.csv 表格里,方便后面逐一修复

比如下面这种格式:

字段名,问题类型,严重程度,建议动作
age,异常值(负数),高,剔除或填充中位数
customer_id,缺失(空串),高,转换为NaN并填充
order_date,格式不一致,中,统一为yyyy-mm-dd

这一步做扎实了,后面的合并才会稳。记住一句话:数据质量报告不是给别人看的,是给你自己的预处理流程指路的。