第2章 数据准备与探索

数据是异常检测模型的粮食。你想想看,模型再厉害,喂进去一堆脏数据,出来的结果能靠谱吗?我见过太多项目,前期数据准备糊弄过去,后面调参调到头秃才发现是数据本身有问题。这一章我们就来把数据这关扎扎实实过一遍。

2.1 数据收集——第一手活儿

数据收集听起来简单,但坑不少。我个人的习惯是,拿到数据先问三个问题:

  • 数据来源可靠吗? 是生产环境日志、传感器读数,还是第三方API?
  • 时间跨度够吗? 异常检测通常需要覆盖正常模式和异常模式,数据至少要涵盖一个完整业务周期。
  • 采样频率合理吗? 每秒记录一次还是每分钟一次?太密浪费存储,太疏可能漏掉异常瞬间。

我曾经接过一个工业设备项目,客户给的数据是每小时平均值。我说这不行,异常往往发生在秒级。后来重新采集了原始波形,模型准确率从62%直接跳到91%。采样频率这个事,千万别将就。

数据格式也值得注意。我比较推荐统一转为CSV或Parquet,列名用英文下划线命名,不要带空格和特殊字符。如果你用的是日志文件,记得先处理多行记录和时间戳对齐。

2.2 数据清洗——去掉那些脏东西

数据清洗说白了就是扔垃圾。但不光是扔,还得知道哪些该扔、哪些该修。我列出几个最常见的场景:

  1. 重复记录:同一时间戳出现多条相同数据,直接去重。
  2. 无关列:比如用户ID、订单号这种跟异常检测无关的字段,果断删除。
  3. 异常编码:有的传感器用 -999 代表缺失,这种得转成NaN。
  4. 格式不一致:时间格式有的是"2025-01-01 08:00:00",有的是"01/01/2025 8:00 AM",统一用datetime标准格式。
清洗类型常用操作我踩过的坑
重复值df.drop_duplicates()注意判断依据:是整行重复还是某几列重复?
异常编码replace(-999, np.nan)一定要先确认-999的含义,别把正常值误判了
格式统一pd.to_datetime()时区不统一会出大问题,我就在跨时区数据上栽过

2.3 缺失值处理——别让模型饿肚子

缺失值是异常检测里的老大难。为什么这么说?因为异常样本本来就少,再一漏数据,样本量雪上加霜。我自己做项目时,一般按以下优先级处理:

  • 删除:缺失比例 < 5%,且数据量够大,直接删除该行。
  • 填充:缺失比例 5%–30%,考虑向前填充、中位数填充、或者用时间序列插值。
  • 标记:缺失比例 > 30%,但特征很重要,我会新增一个"是否缺失"的二元特征,让模型自己学。

千万别用均值填充处理异常检测数据!我曾经试过,结果把异常窗口内的缺失值全填成了正常均值,模型的异常召回率直接腰斩。异常检测对分布变化极其敏感,填充方法一定要谨慎。

实操建议:先用 df.isnull().sum() / len(df) 看一眼每列的缺失比例,再决定策略。对于时间序列,推荐使用 pandas.DataFrame.interpolate(method='time') 做时间感知插值。

# 缺失值处理示例代码
import pandas as pd
import numpy as np

df = pd.read_csv('sensor_data.csv')

# 查看缺失率
missing_rate = df.isnull().sum() / len(df)
print('缺失率:\n', missing_rate)

# 时间序列插值
df['temperature'] = df['temperature'].interpolate(method='time')

# 标记高缺失率特征
df['pressure_missing'] = df['pressure'].isnull().astype(int)
df['pressure'].fillna(method='ffill', inplace=True)

2.4 异常分布可视化——用眼睛找问题

数据洗完了,别急着建模。先画图看看长什么样。我经常说的:可视化是最好的探索性分析工具。对于异常检测,重点关注三个维度:

  • 整体分布:直方图、箱线图看看数据有没有明显偏态或离群点。
  • 时间趋势:折线图看是否有周期性、突刺或平台期。
  • 特征关系:散点图矩阵或相关性热图,找找异常点是否出现在特征组合的稀疏区。

我的个人经验是:先画箱线图,一眼就能看到那些远离上下须的点,这些大概率就是异常候选。再画个时间序列折线图,把离群点用不同颜色标出来,你会看到很多有意思的规律——比如设备故障前半小时,某些特征会先出现抖动。

# 异常分布可视化快速代码
import matplotlib.pyplot as plt
import seaborn as sns

# 箱线图
plt.figure(figsize=(8,4))
sns.boxplot(data=df[['temperature', 'pressure', 'vibration']])
plt.title('各特征箱线图——离群点一目了然')
plt.show()

# 时间序列标记异常
plt.figure(figsize=(12,4))
plt.plot(df['timestamp'], df['temperature'], label='温度')
# 假设我们已经用简单3σ方法标记了异常点
anomalies = df[df['temperature'] > df['temperature'].mean() + 3*df['temperature'].std()]
plt.scatter(anomalies['timestamp'], anomalies['temperature'], color='red', label='异常点')
plt.legend()
plt.title('温度序列中的异常点标记')
plt.show()

我习惯把可视化结果截图放到项目文档里,这样后续调整阈值时,方便对比不同版本的分布变化。尤其是那些边界值附近的点,看数值不如看图形直观。

数据准备与探索流程 原始数据 数据清洗 缺失值处理 可视化探索 异常分布标记 清洗后数据 💡 流程说明:原始数据依次经过清洗、缺失值处理后,一方面生成清洗后数据,另一方面进入可视化探索,标记出异常分布。虚线表示清洗后数据可以直接用于异常标记。

上面这张图就是我脑子里数据准备阶段的完整流程。你可能会问:为什么要把可视化探索放在缺失值处理之后?因为缺失值处理会改变数据分布,先洗好数据再画图,看到的才是真实面貌。我刚开始做的时候反着来,画完图发现一堆假异常,浪费时间。

好了,数据这关我们就算走了一遍。记住,后面所有的模型调优都建立在数据质量之上。这块花的时间,后面一定会加倍还给你。