4、脏数据识别与分类:重复数据、缺失值、异常值、格式错误、逻辑矛盾的识别技巧
数据清理的第一步是什么?很多人上来就写代码,这其实是个坑。
我个人习惯是,先摸清楚数据里都有哪些“脏东西”。做项目久了你会发现,脏数据翻来覆去就是那几类。你如果能快速把它们认出来,后面怎么处理就清晰多了。
这一节,我就跟你聊聊我最常碰到的五类脏数据。每类我都会结合自己踩过的坑来讲。
4.1 重复数据:看着一样,实则不同
重复数据可能是最常见的脏数据了。但你真的以为重复就是“一模一样”吗?
没那么简单。我遇到过几种情况:
| 重复类型 | 描述 | 举个例子 |
|---|---|---|
| 完全重复 | 所有字段值都一样 | 两条记录:姓名=张三,手机号=13800138000 |
| 关键字段重复 | 唯一标识字段相同,其他字段不同 | 订单号相同,但收货地址不同 |
| 相似重复 | 看起来像,但有小差异 | 一条写“北京市”,另一条写“北京” |
| 时间戳重复 | 同一时间产生的多条记录 | 日志里同一毫秒内两条操作记录 |
检测重复数据,我常用的方法是:
# 检测完全重复行
import pandas as pd
df = pd.read_csv('customer_data.csv')
duplicates = df.duplicated(keep=False)
print(f"完全重复行数:{duplicates.sum()}")
# 检测按指定字段重复
key_duplicates = df.duplicated(subset=['customer_id'], keep='first')
print(f"按客户ID重复的行数:{key_duplicates.sum()}")
4.2 缺失值:空的不一定没用
缺失值处理是个老生常谈的话题了。但我想说的是,不是所有空值都是“坏的”。
你想想看,有些字段为空,本身就是一种信息。比如一个员工的“离职日期”为空,说明这个人还在职。如果你简单粗暴地把空值都填充成“0”或者“未知”,反而会把信息弄丢。
缺失值检测很简单,但分类才是关键:
- 完全随机缺失(MCAR):缺失和任何已知字段无关。比如系统随机丢包导致的数据丢失。
- 随机缺失(MAR):缺失依赖于其他字段。比如只填写了收入的用户才会填写“收入类型”,没填收入的自然也没填收入类型。
- 非随机缺失(NMAR):缺失和字段本身有关。比如收入高的人更不愿意填写收入。
你可能会问:“分这么细干嘛?”因为处理方式不一样啊。MCAR可以直接删除,MAR需要建模补全,NMAR就比较棘手了。
# 快速查看缺失情况
missing_summary = df.isnull().sum()
missing_pct = df.isnull().mean() * 100
missing_df = pd.DataFrame({
'缺失数量': missing_summary,
'缺失比例(%)': missing_pct.round(2)
})
print(missing_df[missing_df['缺失数量'] > 0])
4.3 异常值:不是所有离群点都是错误
说到异常值,很多人的第一反应就是“有问题,删掉”。但这样做太武断了。
我在一个电商项目中遇到过一件事:某个用户的月消费金额是其他人的100倍,初步判断是异常值,差点就准备删了。但后来一查,这人是公司的大客户,做批发的。那这笔数据反而是最宝贵的
- Z-Score法:适合正态分布的数据,Z值大于3的一般是异常
- IQR法:四分位距法,Q1 - 1.5*IQR 到 Q3 + 1.5*IQR 之外的是异常
- 业务规则法:比如人的年龄不能大于150岁,这个比前两种都靠谱
我建议的做法是:先用算法检测,再用业务规则复核。算法告诉你有多少个“数学上的异常”,业务告诉你哪些是“真的有问题的异常”。
4.4 格式错误:数据标准化的头号杀手
格式错误有多烦?我举个例子你就懂了。
同一个手机号字段,你能看到这些写法:
- 13800138000 (正常格式)
- 138-0013-8000 (带横线)
- +86 13800138000 (带国家码)
- 138 0013 8000 (带空格)
- 1380013800 (少一位)
如果你要把这些数据做去重或者关联,这五条记录会被识别成五个不同的手机号。
常见的格式错误包括:
| 类型 | 问题表现 | 影响 |
|---|---|---|
| 编码不一致 | 部分记录是UTF-8,部分是GBK | 乱码,无法正常读取 |
| 分隔符混用 | 有的用逗号,有的用制表符 | 数据解析错误 |
| 日期格式不统一 | 2024-01-15 vs 01/15/2024 vs 2024年1月15日 | 排序、计算都出错 |
| 大小写不一致 | Beijing, BEIJING, beijing | 分组统计结果不对 |
| 空格前后缀 | "张三" vs " 张三" vs "张三 " | 看起来一样,程序认为不同 |
4.5 逻辑矛盾:数据之间互相打架
这类脏数据是最隐蔽的,因为每个字段单独看都没问题。但放在一起看,就出问题了。
我举个例子:
- 员工入职日期:2020年3月
- 员工出生日期:1998年6月
- 工资发放日:2020年2月
你看,工资发放日比入职日期还早两个月,这显然不合理。但如果你只看单个字段,哪个都没错。
逻辑矛盾常见的场景:
- 时间先后矛盾:结束时间早于开始时间
- 包含关系矛盾:父类与子类分类不一致,比如商品在“数码”类目下,但标签写的是“食品”
- 金额计算矛盾:订单总价不等于商品单价乘以数量
- 状态矛盾:同一订单同时出现“已发货”和“待付款”
识别这类问题,靠“规则引擎”最有效。你需要把业务逻辑梳理清楚,然后写成规则。
4.6 脏数据识别全景图
说了这么多,我把整个脏数据识别与分类的流程画成了一张图,你可以对照着理解。
这五类脏数据,看起来是独立的问题,但实际项目里它们经常交织在一起。一条数据可能既有格式错误,又存在逻辑矛盾。所以我的建议是:先分类识别,再交叉复核。