4、异常识别算法(一):基于统计阈值的异常检测、3-Sigma原则与箱线图、移动窗口法
各位好,今天我们来聊聊功率曲线异常识别的第一类方法——统计阈值法。
说实话,我刚入行那会儿,面对海量的SCADA数据,第一反应就是「这数据真漂亮,直接建模吧」。结果呢?模型跑出来一塌糊涂。后来才明白,数据里的异常点就像汤里的沙子,不先捞出来,整锅汤都没法喝。
所以,异常识别是数据清洗的第一步。今天讲的方法,说白了就是「用数学规则给数据画个圈,圈外的就是异常」。
4.1 3-Sigma原则:最朴素的异常检测
3-Sigma原则,也叫拉依达准则。它的逻辑很简单:
- 如果数据服从正态分布,那么99.7%的数据会落在均值±3倍标准差范围内
- 超出这个范围的,大概率是异常
公式长这样:
下限 = μ - 3σ
上限 = μ + 3σ
异常点:x < 下限 或 x > 上限
嗯,这里要注意——3-Sigma对正态分布敏感。风功率数据其实不太符合正态分布,尤其是低风速段,数据会严重偏态。我曾在某风场试过直接用3-Sigma筛数据,结果把大量正常低风速点误判为异常,气得运维小哥直拍桌子。
我曾经犯过一个错:直接用原始功率数据的3-Sigma做检测。结果发现,高风速段的标准差极大,导致阈值过宽,异常点全漏了。后来我改成「按风速分箱后,每个箱内单独算3-Sigma」,效果才正常。
4.2 箱线图:不怕偏态的鲁棒方法
箱线图(Boxplot)就不一样了。它基于四分位数,不依赖正态分布假设。
核心参数:
- Q1(下四分位数):25%分位点
- Q3(上四分位数):75%分位点
- IQR = Q3 - Q1(四分位距)
- 异常阈值:Q1 - 1.5×IQR 和 Q3 + 1.5×IQR
你想想看,1.5倍IQR这个系数是怎么来的?其实是个经验值。统计学上认为,超出这个范围的数据,属于「温和异常」。如果系数改成3倍IQR,那就是「极端异常」了。
我个人习惯在风功率分析中优先用箱线图。为什么?因为风功率数据经常有长尾分布,3-Sigma会把尾巴砍得太狠,而箱线图对尾巴更宽容。
我在做某山地风场的数据清洗时,发现箱线图的1.5倍IQR阈值仍然偏紧。后来我调成了2倍IQR,效果反而更好。所以,别死守教科书参数,要根据数据分布灵活调整。
4.3 移动窗口法:让阈值跟着风速跑
前面两种方法都是「全局阈值」——整个数据集用一个阈值。但风功率曲线有个特点:不同风速段,数据的离散程度完全不同。
低风速段(比如3-5m/s),功率波动很小,数据密集。高风速段(比如10-15m/s),功率波动大,数据分散。用一个全局阈值,要么低风速段误报多,要么高风速段漏报多。
移动窗口法就是来解决这个问题的。
它的思路是:
- 把风速轴切成一个个小窗口(比如宽度0.5m/s)
- 在每个窗口内,单独计算统计阈值(均值±3σ 或 箱线图阈值)
- 窗口滑动,逐段判断
代码实现大概长这样:
def moving_window_detection(wind_speed, power, window_width=0.5, method='3sigma'):
results = []
# 按风速排序
sorted_idx = np.argsort(wind_speed)
ws_sorted = wind_speed[sorted_idx]
pw_sorted = power[sorted_idx]
# 滑动窗口
for i in range(len(ws_sorted)):
# 找到当前点所在窗口的数据
mask = (ws_sorted >= ws_sorted[i] - window_width/2) & \
(ws_sorted <= ws_sorted[i] + window_width/2)
window_data = pw_sorted[mask]
if len(window_data) < 10: # 窗口内数据太少,跳过
results.append(False)
continue
# 计算阈值
if method == '3sigma':
mean = np.mean(window_data)
std = np.std(window_data)
lower = mean - 3*std
upper = mean + 3*std
elif method == 'boxplot':
Q1 = np.percentile(window_data, 25)
Q3 = np.percentile(window_data, 75)
IQR = Q3 - Q1
lower = Q1 - 1.5*IQR
upper = Q3 + 1.5*IQR
# 判断当前点是否异常
is_anomaly = (pw_sorted[i] < lower) or (pw_sorted[i] > upper)
results.append(is_anomaly)
return results
这段代码我实际用过很多次。有个细节要注意:窗口宽度怎么选?太窄了,每个窗口数据点太少,统计不稳定;太宽了,又失去了局部检测的意义。我一般取0.5m/s,如果数据量少可以放宽到1m/s。
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 3-Sigma | 简单直观,计算快 | 依赖正态分布,对偏态数据不友好 | 数据量大、分布较对称的风场 |
| 箱线图 | 鲁棒性强,不受极端值影响 | 对数据量要求较高 | 山地、复杂地形风场 |
| 移动窗口法 | 适应风速变化,局部检测精准 | 计算量大,窗口参数需调优 | 所有风场(推荐首选) |
4.4 知识体系:一张图看懂
下面这张SVG图,把今天讲的三种方法串起来了。你可以看到,它们都基于「统计阈值」这个核心思想,只是计算方式和适用范围不同。
看到这张图,你应该能理解:这三种方法不是互斥的,而是互补的。我个人的工作流是:先用移动窗口法做粗筛,再用箱线图做细筛,最后人工复核。这样既保证了效率,又兼顾了准确率。
好了,今天的内容就到这里。记住一句话:没有最好的方法,只有最适合你数据的方法。多试试,多调参,你也能成为异常识别的高手。
公众号:蓝海资料掘金营,微信deep3321