Pandas Data Cleaning:实用工作流(2025)
Updated on
脏数据在早期就阻塞分析。随手 fillna 或字符串替换常常掩盖真正的问题:错误类型、静默重复或把指标拉偏的外值。
PAS 视角:
- 问题: 类型混杂、缺失和重复会破坏 join 并扭曲统计。
- 激化: 临时补丁把问题拖到后期,导致返工。
- 解决: 可复用的清洗清单:统一类型、有意处理缺失、应对外值、去重,并在导出前验证。
工作流速览
| 步骤 | 任务 | 示例 |
|---|---|---|
| 1. 画像 | 看列/缺失/唯一值 | df.info(), df.describe(include="all") |
| 2. 规范列 | 清理列名/文本 | df.columns = df.columns.str.strip().str.lower() |
| 3. 校正类型 | to_datetime、to_numeric、astype("category") | df["date"] = pd.to_datetime(df["date"], errors="coerce") |
| 4. 处理缺失 | 按规则删/补 | df["age"] = df["age"].fillna(df["age"].median()) |
| 5. 处理外值 | 裁剪或打标 | df["rev_cap"] = df["revenue"].clip(upper=df["revenue"].quantile(0.99)) |
| 6. 去重 | 清理全/半重复 | df.drop_duplicates(subset=["id", "date"], keep="last") |
| 7. 验证 | 断言范围/类别 | assert df["score"].between(0,100).all() |
1) 快速画像
summary = {
"rows": len(df),
"columns": df.shape[1],
"nulls": df.isna().sum(),
"unique_counts": df.nunique(),
}- 早发现混合类型(
df.info())。 - 用
df.describe(percentiles=[0.01,0.99])查看分布尾部。
2) 规范列与文本
df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
df["country"] = df["country"].str.strip().str.title()- 统一的名字让 join 和代码更简单。
- 有奇怪字符时可用
.str.normalize("NFKC")。
3) 修正数据类型
df["date"] = pd.to_datetime(df["date"], errors="coerce", utc=True)
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df["segment"] = df["segment"].astype("category")errors="coerce"让坏值显式为NaT/NaN。convert_dtypes()可批量得到可空、省内存的类型。
4) 处理缺失值
df["age"] = df["age"].fillna(df["age"].median())
df["city"] = df["city"].fillna("Unknown")
df = df.dropna(subset=["id"]) # 必填键- 分列决策:数值→中位/均值;分类→众数/占位符。
- 保留信号:
df["age_imputed"] = df["age"].isna()。
5) 应对外值
upper = df["revenue"].quantile(0.99)
lower = df["revenue"].quantile(0.01)
df["revenue_capped"] = df["revenue"].clip(lower=lower, upper=upper)- 比率可用 Z 分数过滤:
abs(z) < 3。 - 金额类优先裁剪而非删除,避免样本量改变。
6) 安全去重
df = df.drop_duplicates(subset=["id", "date"], keep="last")- 确认唯一键:
assert df.duplicated(["id"]).sum() == 0。 - 需要模糊匹配时先归一化(如邮箱小写)。
7) 导出前验证
assert df["score"].between(0, 100).all()
valid_segments = {"basic", "pro", "enterprise"}
assert df["segment"].isin(valid_segments).all()- 测试中可用
pd.testing.assert_frame_equal比较输出。 - 在流水线中加入轻量行数/缺失检查以防回归。
端到端迷你流程
def clean(df):
df = df.copy()
df.columns = df.columns.str.strip().str.lower()
df["date"] = pd.to_datetime(df["date"], errors="coerce")
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df["amount"] = df["amount"].fillna(df["amount"].median())
df["segment"] = df["segment"].fillna("unknown").str.lower()
df = df.dropna(subset=["id"])
df = df.drop_duplicates(subset=["id", "date"], keep="last")
assert df["amount"].ge(0).all()
return df- 先复制,避免修改原数据。
- 用
df.pipe(clean)保持链式调用清晰。
相关指南
关键要点
- 统一命名、强制类型、有意处理缺失。
- 外值优先裁剪/打标,不要静默删除。
- 导出前验证键和数值范围,确保质量。