本节摘要:清洗的第一步不是动手改数据,而是给数据做一次全面体检。本节教你用 info、describe 快速扫描结构,用自定义检查函数揪出缺失、重复、异常、类型错乱四类问题,最后产出一份"问题清单"作为清洗的行动地图。
阅读完本节,你应当能够:
拿到一份新数据的正确姿势是什么?直接开跑分析是大忌——你不知道它缺了多少、有没有重复、金额列里是不是混着字符串。先体检再动手,能避免后面一半的返工。就像看医生,先做全套检查拿到报告,才谈得上治疗。
这四类问题往往同时存在,且会互相放大:类型错乱的金额列,统计出来全是错的;带着异常值算均值,直接被大单带偏。体检的意义就是把它们一次性摊开。
import pandas as pd df = pd.read_csv("orders.csv") print(df.shape) # 行数、列数 df.info() # 类型与非空 df.describe() # 数值统计
💡 关键直觉:
info()的"非空计数"是最快的缺失扫描——哪一列的数字明显少于行数,哪列就有缺失。describe()的 max 异常巨大,多半有异常值。
def quality_report(df): report = { "行数": df.shape[0], "列数": df.shape[1], "每列缺失数": df.isnull().sum().to_dict(), "重复行数": df.duplicated().sum(), "列类型": df.dtypes.astype(str).to_dict(), } for col in df.columns: report[f"{col} 唯一值数"] = df[col].nunique() return report print(quality_report(df))
一份检查报告输出四类关键信息:规模、缺失、重复、类型。把这些信息对照业务常识,就能圈出可疑列。
| 问题 | 表现 | 怎么发现 |
|---|---|---|
| 缺失 | 空值、NaN | isnull().sum() |
| 重复 | 同一订单出现多次 | duplicated().sum() |
| 类型错乱 | 金额列是 object | dtypes、info() |
| 异常值 | max 离谱 | describe()、箱线图 |
| 格式不一 | 日期、空格、大小写 | head()、value_counts() |
| 不一致记录 | 北京 vs 北京市 | value_counts() 观察 |
import pandas as pd import numpy as np df = pd.DataFrame({ "订单号": ["A001", "A002", "A002", "A004", None], "金额": [199, 355, 355, 12999, 89], "门店": ["北京", "上海", "上海", "北京市", "广州"], "日期": ["2026-01-01", "2026-01-01", "2026-01-01", "20260102", "2026-01-03"], }) print("缺失:", df.isnull().sum().sum()) print("重复行:", df.duplicated().sum()) print("金额最大值异常:", df["金额"].max()) # 12999 明显离群 print("门店取值:", df["门店"].value_counts().to_dict()) # 北京 与 北京市 不一致 print("日期格式:", df["日期"].tolist()) # 混用两种格式
体检结果一眼可见五个问题:1 个缺失订单号、1 行重复、12999 的异常金额、门店名不一致、日期格式混用。这份清单就是后续四节的行动地图。
⚠️ 常见坑:体检时看到"重复行"不要直接删——先看它是不是业务上合法的重复。比如同一订单号下多件商品,每行一条,订单号重复是正常的,重复的应该是"订单号+商品"组合。
| 优先级 | 问题类型 | 理由 |
|---|---|---|
| 高 | 缺失、类型错乱 | 影响所有下游计算 |
| 高 | 重复 | 成倍放大统计 |
| 中 | 异常值 | 影响均值类指标 |
| 中 | 格式不一 | 影响分组与匹配 |
| 低 | 不一致记录 | 影响展示与聚合 |
info() 只给整体概览,逐列细查能发现更深的问题:
# 每列类型与缺失 for col in df.columns: print(f"{col}: {df[col].dtype}, 缺失 {df[col].isnull().sum()}, " f"唯一值 {df[col].nunique()}") # 数值列的范围检查 print("金额范围:", df["金额"].min(), "-", df["金额"].max()) print("负值订单数:", (df["金额"] < 0).sum())
"唯一值数"是体检的重要指标:客户 ID 应该基本都不同(唯一值≈行数),门店应该很少(唯一值个位数)。唯一值数明显违背业务直觉的列,就是问题的藏身处。
团队里数据源不止一个时,值得维护一份"数据字典":每列的名称、含义、类型、取值范围、是否允许缺失。体检时按字典逐项核对,新增字段第一时间更新字典。这样任何数据源变动,几分钟就能定位影响面,而不是对着陌生列猜含义。
把 quality_report 写进管道入口,每次数据更新自动跑一遍:
def quality_gate(df): """数据质量闸门:不达标直接报错,阻止脏数据进入下游。""" report = quality_report(df) assert report["重复行数"] == 0, "存在重复行" assert report["每列缺失数"].get("金额", 0) == 0, "金额列有缺失" print("质量闸门通过") return df
"质量闸门"的思路在工程化中很关键——与其让脏数据一路流到分析结果里才爆雷,不如在入口处拦住它。
isnull().sum() 扫缺失、duplicated().sum() 扫重复、describe() 看极值体检报告出来了,下一节逐个击破——先处理最普遍的缺失值与重复值。