2. 数据预处理基础:数据清洗与转换

在数据分析与异常检测项目中,原始数据通常包含缺失值、重复记录、不一致格式以及量纲差异等问题。数据预处理是保证后续算法有效性的关键步骤。本章将系统讲解数据清洗、数据类型转换以及标准化与归一化的核心方法。

2.1 数据清洗:缺失值处理

缺失值是指数据集中某个或某些字段为空或不存在的情况。常见的缺失值处理策略包括删除、填充与插值。

策略 方法 适用场景 注意事项
删除 删除含缺失值的行或列 缺失比例低(<5%),且数据量充足 可能丢失有效信息,导致样本偏差
固定值填充 用 0、均值、中位数、众数填充 数值型特征,缺失随机分布 均值受异常值影响大,中位数更稳健
前向/后向填充 用上一个或下一个有效值填充 时间序列数据,缺失值连续 需确保数据有序,避免跨周期填充
插值法 线性插值、多项式插值、KNN 插值 连续型特征,缺失值较少 计算成本较高,需评估插值合理性
实践建议: 在异常检测场景中,优先使用中位数或 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, np.nan]})

# 删除含缺失值的行
df_drop = df.dropna()

# 用列中位数填充
df_fill = df.fillna(df.median())

# 线性插值
df_interp = df.interpolate(method='linear')
关键点: 使用 df.isnull().sum() 快速检查每列缺失数量;使用 df.duplicated() 检测重复行。

2.2 数据清洗:重复值处理

重复值指数据集中完全一致或部分关键字段一致的多条记录。重复数据会导致统计偏差,并可能使异常检测算法对重复样本过度拟合。

  • 完全重复: 所有字段值相同,直接删除保留一条。
  • 部分重复: 仅主键或关键字段相同,需根据业务规则合并或去重。
  • 近似重复: 字段值存在微小差异(如拼写错误),需使用模糊匹配或编辑距离识别。
# 检测与删除完全重复行
duplicates = df.duplicated()
df_unique = df.drop_duplicates()

# 按指定列去重
df_unique_subset = df.drop_duplicates(subset=['A', 'B'], keep='first')
注意: 在时间序列或日志数据中,重复值可能是正常业务行为(如重试请求),需结合时间戳判断是否真正重复。

2.3 数据类型转换

数据类型转换是将数据从一种类型转换为另一种类型,以满足分析或建模要求。常见转换包括字符串转数值、类别编码、时间格式统一等。

原始类型 目标类型 常用方法 示例
字符串 数值(int/float) pd.to_numeric() df['col'] = pd.to_numeric(df['col'], errors='coerce')
字符串 日期时间 pd.to_datetime() df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
类别文本 整数编码 LabelEncoder / pd.factorize() df['cat'] = pd.factorize(df['cat'])[0]
布尔值 整数(0/1) astype(int) df['flag'] = df['flag'].astype(int)
# 数据类型转换示例
df['price'] = pd.to_numeric(df['price'], errors='coerce')
df['timestamp'] = pd.to_datetime(df['timestamp'])
df['category_code'] = pd.factorize(df['category'])[0]
提示: 使用 df.dtypes 查看当前各列类型;使用 df['col'].astype('float32') 可降低内存占用。

2.4 数据标准化与归一化

标准化与归一化用于消除特征之间的量纲差异,使不同尺度的特征在模型中具有可比性。异常检测算法(如 KNN、聚类、距离度量)对特征尺度敏感,因此预处理至关重要。

2.4.1 Z-score 标准化

Z-score 标准化将数据转换为均值为 0、标准差为 1 的分布。公式为:

z = (x - μ) / σ

其中 μ 为均值,σ 为标准差。适用于数据近似正态分布或存在异常值的场景。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
df_scaled = scaler.fit_transform(df[['feature1', 'feature2']])

2.4.2 Min-Max 归一化

Min-Max 归一化将数据线性映射到 [0, 1] 区间。公式为:

x' = (x - min) / (max - min)

适用于数据有明确边界、且不包含极端异常值的场景。

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
df_norm = scaler.fit_transform(df[['feature1', 'feature2']])
方法 输出范围 对异常值敏感度 适用算法
Z-score 标准化 无固定范围(通常 -3~3) 中等(受异常值影响均值与标准差) 线性回归、SVM、PCA、KNN
Min-Max 归一化 [0, 1] 高(异常值会压缩正常值区间) 神经网络、距离度量、图像处理
选择建议: 若数据存在明显异常值,优先使用 Z-score 标准化;若数据分布均匀且需保持原始比例关系,使用 Min-Max 归一化。
数据预处理流程与标准化/归一化对比 原始数据 缺失值处理 重复值处理 数据类型转换 标准化 / 归一化 干净数据集 Z-score 标准化 Min-Max 归一化 公式对比 Z-score: z = (x - μ) / σ Min-Max: x' = (x - min) / (max - min) 输出范围 Z-score: 无固定范围(通常 -3 ~ 3) Min-Max: 固定 [0, 1] 异常值敏感度 Z-score: 中等(受均值与标准差影响) Min-Max: 高(异常值压缩正常值区间)

2.5 综合案例:预处理流水线

以下代码展示一个完整的预处理流水线,包含缺失值填充、重复值删除、类型转换与标准化。

import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler

# 模拟原始数据
data = {
    'age': [25, 30, np.nan, 35, 40, 30],
    'salary': [50000, 60000, 55000, np.nan, 70000, 60000],
    'department': ['IT', 'HR', 'IT', 'HR', 'IT', 'HR'],
    'join_date': ['2020-01-15', '2019-03-22', '2021-07-01', '2020-11-30', '2018-05-12', '2019-03-22']
}
df = pd.DataFrame(data)

# 1. 缺失值处理:用中位数填充数值列
df['age'] = df['age'].fillna(df['age'].median())
df['salary'] = df['salary'].fillna(df['salary'].median())

# 2. 重复值处理:删除完全重复行
df = df.drop_duplicates()

# 3. 数据类型转换
df['join_date'] = pd.to_datetime(df['join_date'])
df['department_code'] = pd.factorize(df['department'])[0]

# 4. 标准化
scaler = StandardScaler()
df[['age_scaled', 'salary_scaled']] = scaler.fit_transform(df[['age', 'salary']])

print(df)
输出说明: 最终数据框包含原始列、编码列与标准化列,可直接用于异常检测算法输入。

2.6 常见问题与避坑指南

  • 缺失值填充后未检查分布: 填充后应使用 df.describe() 或直方图检查数据分布是否发生偏移。
  • 重复值删除顺序: 先处理缺失值,再去重,避免因缺失值导致误判重复。
  • 标准化与归一化混用: 同一模型应统一使用一种方法,避免特征尺度不一致。
  • 时间序列数据特殊处理: 缺失值优先使用前向填充或插值,避免使用全局均值。
重要提醒: 在异常检测任务中,标准化应在训练集上拟合参数,再应用到测试集,避免数据泄露。