数据剖析:先摸清数据的脾气

数据质量诊断的第一步是什么?不是写规则,不是跑脚本。是「摸」。你得先把手伸进去,感受一下数据长什么样。

我刚开始做数据治理那会儿,接过一个客户的数据集。对方说「这表很干净,就几万条」。我一跑单列剖析,好家伙,空值率超过 60% 的列有七八个。所以你看,别信「感觉」,要信数据。

这一章,我们就来聊聊数据剖析的两个基本功:单列剖析和多列关联剖析。说白了,就是先看每一列自己有没有毛病,再看列与列之间有没有扯皮。

单列剖析:给每一列做个体检

单列剖析,就是针对数据表里的每一列,单独计算几个关键指标。我一般会看三个东西:空值率、唯一值、数据类型。

1. 空值率:数据缺失有多严重?

空值率 = 空值数量 / 总记录数。这个指标最直观,也最容易踩坑。

我曾经遇到一个项目,业务方说「客户手机号字段基本都填了」。我一查,空值率只有 5%。但再往下看,那 5% 的空值全是 VIP 客户。你想想看,VIP 客户的手机号缺失,这影响有多大?

所以,空值率不能只看整体,还要结合业务含义。我习惯的做法是:

  • 先算整体空值率,快速定位「问题列」
  • 再按关键维度分组,比如按客户等级、按时间分区
  • 最后看空值分布,是不是集中在某个时间段或某个业务环节
我的小技巧: 空值率超过 30% 的列,我一般会标记为「高危」。但别急着删,先问业务方:这些空值是「应该填但没填」,还是「本来就不需要填」?两者处理方式完全不同。

2. 唯一值:这列是「身份证」还是「性别」?

唯一值数量(Cardinality)能告诉你一列的数据分布情况。比如「性别」列,唯一值最多就 3 个(男、女、未知)。而「订单 ID」列,唯一值应该接近总行数。

我一般用这个指标来快速判断:

  • 唯一值占比接近 100%:可能是主键或业务流水号
  • 唯一值占比很低:可能是分类字段,比如状态、类型
  • 唯一值占比异常:比如「省份」列突然出现 100 多个唯一值,那肯定有脏数据

举个例子。有一次我剖析一个「城市」字段,发现唯一值有 500 多个。正常中国城市也就 300 多个。一查,里面混入了「北京市」「北京」「BJ」「beijing」四种写法。这就是唯一值分析能发现的问题。

3. 数据类型推断:别信元数据,信数据本身

很多系统的元数据是错的。字段定义成「字符串」,里面存的却是日期。字段定义成「整数」,里面混着「N/A」和「-」。

我的做法是:采样推断。取前 1000 条数据,尝试用不同数据类型去解析:

-- 伪代码示例:数据类型推断逻辑
function infer_data_type(values):
    if all values match pattern '^\d{4}-\d{2}-\d{2}$':
        return 'DATE'
    if all values can be parsed as integer:
        return 'INTEGER'
    if all values can be parsed as float:
        return 'FLOAT'
    if max_length(values) < 50 and unique_count(values) < 20:
        return 'CATEGORY'
    else:
        return 'STRING'

嗯,这里要注意:推断结果只是参考。我见过一个「金额」字段,90% 的数据都能转成浮点数,但剩下的 10% 是「N/A」和「-」。这时候我会把推断结果标记为「FLOAT(含异常值)」,而不是直接改成字符串。

核心原则: 单列剖析不是一次性的。我建议每次数据接入都跑一遍,形成基线。下次再跑,对比基线,就能快速发现数据质量的变化。

多列关联剖析:看列与列之间的「关系网」

单列剖析看的是个体,多列关联剖析看的是关系。我主要关注两类关系:外键依赖和冗余检测。

1. 外键依赖:你的引用真的有效吗?

外键依赖,说白了就是 A 表引用了 B 表的某个字段,但 B 表里有没有这个值?

我遇到过最典型的场景:订单表里有个「客户 ID」,但客户表里已经删掉了这个客户。这就是「孤儿记录」。外键约束在数据库层面可能没建,但业务逻辑上它应该存在。

我的检测方法很简单:

  1. 找出所有「应该存在外键关系」的字段对
  2. 用 LEFT JOIN 找出「在子表存在,但在父表不存在」的记录
  3. 计算「外键失效比例」
-- 外键依赖检测示例
SELECT 
    COUNT(*) AS total_orders,
    SUM(CASE WHEN c.customer_id IS NULL THEN 1 ELSE 0 END) AS orphan_orders,
    ROUND(100.0 * SUM(CASE WHEN c.customer_id IS NULL THEN 1 ELSE 0 END) / COUNT(*), 2) AS orphan_rate
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.customer_id;

这个比例如果超过 1%,我建议立刻排查。因为外键失效往往意味着数据链路断了,下游报表、模型都会受影响。

避坑指南: 我曾经以为「只要数据库建了外键约束,数据就一定完整」。结果发现,业务系统为了性能,把外键约束关了。所以,永远不要假设约束存在,要实际去查。

2. 冗余检测:两列说的是同一件事吗?

冗余检测,就是看两列或多列之间是否存在信息重叠。比如「出生日期」和「年龄」,理论上可以由一个推导出另一个。但如果两者不一致,就是数据质量问题。

我常用的方法:

  • 完全冗余:两列值完全一一对应,比如「身份证号」和「身份证号(加密)」
  • 函数冗余:一列可以由另一列通过函数推导,比如「下单时间」和「下单日期」
  • 近似冗余:两列大部分一致,但有小部分差异,比如「客户姓名」和「客户昵称」

检测逻辑也不复杂:

-- 冗余检测示例:检查「出生日期」和「年龄」是否一致
SELECT 
    COUNT(*) AS total,
    SUM(CASE 
        WHEN age != TIMESTAMPDIFF(YEAR, birth_date, CURDATE()) 
        THEN 1 ELSE 0 
    END) AS mismatch_count
FROM users;

如果 mismatch_count 超过 5%,我一般会建议业务方:要么只保留一列,要么建立数据一致性校验规则。

知识体系总览

下面这张图,是我自己梳理的数据剖析知识框架。你可以把它当成一个检查清单,每次做数据剖析时对照着来。

数据剖析知识体系 数据剖析 单列剖析 多列关联剖析 空值率 唯一值 数据类型推断 外键依赖 冗余检测 整体空值率 分组空值率 唯一值数量 唯一值占比 采样推断 异常值标记 孤儿记录检测 外键失效比例 完全冗余 函数冗余 / 近似冗余 目标:形成数据质量基线,支撑后续诊断与修复 每次数据接入都跑一遍,对比基线发现变化

剖析结果怎么用?

数据剖析不是终点,是起点。我一般会把剖析结果整理成一张「数据质量体检表」,包含以下信息:

字段名 空值率 唯一值数 推断类型 外键失效 冗余标记 风险等级
customer_id 0.5% 98,500 INTEGER 1.2% 无 中
birth_date 12.3% 8,200 DATE — 与 age 冗余 高
city 3.1% 523 CATEGORY — 无 中
order_amount 0.0% 72,100 FLOAT(含异常) — 无 低

有了这张表,你就能快速回答三个问题:

  • 哪些字段需要清洗? → 风险等级高的优先处理
  • 哪些字段需要业务确认? → 空值率高、冗余字段
  • 哪些字段可以直接用? → 风险等级低的

说白了,数据剖析就是给数据做「分诊」。分诊清楚了,后面的修复才能对症下药。我见过太多团队,一上来就写清洗脚本,结果洗了半天发现洗错了方向。所以,别急,先剖析,再动手。