4、基于统计学的异常检测:3σ原则、箱线图法、Z-Score方法、移动平均法
各位同事,咱们今天聊点实在的。风电场的数据,说白了就是一堆带噪声的数字。你打开SCADA系统,看到风速、功率、桨距角这些曲线,里面总有些“不听话”的点——要么突然跳上天,要么直接掉到地板上。这些点,就是我们要揪出来的异常数据。
我做了这么多年风电数据分析,最深的体会就是:别一上来就上深度学习,先拿统计学方法过一遍。简单、高效、可解释,这才是工程界的王道。今天我就把这四种最常用的统计学方法,掰开了揉碎了讲给你听。
4.1 3σ原则——最直观的“三倍标准差”
这个方法,我估计你在大学概率论课上都学过。但真正在风电场用起来,还是有些门道的。
核心思想:如果数据服从正态分布,那么99.7%的数据会落在均值±3σ的范围内。落在外面的,就是异常。
嗯,这里要注意——前提是数据得近似正态分布。风电功率数据在额定功率附近往往有个“平台”,不是完美的钟形曲线。我建议你先画个直方图看看,如果明显双峰或者偏态严重,3σ原则就不太灵了。
import numpy as np
def detect_3sigma(data):
mean = np.mean(data)
std = np.std(data)
lower = mean - 3 * std
upper = mean + 3 * std
anomalies = [(i, v) for i, v in enumerate(data) if v < lower or v > upper]
return anomalies, lower, upper
# 举个栗子:某风电场10分钟平均风速
wind_speed = [5.2, 5.8, 6.1, 12.5, 5.5, 6.0, 5.9, 6.3, 5.7, 6.2]
anomalies, low, up = detect_3sigma(wind_speed)
print(f"正常范围: [{low:.2f}, {up:.2f}]")
print(f"异常点: {anomalies}")
4.2 箱线图法——不看分布,看分位
箱线图的好处是不依赖正态分布假设。你想想看,风功率曲线那个“弯弯绕”的形状,用箱线图反而更稳。
怎么算:
- 计算下四分位数 Q1(25%分位)和上四分位数 Q3(75%分位)
- 四分位距 IQR = Q3 - Q1
- 正常范围:[Q1 - 1.5×IQR, Q3 + 1.5×IQR]
- 超出这个范围的,就是异常值
我在项目中遇到过一件事:某台机组功率曲线在低风速段总有“掉坑”的数据点。用3σ原则死活揪不出来,换成箱线图法,一下子就识别出来了。为什么?因为低风速段数据分布不对称,箱线图不受这个影响。
import numpy as np
def detect_iqr(data):
q1 = np.percentile(data, 25)
q3 = np.percentile(data, 75)
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
anomalies = [(i, v) for i, v in enumerate(data) if v < lower or v > upper]
return anomalies, lower, upper
# 功率数据示例(单位:kW)
power = [1500, 1520, 1480, 1510, 800, 1490, 1530, 1470, 1515, 1525]
anomalies, low, up = detect_iqr(power)
print(f"IQR正常范围: [{low:.0f}, {up:.0f}]")
print(f"异常点: {anomalies}")
4.3 Z-Score方法——标准化后的“距离”
Z-Score说白了就是:这个点离均值有几个标准差那么远。
公式很简单:Z = (x - μ) / σ
通常认为 |Z| > 3 就是异常。但这里有个坑——Z-Score对极端值非常敏感。为什么?因为均值和标准差本身会被异常值“带偏”。
我曾经处理过一个数据集,里面有个传感器坏了,读数飙到10000,结果均值和标准差都被拉得很大,导致其他真正的异常反而被掩盖了。这就是所谓的掩蔽效应。
import numpy as np
def zscore_detect(data, threshold=3):
mean = np.mean(data)
std = np.std(data)
z_scores = [(x - mean) / std for x in data]
anomalies = [(i, data[i]) for i, z in enumerate(z_scores) if abs(z) > threshold]
return anomalies, z_scores
# 稳健版本
def robust_zscore_detect(data, threshold=3):
median = np.median(data)
mad = np.median(np.abs(data - median))
# 如果MAD为0,特殊处理
if mad == 0:
return [], []
modified_z = 0.6745 * (data - median) / mad
anomalies = [(i, data[i]) for i, z in enumerate(modified_z) if abs(z) > threshold]
return anomalies, modified_z
4.4 移动平均法——抓住“时间上的异常”
前面三种方法都是“只看数值,不看时间”。但风电场数据是时序的——上一秒和下一秒有关系。移动平均法就是利用这个关系。
思路:
- 选一个窗口大小(比如5个点、10个点)
- 计算窗口内的平均值(或中位数)作为“期望值”
- 计算实际值与期望值的差值(残差)
- 如果残差超过某个阈值,就是异常
举个例子:风速从5m/s突然跳到15m/s,然后又跳回来。这种“尖峰”在物理上不可能(风有惯性),但传感器可能误报。移动平均法对这种孤立跳变点特别敏感。
import numpy as np
def moving_average_detect(data, window=5, threshold=2.0):
anomalies = []
for i in range(window, len(data)):
window_data = data[i-window:i]
avg = np.mean(window_data)
residual = abs(data[i] - avg)
if residual > threshold * np.std(window_data):
anomalies.append((i, data[i], residual))
return anomalies
# 模拟一个带尖峰的风速序列
wind = [5.0, 5.2, 5.1, 5.3, 5.0, 15.8, 5.1, 5.4, 5.2, 5.0]
anomalies = moving_average_detect(wind, window=4, threshold=2.0)
print(f"移动平均法检测到的异常: {anomalies}")
4.5 四种方法怎么选?一张表说清楚
| 方法 | 数据要求 | 优点 | 缺点 | 风电典型场景 |
|---|---|---|---|---|
| 3σ原则 | 近似正态分布 | 简单、计算快 | 对偏态数据失效 | 风速、温度 |
| 箱线图法 | 无分布要求 | 稳健、不受极端值影响 | 对样本量敏感 | 功率、桨距角 |
| Z-Score | 近似正态分布 | 标准化后可比 | 掩蔽效应 | 多传感器对比 |
| 移动平均法 | 时序数据 | 捕捉时间相关性 | 窗口参数难调 | 跳变、毛刺检测 |
最后说一句:别指望一种方法打天下。我通常的做法是:先用箱线图法扫一遍全局异常,再用移动平均法抓局部跳变,最后用3σ原则做个交叉验证。三种方法都标记为异常的,基本可以确定是“实锤”了。
好了,这四种方法你可以在自己的数据上跑一跑。下一节我们会聊更高级的方法——基于机器学习的异常检测,到时候你会发现,统计方法其实是ML方法的基础。先把今天这些吃透,后面才跟得上。
公众号:蓝海资料掘金营,微信deep3321