摘要
原始数据通常包含缺失值、重复记录、格式错误和业务异常。数据清洗的目标不是简单删除问题数据,而是在理解业务含义的基础上,识别问题、选择处理策略、保留处理记录,并验证清洗结果。
本文使用 pandas 介绍缺失值统计与填充、重复记录识别、异常值检测、字段标准化和清洗结果校验,构建一套可复用的数据清洗流程。
一、背景与问题
数据质量问题通常来自多个环节:
- 用户填写不完整。
- 不同系统字段规则不一致。
- 文件导出时出现空行或重复行。
- 数据迁移造成格式变化。
- 业务状态和金额规则没有统一。
- 人工修改表格时产生异常值。
如果不处理这些问题,后续统计会出现:
| 问题 | 可能影响 |
|---|---|
| 缺失值 | 平均值、比例和分组结果失真 |
| 重复值 | 订单量、用户数和金额重复计算 |
| 异常值 | 极端值拉高或拉低统计指标 |
| 格式不一致 | 分组时同一类别被拆成多个类别 |
| 类型错误 | 日期和数值无法正确计算 |
清洗流程应遵循:
保留原始数据 → 识别问题 → 记录规则 → 执行处理 → 校验结果 → 输出清洗数据和处理报告二、核心概念
1. 缺失值
缺失值表示字段没有有效内容,但缺失原因可能不同:
- 真正未知。
- 业务上不适用。
- 尚未产生。
- 采集失败。
- 转换失败。
不同原因不能使用同一种填充方式。比如“未发生退款”和“退款金额未知”都为空时,业务含义并不相同。
2. 重复值
重复值至少分为三类:
- 完全重复:所有字段都相同。
- 主键重复:业务 ID 重复,但其他字段不同。
- 逻辑重复:字段有细微差异,实际代表同一条业务记录。
完全重复可以较容易处理,主键重复和逻辑重复需要结合业务规则判断。
3. 异常值
异常值不一定是错误。它可能是:
- 真实的大额订单。
- 正常退款。
- 录入错误。
- 单位错误。
- 测试数据。
异常检测负责发现可疑记录,最终处理需要业务确认。
4. 清洗规则
每条规则都应说明:
| 内容 | 示例 |
|---|---|
| 字段 | amount |
| 条件 | 小于 0 |
| 判断 | 只有退款订单允许 |
| 处理 | 保留并标记 |
| 原因 | 负数具有业务含义 |
不要只保存清洗后的结果,还要保存规则和处理数量。
三、工作原理
1. 清洗流程
数据读取 → 字段和类型检查 → 缺失值检查 → 重复值检查 → 业务范围检查 → 字符串和分类标准化 → 应用处理规则 → 清洗结果验证清洗顺序会影响结果。一般先完成类型转换和字段标准化,再执行缺失、重复和异常判断。
2. 删除与填充的取舍
缺失值处理常见方式:
| 方法 | 适用场景 |
|---|---|
| 删除行 | 缺失记录很少且不影响代表性 |
| 删除列 | 字段缺失率极高且当前分析不需要 |
| 常量填充 | 业务上明确存在默认值 |
| 统计值填充 | 数值近似随机缺失 |
| 分组填充 | 不同群体有不同典型值 |
| 保留并标记 | 缺失本身具有业务含义 |
删除前要记录删除数量,填充后要保留原始缺失标记。
3. 异常值检测
常见方法包括:
- 业务规则。
- 分位数和 IQR。
- Z-score。
- 时间范围。
- 分类枚举。
- 同期对比。
业务规则通常优先于纯统计方法。统计上的极端值可能正是业务最重要的记录。
四、实战示例
1. 准备数据
importpandasaspd orders=pd.DataFrame({"order_id":["A001","A002","A003","A003","A005",None],"region":["华东","华东 ","华南","华南",None,"华东"],"amount":[1200.0,800.0,None,1560.0,-20.0,500.0],"status":["paid","Paid","paid","paid","refund","paid"],"order_date":["2026-09-01","2026-09-02","2026-09-03","2026-09-03","2026-09-04","bad-date",],})2. 统一字段类型
orders["order_id"]=orders["order_id"].astype("string")orders["region"]=orders["region"].astype("string")orders["status"]=orders["status"].astype("string")orders["amount"]=pd.to_numeric(orders["amount"],errors="coerce",)orders["order_date"]=pd.to_datetime(orders["order_date"],errors="coerce",)类型转换可能产生新的缺失值,因此转换后要重新检查。
3. 保留缺失标记
orders["amount_was_missing"]=orders["amount"].isna()orders["date_was_invalid"]=orders["order_date"].isna()print(orders.isna().sum())保留标记后,后续可以区分“原始就是缺失”和“清洗过程中生成缺失”。
4. 处理字符串格式
orders["region"]=orders["region"].str.strip()orders["status"]=orders["status"].str.strip().str.lower()print(orders["region"].value_counts(dropna=False))print(orders["status"].value_counts(dropna=False))标准化字符串前应确认大小写、空格和别名是否确实代表同一业务类别。
5. 删除完全重复行
before=len(orders)orders=orders.drop_duplicates()removed_rows=before-len(orders)print("removed:",removed_rows)如果重复行具有业务含义,例如同一订单的多次状态快照,就不能直接删除。
6. 检查主键重复
duplicated_ids=orders.loc[orders["order_id"].notna()&orders["order_id"].duplicated(keep=False)]print(duplicated_ids)发现重复主键后,应根据订单明细、状态快照或导入批次判断如何处理。
7. 删除或填充缺失值
required=orders.dropna(subset=["order_id","status"],).copy()orders["amount"]=orders["amount"].fillna(0)orders["region"]=orders["region"].fillna("未知")示例中的填充只是演示。金额填充为 0 只有在业务明确表示“没有金额”时才合理。
8. 使用分组统计填充
orders["amount"]=orders["amount"].fillna(orders.groupby("region")["amount"].transform("median"))分组填充比全局平均值更能保留群体差异,但分组样本过少时可能不稳定。
9. 使用 IQR 识别异常值
amount=orders["amount"].dropna()q1=amount.quantile(0.25)q3=amount.quantile(0.75)iqr=q3-q1 lower=q1-1.5*iqr upper=q3+1.5*iqr outliers=orders.loc[(orders["amount"]<lower)|(orders["amount"]>upper)]print(outliers)IQR 只负责筛选可疑记录,不代表这些记录一定需要删除。
10. 应用业务规则
valid_statuses={"paid","pending","cancelled","refund"}invalid_status=~orders["status"].isin(valid_statuses)invalid_amount=((orders["amount"]<0)&(orders["status"]!="refund"))quality_issues=orders.loc[invalid_status|invalid_amount].copy()print(quality_issues)业务规则应输出问题记录,而不是静默删除。
11. 清洗报告
report=pd.DataFrame([{"rule":"missing_order_id","count":int(orders["order_id"].isna().sum())},{"rule":"missing_amount","count":int(orders["amount"].isna().sum())},{"rule":"invalid_status","count":int(invalid_status.sum())},{"rule":"invalid_amount","count":int(invalid_amount.sum())},])report.to_csv("output/cleaning_report.csv",index=False,encoding="utf-8-sig",)清洗报告可以作为数据管道的运行结果,帮助定位源数据质量变化。
五、常见问题与实践建议
1. 缺失值是否一定要填充?
不一定。某些分析可以忽略缺失值,某些场景需要保留“未知”状态。先理解业务含义,再选择删除、填充、保留或标记。
2. 异常值是否应该直接删除?
不应该。先保存异常记录,确认它是错误、特殊业务还是单位问题。直接删除可能丢失真实的重要事件。
3. 如何处理重复主键?
可以根据时间、版本、状态和更新时间选择保留规则。例如保留最新记录、聚合明细或拆分为订单主表和状态流水表。
4. 清洗后数据量减少怎么办?
记录每一步处理前后的行数:
print("after drop duplicates:",len(orders))如果数据量突然大幅减少,应检查过滤条件、日期解析和缺失处理逻辑。
5. 清洗规则应该写在 Notebook 里吗?
探索阶段可以写在 Notebook 中,稳定后应提取到函数或模块,并配套测试、日志和配置。
六、进阶思考
1. 原始数据不可变
推荐分层保存:
data/raw/ 原始数据,只读 data/interim/ 中间结果 data/processed/ 清洗后的数据 output/ 报告和交付结果不要直接覆盖原始文件,否则无法复核清洗前后的差异。
2. 清洗规则版本化
规则可能随着业务变化而变化。建议记录:
- 规则版本。
- 执行时间。
- 输入文件或数据批次。
- 处理行数。
- 删除行数。
- 输出文件。
3. 不同字段使用不同策略
金额、订单号、日期和状态不能使用同一套通用逻辑。字段级规则更容易解释,也更适合单元测试。
4. 数据清洗与数据质量
清洗是处理问题,数据质量是持续监控问题。稳定流程需要在清洗前检查质量,在清洗后验证质量,并在问题超阈值时停止发布。
结论
数据清洗的关键是先识别问题,再根据业务含义选择处理方法。缺失值、重复值和异常值都不能机械处理,应该保留规则、统计和审计信息。
下一篇将重点讲解数据类型转换和日期时间处理,解决字符串、数字、日期、时区和时间范围统计中的常见问题。
参考资料
- pandas 缺失值处理:https://pandas.pydata.org/docs/user_guide/missing_data.html
- pandas 重复数据处理:https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.drop_duplicates.html
- pandas 数据类型:https://pandas.pydata.org/docs/user_guide/basics.html