数据预处理实战:缺失值处理、异常值处理、数据标准化与归一化、特征工程入门
各位同学,欢迎来到第四讲。说实话,数据预处理这活儿,看着不起眼,但在我做过的储能项目里,80% 的坑都埋在这一步。你想想看,原始数据就像刚从工地拉回来的砖,有缺角的、有裂痕的、尺寸还不一样。直接拿它盖楼?那楼肯定歪。
今天我们就来聊聊,怎么把这些「脏砖」变成能用的「标准砖」。我会结合我在储能BMS(电池管理系统)项目中的实际经验,带大家走一遍完整的预处理流程。
4.1 缺失值处理:别让「空」坑了你
储能系统的数据采集,经常出幺蛾子。传感器掉线、通信中断、存储故障……结果就是数据表里出现一堆 NaN 或 null。我见过最夸张的一次,某风电场的数据缺失率高达 40%,那真是「千疮百孔」。
处理缺失值,常用的就三招:
- 直接删除:简单粗暴,但只适合缺失比例极低(<5%)的情况。删多了,样本量不够,模型就「营养不良」。
- 填充法:用均值、中位数、众数去填。我个人习惯,对正态分布的数据用均值,对有异常值的数据用中位数。比如电池电压数据,偶尔有跳变,用中位数更稳。
- 插值法:线性插值、多项式插值,适合时间序列数据。比如温度曲线,前后两个点之间缺了一个,用线性插值基本能还原。
举个例子,假设我们有一组电池SOC(荷电状态)数据,中间缺了几个点:
import pandas as pd
import numpy as np
# 模拟数据
data = {'time': ['00:00', '00:05', '00:10', '00:15', '00:20'],
'soc': [80, np.nan, np.nan, 75, 73]}
df = pd.DataFrame(data)
# 线性插值
df['soc_interp'] = df['soc'].interpolate(method='linear')
print(df)
4.2 异常值处理:揪出「捣乱分子」
异常值,说白了就是数据里的「刺头」。传感器故障、通信误码、人为操作失误,都会产生异常值。比如电池温度突然飙到 200°C,这明显是传感器坏了,不是真的热失控。
怎么揪出它们?我常用两种方法:
- 3σ 原则:数据服从正态分布时,超过均值±3倍标准差的值,视为异常。简单,但要求数据分布比较「规矩」。
- 箱线图法(IQR):不依赖分布假设。用四分位距(Q3 - Q1)来界定。低于 Q1 - 1.5×IQR 或高于 Q3 + 1.5×IQR 的,就是异常。我在项目中用得最多,因为储能数据往往不是正态分布。
处理方式也分几种:
- 直接删除:如果异常值明显是错误数据,删了最干净。
- 修正:用前后时刻的均值或中位数替换。比如某个时间点的电流值突然跳变,用前后5个点的均值去修正。
- 标记:保留异常值,但加一个「异常标记」列。有时候异常值本身也包含信息,比如它可能预示着设备故障的前兆。
4.3 数据标准化与归一化:把「尺子」统一
储能数据里,电压是伏特(V),电流是安培(A),温度是摄氏度(°C),SOC 是百分比(%)。这些特征的量纲完全不同。如果你直接拿原始数据去跑模型,量级大的特征会「吃掉」量级小的特征,模型就学偏了。
举个例子,电压 400V 和 SOC 80%,数值上差了好几个数量级。如果不做处理,模型会认为电压更重要,但实际上 SOC 对电池状态的影响同样关键。
常用的两种方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Min-Max 归一化 | X' = (X - X_min) / (X_max - X_min) | 数据有明确边界,如 SOC(0~100%) |
| Z-score 标准化 | X' = (X - μ) / σ | 数据分布未知或有异常值,如电压、电流 |
我个人习惯:对聚类算法(如 K-Means),优先用 Z-score 标准化。因为 Min-Max 对异常值太敏感,一个离群点就能把整个缩放范围拉偏。而 Z-score 用均值和标准差,相对稳健。
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
# 模拟数据:电压、电流、温度
data = np.array([[400, 50, 25],
[410, 55, 26],
[390, 45, 24],
[405, 60, 27]])
# Z-score 标准化
scaler = StandardScaler()
data_std = scaler.fit_transform(data)
print("标准化后:\n", data_std)
# Min-Max 归一化
scaler_mm = MinMaxScaler()
data_mm = scaler_mm.fit_transform(data)
print("归一化后:\n", data_mm)
4.4 特征工程入门:从「数据」到「信息」
特征工程,说白了就是「造新特征」。原始数据里的电压、电流、温度,是「原材料」。但真正对模型有用的,往往是这些原始特征的组合或变换。
比如,在储能系统中,我们关心电池的「内阻」。但内阻不能直接测量,只能通过电压和电流算出来:R = ΔV / ΔI。这就是一个典型的特征工程操作。
常用的特征工程方法:
- 统计特征:均值、方差、最大值、最小值、峰度、偏度。比如过去1小时内的电压波动幅度,能反映电池的稳定性。
- 时序特征:差分(当前值 - 上一时刻值)、滑动窗口均值、指数加权移动平均。比如电流的差分,能捕捉到充放电切换的瞬间。
- 频域特征:通过 FFT(快速傅里叶变换)提取频谱特征。我在做储能系统谐波分析时用过,能发现一些时域里看不到的规律。
- 组合特征:两个特征的比值、乘积、差值。比如「电压 × 电流 = 功率」,这个新特征对电池状态评估非常关键。
举个例子,从原始数据中提取「电压波动特征」:
import pandas as pd
import numpy as np
# 模拟电压时间序列
np.random.seed(42)
voltage = 400 + np.random.normal(0, 2, 100) # 均值400,标准差2
df = pd.DataFrame({'voltage': voltage})
# 提取特征
df['voltage_diff'] = df['voltage'].diff() # 一阶差分
df['voltage_rolling_mean'] = df['voltage'].rolling(window=10).mean() # 10点滑动均值
df['voltage_rolling_std'] = df['voltage'].rolling(window=10).std() # 10点滑动标准差
print(df.head(15))
4.5 本章知识体系总览
下面这张图,是我自己梳理的「数据预处理四步走」流程。你可以把它当作一个检查清单,每次做预处理时对照着来:
嗯,到这里,数据预处理的核心内容就讲完了。你可能会觉得,这些操作看起来都不难啊?确实,每个步骤单独看都不复杂。但难的是把它们组合成一个完整的流水线,并且每一步都做对、做扎实。
我在实际项目中,经常要花 60%~70% 的时间在数据预处理上。剩下的 30% 才是建模和调参。很多初学者急着跑模型,结果数据没处理好,模型效果一塌糊涂,然后反过来怀疑算法不行。其实,很多时候不是算法的问题,是数据的问题。
记住一句话:垃圾进,垃圾出(Garbage In, Garbage Out)。把数据预处理做好了,你的模型就已经成功了一半。