powerCurveAnalyst.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import os
  2. from datetime import datetime
  3. import pandas as pd
  4. import numpy as np
  5. import pandas as pd
  6. import matplotlib.pyplot as plt
  7. import matplotlib.cm as cm
  8. from matplotlib.ticker import MultipleLocator
  9. from matplotlib.colors import Normalize
  10. import seaborn as sns
  11. import plotly.graph_objects as go
  12. from plotly.subplots import make_subplots
  13. from geopy.distance import geodesic
  14. from behavior.analyst import Analyst
  15. from utils.directoryUtil import DirectoryUtil as dir
  16. from algorithmContract.confBusiness import *
  17. class PowerCurveAnalyst(Analyst):
  18. """
  19. 风电机组功率曲线散点分析。
  20. 秒级scada数据运算太慢,建议使用分钟级scada数据
  21. """
  22. def typeAnalyst(self):
  23. return "power_curve"
  24. def turbinesAnalysis(self, dataFrameMerge, outputAnalysisDir, confData: ConfBusiness):
  25. if len(dataFrameMerge)<=0:
  26. print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
  27. return
  28. self.drawOfPowerCurve(dataFrameMerge, outputAnalysisDir, confData,self.dataFrameContractOfTurbine)
  29. # self.drawOfPowerCurveScatter(dataFrameMerge,outputAnalysisDir,confData,dataFrameGuaranteePowerCurve)
  30. def drawOfPowerCurveScatter(self, dataFrame: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness,dataFrameGuaranteePowerCurve:pd.DataFrame):
  31. """
  32. 绘制风速-功率分布图并保存为文件。
  33. 参数:
  34. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  35. csvPowerCurveFilePath (str): 功率曲线文件路径。
  36. outputAnalysisDir (str): 分析输出目录。
  37. confData (ConfBusiness): 配置
  38. """
  39. x_name = 'wind_speed'
  40. y_name = 'power'
  41. # 按设备名分组数据
  42. grouped = dataFrame.groupby(Field_NameOfTurbine)
  43. # 遍历每个设备的数据
  44. for name, group in grouped:
  45. # 创建图形和坐标轴
  46. fig, ax = plt.subplots(figsize=(12, 8), dpi=96)
  47. cmap = cm.get_cmap('rainbow')
  48. # 绘制散点图
  49. scatter = ax.scatter(x=group[confData.field_wind_speed],
  50. y=group[confData.field_power], c=group['monthIntTime'], cmap=cmap, s=5)
  51. # 绘制合同功率曲线
  52. ax.plot(dataFrameGuaranteePowerCurve['风速'], dataFrameGuaranteePowerCurve['有功功率'], marker='o',
  53. c='gray', label='Contract Guarantee Power Curve')
  54. # 设置图形标题和坐标轴标签
  55. ax.set_title(f'turbine_name={name}')
  56. # 设置坐标轴的主刻度定位器
  57. ax.xaxis.set_major_locator(MultipleLocator(1))
  58. ax.set_xlim(0, 26)
  59. # 创建每100个单位一个刻度的定位器
  60. yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
  61. confData.graphSets["activePower"]) and not self.common.isNone(
  62. confData.graphSets["activePower"]["step"]) else 250)
  63. ax.yaxis.set_major_locator(yloc) # 将定位器应用到y轴上
  64. ax.set_ylim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
  65. confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
  66. ax.set_xlabel(x_name)
  67. ax.set_ylabel(y_name)
  68. # 显示图例,并调整位置
  69. ax.legend(loc='lower right')
  70. # 设置颜色条
  71. unique_months = len(group['年月'].unique())
  72. ticks = np.linspace(group['monthIntTime'].min(), group['monthIntTime'].max(), min(unique_months, 6)) # 减少刻度数量
  73. ticklabels = [datetime.fromtimestamp(tick).strftime('%Y-%m') for tick in ticks]
  74. norm = Normalize(group['monthIntTime'].min(), group['monthIntTime'].max())
  75. sm = cm.ScalarMappable(norm=norm, cmap=cmap)
  76. # 添加颜色条
  77. cbar = fig.colorbar(sm, ax=ax)
  78. cbar.set_ticks(ticks)
  79. cbar.set_ticklabels(ticklabels)
  80. # 旋转x轴刻度标签
  81. plt.xticks(rotation=45)
  82. # 保存图形为文件
  83. output_file = os.path.join(outputAnalysisDir, f"{name}-scatter.png")
  84. plt.savefig(output_file, bbox_inches='tight')
  85. # 关闭图形
  86. plt.close()
  87. def drawOfPowerCurve(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness,dataFrameGuaranteePowerCurve:pd.DataFrame):
  88. """
  89. 生成功率曲线并保存为文件。
  90. 参数:
  91. frames (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  92. outputAnalysisDir (str): 分析输出目录。
  93. confData (ConfBusiness): 配置
  94. """
  95. # 定义风速区间
  96. bins = np.arange(0, 26, 0.5)
  97. # 初始化结果DataFrame
  98. all_res = pd.DataFrame()
  99. # 按设备名分组数据
  100. grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
  101. # 计算每个设备的功率曲线
  102. for name, group in grouped:
  103. act_line = self.power_curve_helper(
  104. group, confData.field_wind_speed, confData.field_power, bins)
  105. act_line[Field_NameOfTurbine] = name
  106. all_res = pd.concat([all_res, act_line], axis=0, sort=False)
  107. # 绘制全场功率曲线图
  108. ress = all_res.reset_index(drop=True)
  109. self.plot_power_curve(ress, outputAnalysisDir, dataFrameGuaranteePowerCurve,Field_NameOfTurbine,
  110. '全场-{}功率曲线.png'.format(confData.farm_name),confData)
  111. # 绘制每个设备的功率曲线图
  112. grouped=ress.groupby(Field_NameOfTurbine)
  113. for name, group in grouped:
  114. self.plot_single_power_curve(ress, group,dataFrameGuaranteePowerCurve, name, outputAnalysisDir,confData)
  115. def power_curve_helper(self, group, wind_speed_col, power_col, bins):
  116. """
  117. 计算设备的功率曲线。
  118. """
  119. powerCut = group.groupby(pd.cut(group[wind_speed_col], bins, labels=np.arange(0, 25.5, 0.5))).agg({
  120. power_col: 'mean',
  121. wind_speed_col: ['mean', 'count']
  122. })
  123. wind_count = powerCut[wind_speed_col]['count'].tolist()
  124. line = powerCut[power_col]['mean'].round(decimals=2).tolist()
  125. act_line = pd.DataFrame([powerCut.index, wind_count, line]).T
  126. act_line.columns = ['风速区间', '有效数量', '实际功率曲线']
  127. return act_line
  128. def plot_power_curve(self, ress, output_path,dataFrameGuaranteePowerCurve:pd.DataFrame, Field_NameOfTurbine, filename,confData:ConfBusiness):
  129. """
  130. 绘制全场功率曲线图。
  131. """
  132. sns.set_palette('deep')
  133. fig, ax = plt.subplots(figsize=(16, 8))
  134. ax = sns.lineplot(x='风速区间', y='实际功率曲线', data=ress, hue=Field_NameOfTurbine)
  135. # # 绘制合同功率曲线
  136. # ax.plot(dataFrameGuaranteePowerCurve['风速'], dataFrameGuaranteePowerCurve['有功功率'], marker='o',
  137. # c='red', label='Contract Guarantee Power Curve')
  138. ax.set_xlabel('wind speed')
  139. ax.set_ylabel('power')
  140. ax.set_title('power curve')
  141. # 设置坐标轴的主刻度定位器
  142. ax.xaxis.set_major_locator(MultipleLocator(1))
  143. ax.set_xlim(0, 26)
  144. # 创建每100个单位一个刻度的定位器
  145. yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
  146. confData.graphSets["activePower"]) and not self.common.isNone(
  147. confData.graphSets["activePower"]["step"]) else 250)
  148. ax.yaxis.set_major_locator(yloc) # 将定位器应用到y轴上
  149. ax.set_ylim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
  150. confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
  151. plt.legend(title='turbine',bbox_to_anchor=(1.02, 0.5),ncol=2, loc='center left', borderaxespad=0.)
  152. plt.xticks(rotation=45) # 旋转45度
  153. plt.savefig(os.path.join(output_path, filename),
  154. bbox_inches='tight', dpi=120)
  155. plt.close()
  156. def plot_single_power_curve(self, ress, group,dataFrameGuaranteePowerCurve:pd.DataFrame , turbineName, outputAnalysisDir,confData:ConfBusiness):
  157. color = ["lightgrey"]*len(ress[Field_NameOfTurbine].unique())
  158. fig, ax = plt.subplots(figsize=(8, 8))
  159. ax = sns.lineplot(x='风速区间', y='实际功率曲线', data=ress, hue=Field_NameOfTurbine,
  160. palette=sns.set_palette(color), legend=False)
  161. ax = sns.lineplot(x='风速区间', y='实际功率曲线', data=group,
  162. color='darkblue', legend=False)
  163. # 绘制合同功率曲线
  164. ax.plot(dataFrameGuaranteePowerCurve['风速'], dataFrameGuaranteePowerCurve['有功功率'], marker='o',
  165. c='red', label='Contract Guarantee Power Curve')
  166. ax.set_xlabel('wind speed')
  167. ax.set_ylabel('power')
  168. ax.set_title('turbine_name={}'.format(turbineName))
  169. # 设置坐标轴的主刻度定位器
  170. ax.xaxis.set_major_locator(MultipleLocator(1))
  171. ax.set_xlim(0, 26)
  172. # 创建每100个单位一个刻度的定位器
  173. yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
  174. confData.graphSets["activePower"]) and not self.common.isNone(
  175. confData.graphSets["activePower"]["step"]) else 250)
  176. ax.yaxis.set_major_locator(yloc) # 将定位器应用到y轴上
  177. ax.set_ylim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
  178. confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
  179. # 显示图例,并调整位置
  180. ax.legend(loc='lower right')
  181. plt.xticks(rotation=45) # 旋转45度
  182. plt.savefig(outputAnalysisDir + r"/{}-curve.png".format(turbineName),
  183. bbox_inches='tight', dpi=120)
  184. plt.close()