数据剖析:先摸清数据的脾气
数据质量诊断的第一步是什么?不是写规则,不是跑脚本。是「摸」。你得先把手伸进去,感受一下数据长什么样。
我刚开始做数据治理那会儿,接过一个客户的数据集。对方说「这表很干净,就几万条」。我一跑单列剖析,好家伙,空值率超过 60% 的列有七八个。所以你看,别信「感觉」,要信数据。
这一章,我们就来聊聊数据剖析的两个基本功:单列剖析和多列关联剖析。说白了,就是先看每一列自己有没有毛病,再看列与列之间有没有扯皮。
单列剖析:给每一列做个体检
单列剖析,就是针对数据表里的每一列,单独计算几个关键指标。我一般会看三个东西:空值率、唯一值、数据类型。
1. 空值率:数据缺失有多严重?
空值率 = 空值数量 / 总记录数。这个指标最直观,也最容易踩坑。
我曾经遇到一个项目,业务方说「客户手机号字段基本都填了」。我一查,空值率只有 5%。但再往下看,那 5% 的空值全是 VIP 客户。你想想看,VIP 客户的手机号缺失,这影响有多大?
所以,空值率不能只看整体,还要结合业务含义。我习惯的做法是:
- 先算整体空值率,快速定位「问题列」
- 再按关键维度分组,比如按客户等级、按时间分区
- 最后看空值分布,是不是集中在某个时间段或某个业务环节
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」,但客户表里已经删掉了这个客户。这就是「孤儿记录」。外键约束在数据库层面可能没建,但业务逻辑上它应该存在。
我的检测方法很简单:
- 找出所有「应该存在外键关系」的字段对
- 用 LEFT JOIN 找出「在子表存在,但在父表不存在」的记录
- 计算「外键失效比例」
-- 外键依赖检测示例
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(含异常) | — | 无 | 低 |
有了这张表,你就能快速回答三个问题:
- 哪些字段需要清洗? → 风险等级高的优先处理
- 哪些字段需要业务确认? → 空值率高、冗余字段
- 哪些字段可以直接用? → 风险等级低的
说白了,数据剖析就是给数据做「分诊」。分诊清楚了,后面的修复才能对症下药。我见过太多团队,一上来就写清洗脚本,结果洗了半天发现洗错了方向。所以,别急,先剖析,再动手。