第1章:数据预处理实战——打好基础,少走弯路

大家好,我是老张。在故障预测这个行当摸爬滚打了十几年,我最大的体会就是:数据预处理做得好,模型成功一半。这话一点都不夸张。你想想看,原始数据就像刚从矿里挖出来的矿石,杂质多、形态乱,直接拿去训练模型,结果可想而知。

今天这一章,咱们就聊聊数据预处理的四个核心环节:缺失值处理、异常值检测、数据标准化与归一化、时间序列重采样。这些都是我每天都会用到的基本功,也是很多新手容易踩坑的地方。

数据预处理核心流程 原始数据 (脏数据) 缺失值处理 删除 / 填充 / 插值 异常值检测 3σ / IQR / 箱线图 标准化 & 归一化 Z-score / Min-Max 时间序列重采样 降采样 / 升采样 干净、规整的数据

1.1 缺失值处理——别让"空"坑了你

我在项目中遇到过最头疼的事,就是传感器数据突然断档。设备还在转,数据却没了。这种缺失值如果不处理,模型会学出"幻觉"来。

缺失值处理,说白了就三种思路:删、填、猜

1.1.1 直接删除

如果缺失比例很小(比如不到5%),而且缺失是随机的,直接删掉最省事。但要注意——千万别删成片的数据。我曾经有个项目,连续删了30%的行,结果模型训练出来完全不准。后来才发现,那些缺失值其实集中在某个故障模式里,删掉等于把关键信息丢了。

⚠️ 避坑指南: 我曾经在振动数据里直接删除了所有含NaN的行,结果模型在测试集上表现极差。后来复盘发现,那些NaN恰好出现在设备即将故障的时刻——传感器在高温下短暂失效了。所以,删除前一定要先看看缺失值有没有规律。

1.1.2 填充法

填充是最常用的方法。我个人习惯按这个顺序来:

  • 均值/中位数填充:数值型数据,分布对称用均值,有偏态用中位数
  • 前向/后向填充:时间序列数据,用前一时刻的值填充,这叫"ffill"
  • 插值法:线性插值、样条插值,适合变化平滑的数据
import pandas as pd
import numpy as np

# 生成示例数据
df = pd.DataFrame({
    '温度': [25, np.nan, 27, 28, np.nan, 30],
    '振动': [0.5, 0.6, np.nan, 0.8, 0.9, np.nan]
})

# 均值填充
df['温度'].fillna(df['温度'].mean(), inplace=True)

# 前向填充(时间序列常用)
df['振动'].fillna(method='ffill', inplace=True)

print(df)

1.2 异常值检测——揪出"捣乱分子"

异常值是什么?就是那些明显偏离正常范围的数据点。在故障预测里,异常值可能是传感器误报,也可能是故障的前兆。怎么区分?这就是检测方法要解决的问题。

1.2.1 3σ 法则

这个方法基于正态分布。数据在均值±3个标准差范围内的概率是99.7%,之外的就算异常。简单粗暴,但好用。

💡 我的经验: 3σ法则对正态分布的数据效果很好。但如果数据本身有偏态,比如设备磨损数据往往右偏,这时候用3σ会漏掉很多真正的异常。我一般先用直方图看一眼分布形态,再决定用不用。
def detect_outliers_3sigma(data):
    mean = np.mean(data)
    std = np.std(data)
    lower = mean - 3 * std
    upper = mean + 3 * std
    outliers = (data < lower) | (data > upper)
    return outliers

# 示例
data = np.random.normal(50, 10, 1000)
data[0] = 120  # 人为加入异常
outliers = detect_outliers_3sigma(data)
print(f"检测到 {sum(outliers)} 个异常值")

1.2.2 IQR 方法

IQR(四分位距)法不依赖正态分布假设,更稳健。它把数据分成四份,取Q1(25%分位)和Q3(75%分位),IQR = Q3 - Q1。异常值定义为小于Q1-1.5×IQR或大于Q3+1.5×IQR的点。

方法 适用场景 优点 缺点
正态分布数据 计算简单,理论成熟 对偏态数据不敏感
IQR 任意分布数据 稳健,不受极端值影响 对样本量有要求
🔧 实用技巧: 我通常两种方法都用,取交集作为"高度可疑"的异常值,取并集作为"需要关注"的异常值。这样既不会漏掉,也不会过度删除。

1.3 数据标准化与归一化——让不同量纲的数据"对话"

你想想看,温度是几十度,振动是零点几毫米/秒,压力是几兆帕。这些数据直接放一起,模型会天然偏向数值大的特征。所以我们需要标准化或归一化。

1.3.1 Z-score 标准化

公式很简单:z = (x - μ) / σ。处理后数据均值为0,标准差为1。适合数据近似正态分布的情况。

1.3.2 Min-Max 归一化

公式:x' = (x - min) / (max - min)。把数据压缩到[0,1]区间。适合数据有明确边界的情况。

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# 标准化
scaler_std = StandardScaler()
data_std = scaler_std.fit_transform(df[['温度', '振动']])

# 归一化
scaler_mm = MinMaxScaler()
data_mm = scaler_mm.fit_transform(df[['温度', '振动']])
⚠️ 注意: 标准化和归一化一定要在划分训练集和测试集之后做。先用训练集的数据计算均值和标准差,再用同样的参数去转换测试集。我见过有人把整个数据集一起标准化,结果模型在线上表现一塌糊涂——因为测试集的信息"泄露"到训练过程里了。

1.4 时间序列重采样——对齐时间轴

故障预测的数据往往来自不同传感器,采样频率不一样。有的每秒采一次,有的每分钟采一次。不重采样,根本没法分析。

1.4.1 降采样

从高频到低频。比如把1秒的数据聚合成1分钟的数据。常用聚合函数:均值、中位数、最大值、最小值。

1.4.2 升采样

从低频到高频。比如把1分钟的数据插值到1秒。这需要小心——插值出来的数据是"假的",不能太当真。

# 生成时间序列数据
dates = pd.date_range('2024-01-01', periods=100, freq='5min')
df_ts = pd.DataFrame({'value': np.random.randn(100)}, index=dates)

# 降采样到1小时
df_hourly = df_ts.resample('1H').mean()

# 升采样到1分钟(线性插值)
df_minutely = df_ts.resample('1min').interpolate(method='linear')

print("原始数据量:", len(df_ts))
print("降采样后:", len(df_hourly))
print("升采样后:", len(df_minutely))
💡 我的习惯: 重采样时,我一般会保留原始数据的统计特征。比如降采样时,除了均值,我还会保留最大值和最小值。这样能捕捉到短时间内的波动信息,对故障预测很有帮助。

好了,这一章的内容就到这里。数据预处理看起来琐碎,但每一步都关系到最终模型的成败。我个人觉得,花在预处理上的时间,永远值得。


公众号:蓝海数据掘金营,微信deep3321