4、探索性数据分析(EDA):数据分布可视化、相关性分析、趋势与季节性分解
各位同行,咱们接着聊。上一节我们把数据清洗干净了,但光有干净数据还不够——你得知道这些数据到底长什么样,藏着什么秘密。这就是EDA要做的事。
我个人习惯,拿到清洗后的数据,第一件事不是急着建模,而是先画图。你想想看,一堆数字摆在那,你很难看出门道。但一张图,有时候一眼就能发现问题。
4.1 数据分布可视化:看看数据“长”什么样
数据分布,说白了就是看某个参数(比如风速、功率、振动值)的数值都集中在哪个范围。我见过不少风机,明明额定功率是2MW,但实际运行数据一画出来,大部分时间都在1.2MW附近晃悠。这说明什么?要么风资源不行,要么控制策略有问题。
常用的可视化手段就这几个:
- 直方图:看数据集中在哪个区间。比如风速直方图,能看出你风场的主导风速范围。
- 箱线图:看异常值。箱线图上下两个“须”之外的点,基本就是异常点了。我在项目中遇到过,齿轮箱振动值的箱线图,每年夏天都会多出几个离群点,后来发现是润滑脂高温失效导致的。
- 密度图:直方图的平滑版,适合对比不同机组的分布差异。
重点提醒:做直方图时,bin的个数别随便设。设少了看不清细节,设多了全是毛刺。我一般用“平方根法则”或“斯特吉斯公式”来算,或者直接试几个值,选看起来最顺眼的那个。
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 假设df是清洗后的数据
plt.figure(figsize=(12, 4))
# 风速分布
plt.subplot(1, 3, 1)
sns.histplot(df['wind_speed'], bins=30, kde=True)
plt.title('风速分布')
# 有功功率分布
plt.subplot(1, 3, 2)
sns.boxplot(y=df['active_power'])
plt.title('有功功率箱线图')
# 齿轮箱振动分布
plt.subplot(1, 3, 3)
sns.kdeplot(df['gearbox_vibration'], shade=True)
plt.title('齿轮箱振动密度图')
plt.tight_layout()
plt.show()
我的小技巧:看分布图时,别忘了对比设计值。比如设计风速是12m/s,但实际分布峰值在8m/s,那这个风场大概率是低风速机型选错了。
4.2 相关性分析:谁和谁“有关系”
相关性分析,就是找变量之间的“暧昧关系”。风速和功率肯定正相关,这谁都知道。但有些关系藏得很深——比如环境温度和变桨轴承温度,看起来八竿子打不着,但相关系数一算,嘿,还挺高。
为什么会这样?我后来查了现场记录,发现那台风机变桨轴承的散热风扇坏了,导致温度跟着环境温度一起波动。嗯,这就是相关性分析的价值——帮你发现那些“意料之外,情理之中”的关联。
常用的方法:
- 皮尔逊相关系数:最常用,适合线性关系。范围-1到1,绝对值越大关系越强。
- 斯皮尔曼秩相关系数:不要求线性,适合单调关系。我处理振动数据时常用这个,因为振动和磨损的关系往往不是线性的。
- 热力图:把相关系数矩阵画出来,一目了然。
# 计算相关系数矩阵
corr_matrix = df[['wind_speed', 'active_power', 'rotor_speed',
'gearbox_temp', 'ambient_temp']].corr(method='pearson')
# 画热力图
plt.figure(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm',
fmt='.2f', linewidths=0.5)
plt.title('关键参数相关性热力图')
plt.show()
我曾经踩过的坑:相关性不等于因果!有一次我发现“发电机温度”和“电网频率”相关系数高达0.8,差点以为发现了新大陆。后来一查,是因为那段时间电网改造,频率波动大,同时夏天高温导致发电机温度也高——纯粹是巧合。所以,看到高相关性,先问自己一句:“这合理吗?”
4.3 趋势与季节性分解:把时间序列“拆开”看
风机数据是典型的时间序列。时间序列里藏着三个成分:趋势、季节性、残差(随机波动)。
趋势,就是长期变化方向。比如某台风机功率逐年下降,那可能是叶片结垢或者齿轮箱效率衰退了。
季节性,就是周期性波动。风资源有季节性,这好理解。但有些季节性很隐蔽——比如冷却系统的效率,夏天低冬天高,这个周期和气温周期是同步的。
残差,就是去掉趋势和季节性之后剩下的“噪音”。如果残差突然变大,或者出现规律性波动,那就要警惕了——可能是有隐性故障在“冒头”。
我常用的分解方法是STL(Seasonal-Trend decomposition using LOESS)。它比经典加法/乘法分解更稳健,能处理非线性趋势。
from statsmodels.tsa.seasonal import STL
# 以有功功率为例,按小时重采样
power_series = df.set_index('timestamp')['active_power']
power_hourly = power_series.resample('H').mean()
# STL分解
stl = STL(power_hourly, period=24) # 24小时周期
result = stl.fit()
# 画图
fig, axes = plt.subplots(4, 1, figsize=(12, 8))
result.observed.plot(ax=axes[0], title='原始序列')
result.trend.plot(ax=axes[1], title='趋势')
result.seasonal.plot(ax=axes[2], title='季节性')
result.resid.plot(ax=axes[3], title='残差')
plt.tight_layout()
plt.show()
关键点:残差才是“宝藏”。我习惯在残差上设置阈值,一旦连续3个点超过2倍标准差,就触发预警。有一次就是这样发现了一台风机变桨系统的微小卡涩——功率曲线看起来正常,但残差里出现了规律性的尖峰。
4.4 本章知识体系
下面这张图,是我自己总结的EDA核心逻辑。你照着这个流程走,基本不会漏掉重要信息。
这张图的核心逻辑是:先看分布,了解数据的基本面貌;再看相关性,找出变量之间的关联;最后做时序分解,挖掘时间维度上的规律。这三步走完,你基本就能回答“数据里有什么异常”这个问题了。
我的经验:EDA不是一次性工作。你发现一个异常,处理完,还得回来重新做EDA,看看处理效果。我管这叫“EDA循环”——每循环一次,你对数据的理解就深一层。
好了,EDA这部分就聊到这。记住,EDA不是走过场,它是你和数据之间的“对话”。你问得越细,数据告诉你的就越多。