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