Skip to content

元数据卡

  • 前置知识:第1章(数据生命周期)、Python / pandas 基础
  • 预计时间:45 分钟
  • 核心难度:入门
  • 阅读模式:高度专注
  • 完成标志:能独立清洗一个含缺失值、异常值、重复行的真实数据集

你的进度

上一章你看到了数据流动的全貌——采集、传输、存储、处理、分析、归档、销毁。但你真正开始动手的时候,发现第一关就过不去:你拉出来的数据全是脏的。

日期格式五花八门,有些字段是空的,有些数值明显不对——温度传感器报了个 999°C。数据预言厅的档案管理员看了一眼说:'不奇怪。原始数据从来都是脏的。洗干净了才能用。'

你的任务

你加载了一个 CSV,调出 df.info(),看到 1000 行数据。但你往下翻几行发现:有些单元格是空的,有些日期格式不一样,有几行看起来一模一样,还有一列的值范围离谱——温度读数 999 度。原始数据几乎总是脏的。这一章教你怎么系统性地把脏数据修好。


脏数据的四种形态

几乎所有脏数据都能归入以下四类:

  1. 缺失值 —— 空着,或者用占位符(-999, N/A, None
  2. 异常值 —— 值超出了合理范围
  3. 重复数据 —— 完全重复或关键字段重复
  4. 格式不一致 —— 日期、字符串大小写、编码不统一

你的工具箱里不需要复杂的模型。先用眼睛看,再用简单的统计工具量化。


第一步:概览数据健康状况

python
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() 显示数值列的统计量——如果 max75% 高几个数量级,那就有异常值。

第二步:处理缺失值

缺失值有三种产生原因:

  • 完全随机缺失:比如传感器偶尔断连,记录没写进去
  • 条件随机缺失:比如某些类型的任务不产生某个字段
  • 非随机缺失:比如高温下传感器会关机,所以高温读数缺失——这不是偶然的

处理方法取决于缺失比例和重要性:

python
# 方法一:删除 —— 适用于缺失比例低
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)。

python
# 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(截尾)把极端值拉到边界
python
# Winsorize:把超过 99% 分位数的值拉到 99% 分位数
upper_limit = df["measurement"].quantile(0.99)
df["measurement_clipped"] = df["measurement"].clip(upper=upper_limit)

第四步:去重

重复数据有时是明显的——整行一模一样。有时是微妙的——关键字段相同但辅助字段不同。

python
# 完全重复
df.drop_duplicates(inplace=True)

# 基于关键字段去重(保留最新记录)
df.sort_values("timestamp", inplace=True)
df.drop_duplicates(subset=["mission_id", "sensor_id"], keep="last", inplace=True)

第五步:统一格式

字符串数据经常因为大小写、空格、编码不一致而"看起来不同,实际上相同"。

python
# 统一为小写 + 去空格
df["location"] = df["location"].str.strip().str.lower()

# 日期统一
df["date"] = pd.to_datetime(df["date"], errors="coerce")

errors="coerce" 会把无法解析的日期变成 NaT(缺失),你可以在下一步统一处理这些缺失值。


清洗的迭代本质

你不会一次做对。一个典型的清洗会话:

  1. 加载 → 看概览 → 发现缺失
  2. 填缺失 → 再检查 → 发现异常值
  3. 处理异常 → 再检查 → 发现重复
  4. 去重 → 再检查 → 发现格式问题
  5. 修格式 → 走到分析阶段 → 回来发现某个清洗逻辑是错的

这是正常的。建立清洗脚本,每次发现问题就加一条规则。你的脚本会逐渐变成一套可复用的数据质量规则库。


常见陷阱

  • 在原始数据上直接修改。想撤销的时候没有备份。
  • 用均值填充倾斜严重的列。均值对异常值敏感,中位数更安全。
  • 认为去重是"完全行匹配"。实际项目中几乎总是基于关键字段去重。
  • 清洗和分析混在一个脚本里。确保你能单独重跑清洗步骤。

通关挑战

  • 热身:找一个 CSV 文件,用 df.info()df.describe() 找出至少三个数据质量问题。
  • 挑战:写一个通用的数据清洗函数,接受 DataFrame 和配置字典,自动处理缺失值、异常值、重复和格式问题。
  • 排障:你的清洗脚本删除了 20% 的行。你怎么判断是清洗规则太严还是数据真的太脏?

验收标准

  • 能系统性地识别四种脏数据类型
  • 能根据缺失比例选择合适的处理策略
  • 能用 IQR 方法检测异常值并区分"要保留"和"要删除"
  • 能写出可重用的清洗脚本

旅人笔记

数据清洗没有魔法。你花在这个阶段的时间,会直接回报在分析阶段的准确性上。


下一站预告

数据干净了。现在可以开始探索它——下一章进入探索性数据分析与可视化。

Built with VitePress | Software Systems Atlas