4. 数据清洗基础:缺失值处理、异常值检测与重复数据去重
数据清洗是数据科学流程中最耗时但最关键的环节。原始数据通常包含缺失值、异常值和重复记录,若不处理,将直接影响模型训练效果与业务决策的准确性。本章聚焦三大核心清洗任务:缺失值处理、异常值检测与重复数据去重,提供从理论到代码的完整实战方案。
4.1 缺失值处理
缺失值指数据集中某个或某些字段为空(NaN、None、空字符串等)。处理方式主要分为三类:删除、填充与插值。
4.1.1 删除缺失值
当缺失比例极低(如 < 5%)且缺失完全随机时,可直接删除含缺失值的行或列。但需注意:删除会损失样本量,若缺失非随机,可能引入偏差。
| 方法 | 适用场景 | 代码示例(Pandas) |
|---|---|---|
| 删除含缺失的行 | 缺失比例低,样本充足 | df.dropna(axis=0) |
| 删除含缺失的列 | 某列缺失率极高(如 > 80%) | df.dropna(axis=1, thresh=len(df)*0.8) |
| 删除全部缺失的行 | 整行无有效数据 | df.dropna(how='all') |
4.1.2 填充缺失值
填充是更常用的策略,通过统计量或模型预测填补空缺。常见填充方式包括:
- 固定值填充:适用于分类变量(如用“未知”填充)或业务已知默认值。
- 统计量填充:数值型常用均值、中位数、众数。均值对异常值敏感,中位数更稳健。
- 前向/后向填充:适用于时间序列数据,用前一个或后一个有效值填充。
- KNN 填充:基于近邻样本的均值或众数填充,保留数据分布结构。
# 统计量填充示例
import pandas as pd
import numpy as np
df = pd.DataFrame({'A': [1, 2, np.nan, 4, 5],
'B': [10, np.nan, 30, 40, 50]})
# 均值填充
df['A'].fillna(df['A'].mean(), inplace=True)
# 中位数填充
df['B'].fillna(df['B'].median(), inplace=True)
# 前向填充(时间序列)
df['C'] = df['A'].fillna(method='ffill')
4.1.3 插值法
插值利用已知数据点之间的数学关系估算缺失值,适用于连续型数据。常用方法包括线性插值、多项式插值和样条插值。
# 线性插值示例
df['D'] = [1, np.nan, 3, np.nan, 5]
df['D'].interpolate(method='linear', inplace=True)
print(df['D'])
# 输出: [1.0, 2.0, 3.0, 4.0, 5.0]
4.2 异常值检测
异常值(离群点)是显著偏离其他观测值的样本点。异常值可能由测量误差、数据录入错误或真实稀有事件引起。常用检测方法包括 3σ 原则、IQR 法和 Z-score 法。
4.2.1 3σ 原则(拉依达准则)
假设数据服从正态分布,约 99.7% 的数据落在均值 ±3 个标准差范围内。超出此范围的值视为异常。
import numpy as np
def detect_outliers_3sigma(data):
mean = np.mean(data)
std = np.std(data)
lower_bound = mean - 3 * std
upper_bound = mean + 3 * std
outliers = [x for x in data if x < lower_bound or x > upper_bound]
return outliers, lower_bound, upper_bound
data = [10, 12, 11, 13, 100, 9, 11, 12, 10, 11]
outliers, lb, ub = detect_outliers_3sigma(data)
print(f"异常值: {outliers}, 下界: {lb:.2f}, 上界: {ub:.2f}")
4.2.2 IQR 法(四分位距法)
IQR 法不依赖正态分布假设,适用于偏态分布。定义 Q1(25% 分位数)和 Q3(75% 分位数),IQR = Q3 - Q1。异常值定义为小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR 的值。
def detect_outliers_iqr(data):
q1 = np.percentile(data, 25)
q3 = np.percentile(data, 75)
iqr = q3 - q1
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
outliers = [x for x in data if x < lower_bound or x > upper_bound]
return outliers, lower_bound, upper_bound
outliers, lb, ub = detect_outliers_iqr(data)
print(f"异常值: {outliers}, 下界: {lb:.2f}, 上界: {ub:.2f}")
4.2.3 Z-score 法
Z-score 衡量数据点与均值的偏离程度,以标准差为单位。通常 |Z| > 3 视为异常。Z-score 对极端值敏感,适合数据量较大且近似正态的场景。
from scipy import stats
def detect_outliers_zscore(data, threshold=3):
z_scores = np.abs(stats.zscore(data))
outliers = [data[i] for i, z in enumerate(z_scores) if z > threshold]
return outliers, z_scores
outliers, z_scores = detect_outliers_zscore(data)
print(f"异常值: {outliers}, Z-scores: {z_scores}")
| 方法 | 假设条件 | 优点 | 缺点 |
|---|---|---|---|
| 3σ 原则 | 数据服从正态分布 | 简单直观,计算快 | 对非正态分布效果差 |
| IQR 法 | 无分布假设 | 稳健,不受极端值影响 | 对样本量敏感 |
| Z-score 法 | 近似正态分布 | 标准化后易于比较 | 小样本时不可靠 |
4.3 重复数据去重
重复数据指数据集中完全一致或部分关键字段相同的记录。重复数据会导致统计偏差、模型过拟合和存储浪费。去重是清洗流程中成本最低但收益最高的步骤。
4.3.1 完全重复去重
所有字段值完全相同的记录。使用 Pandas 的 duplicated() 和 drop_duplicates() 即可处理。
# 完全重复去重
df = pd.DataFrame({
'id': [1, 2, 2, 3, 4],
'name': ['A', 'B', 'B', 'C', 'D'],
'value': [10, 20, 20, 30, 40]
})
# 检测重复
print(df.duplicated()) # 返回布尔序列
# 删除重复(保留第一个)
df_unique = df.drop_duplicates()
print(df_unique)
4.3.2 部分重复去重
基于指定字段判断重复。例如,同一用户 ID 出现多次但其他字段不同,可能只需保留最新记录。
# 基于 'id' 字段去重,保留最后一条记录
df_dedup = df.drop_duplicates(subset=['id'], keep='last')
print(df_dedup)
4.3.3 模糊匹配去重
当数据存在拼写差异、空格、大小写不一致时,需先标准化再匹配。常用方法包括:
- 字符串标准化:去除空格、统一大小写、替换特殊字符。
- 编辑距离(Levenshtein):计算字符串相似度,设定阈值判定重复。
- 基于库的匹配:使用
fuzzywuzzy、recordlinkage等库进行近似匹配。
# 字符串标准化示例
df['name_clean'] = df['name'].str.strip().str.lower()
# 模糊匹配(需安装 fuzzywuzzy)
# from fuzzywuzzy import fuzz
# df['similarity'] = df.apply(lambda row: fuzz.ratio(row['name'], row['name_clean']), axis=1)
- 先做完全重复去重,再做部分重复去重。
- 定义去重字段时,优先选择业务唯一标识(如用户 ID、订单号)。
- 模糊匹配前先做字符串标准化,减少计算量。
- 保留去重日志,记录删除的记录数,便于审计。
4.4 本章小结
数据清洗是数据科学流程的基石。本章覆盖了三大核心任务:
- 缺失值处理:根据缺失比例和数据类型选择删除、填充或插值。
- 异常值检测:3σ 原则适用于正态分布,IQR 法更稳健,Z-score 法适合大数据量。
- 重复数据去重:从完全重复到模糊匹配,逐步提升数据质量。