元数据卡
- 前置知识:第1章(数据生命周期)、Python / pandas 基础
- 预计时间:45 分钟
- 核心难度:入门
- 阅读模式:高度专注
- 完成标志:能独立清洗一个含缺失值、异常值、重复行的真实数据集
你的进度
上一章你看到了数据流动的全貌——采集、传输、存储、处理、分析、归档、销毁。但你真正开始动手的时候,发现第一关就过不去:你拉出来的数据全是脏的。
日期格式五花八门,有些字段是空的,有些数值明显不对——温度传感器报了个 999°C。数据预言厅的档案管理员看了一眼说:'不奇怪。原始数据从来都是脏的。洗干净了才能用。'
你的任务
你加载了一个 CSV,调出 df.info(),看到 1000 行数据。但你往下翻几行发现:有些单元格是空的,有些日期格式不一样,有几行看起来一模一样,还有一列的值范围离谱——温度读数 999 度。原始数据几乎总是脏的。这一章教你怎么系统性地把脏数据修好。
脏数据的四种形态
几乎所有脏数据都能归入以下四类:
- 缺失值 —— 空着,或者用占位符(
-999,N/A,None) - 异常值 —— 值超出了合理范围
- 重复数据 —— 完全重复或关键字段重复
- 格式不一致 —— 日期、字符串大小写、编码不统一
你的工具箱里不需要复杂的模型。先用眼睛看,再用简单的统计工具量化。
第一步:概览数据健康状况
import pandas as pd
import numpy as np
df = pd.read_csv("sensor_readings.csv")
# 先看数据概览
print(df.info())
print(df.describe())df.info() 会告诉你每列的非空数量。如果某列的行数明显少于总行数,那列就有缺失值。df.describe() 显示数值列的统计量——如果 max 比 75% 高几个数量级,那就有异常值。
第二步:处理缺失值
缺失值有三种产生原因:
- 完全随机缺失:比如传感器偶尔断连,记录没写进去
- 条件随机缺失:比如某些类型的任务不产生某个字段
- 非随机缺失:比如高温下传感器会关机,所以高温读数缺失——这不是偶然的
处理方法取决于缺失比例和重要性:
# 方法一:删除 —— 适用于缺失比例低
df_clean = df.dropna(subset=["critical_field"])
# 方法二:填充 —— 适用于数值型
df["temperature"].fillna(df["temperature"].median(), inplace=True)
# 方法三:向前填充 —— 适用于时序数据
df["status"].ffill(inplace=True)
# 方法四:标记缺失 —— 保留信息
df["speed_missing"] = df["speed"].isna().astype(int)一个经验规则:如果某列缺失超过 70%,你大概率应该直接扔掉这列。如果你需要保留它,要问自己:为什么缺失这么多,它真的还有价值吗?
第三步:检测异常值
最简单的异常检测方法不是机器学习——而是四分位距(IQR)。
# IQR 方法
Q1 = df["measurement"].quantile(0.25)
Q3 = df["measurement"].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df["measurement"] < lower_bound) | (df["measurement"] > upper_bound)]
print(f"检测到 {len(outliers)} 个异常值")1.5 倍 IQR 是经验值,不是硬性规则。如果你的数据有特定的物理约束(比如温度不可能超过 200 度),用领域知识判断优先级更高。
异常值处理策略:
- 领域上不可能的值 → 直接删除或置空
- 虽反常但物理上可能的值 → 保留但标记,不轻易删除
- 边界震荡的数据 → 用 Winsorize(截尾)把极端值拉到边界
# Winsorize:把超过 99% 分位数的值拉到 99% 分位数
upper_limit = df["measurement"].quantile(0.99)
df["measurement_clipped"] = df["measurement"].clip(upper=upper_limit)第四步:去重
重复数据有时是明显的——整行一模一样。有时是微妙的——关键字段相同但辅助字段不同。
# 完全重复
df.drop_duplicates(inplace=True)
# 基于关键字段去重(保留最新记录)
df.sort_values("timestamp", inplace=True)
df.drop_duplicates(subset=["mission_id", "sensor_id"], keep="last", inplace=True)第五步:统一格式
字符串数据经常因为大小写、空格、编码不一致而"看起来不同,实际上相同"。
# 统一为小写 + 去空格
df["location"] = df["location"].str.strip().str.lower()
# 日期统一
df["date"] = pd.to_datetime(df["date"], errors="coerce")errors="coerce" 会把无法解析的日期变成 NaT(缺失),你可以在下一步统一处理这些缺失值。
清洗的迭代本质
你不会一次做对。一个典型的清洗会话:
- 加载 → 看概览 → 发现缺失
- 填缺失 → 再检查 → 发现异常值
- 处理异常 → 再检查 → 发现重复
- 去重 → 再检查 → 发现格式问题
- 修格式 → 走到分析阶段 → 回来发现某个清洗逻辑是错的
这是正常的。建立清洗脚本,每次发现问题就加一条规则。你的脚本会逐渐变成一套可复用的数据质量规则库。
常见陷阱
- 在原始数据上直接修改。想撤销的时候没有备份。
- 用均值填充倾斜严重的列。均值对异常值敏感,中位数更安全。
- 认为去重是"完全行匹配"。实际项目中几乎总是基于关键字段去重。
- 清洗和分析混在一个脚本里。确保你能单独重跑清洗步骤。
通关挑战
- 热身:找一个 CSV 文件,用
df.info()和df.describe()找出至少三个数据质量问题。 - 挑战:写一个通用的数据清洗函数,接受 DataFrame 和配置字典,自动处理缺失值、异常值、重复和格式问题。
- 排障:你的清洗脚本删除了 20% 的行。你怎么判断是清洗规则太严还是数据真的太脏?
验收标准
- 能系统性地识别四种脏数据类型
- 能根据缺失比例选择合适的处理策略
- 能用 IQR 方法检测异常值并区分"要保留"和"要删除"
- 能写出可重用的清洗脚本
旅人笔记
数据清洗没有魔法。你花在这个阶段的时间,会直接回报在分析阶段的准确性上。
下一站预告
数据干净了。现在可以开始探索它——下一章进入探索性数据分析与可视化。