ratedPowerWindSpeedAnalyst.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import os
  2. import pandas as pd
  3. import plotly.graph_objects as go
  4. from algorithmContract.confBusiness import *
  5. from algorithmContract.contract import Contract
  6. from behavior.analystWithGoodBadPoint import AnalystWithGoodBadPoint
  7. from plotly.subplots import make_subplots
  8. class RatedPowerWindSpeedAnalyst(AnalystWithGoodBadPoint):
  9. """
  10. 风电机组额定功率风速分析。
  11. 秒级scada数据运算太慢,建议使用分钟级scada数据
  12. """
  13. def typeAnalyst(self):
  14. return "rated_power_windspeed"
  15. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  16. dictionary=self.processTurbineData(turbineCodes,conf,[Field_DeviceCode,Field_Time,Field_EnvTemp,Field_WindSpeed,Field_ActiverPower])
  17. # dataFrameMerge=self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self)
  18. dataFrameOfTurbines = self.userDataFrame(
  19. dictionary, conf.dataContract.configAnalysis, self)
  20. turbrineInfos = self.common.getTurbineInfos(
  21. conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  22. groupedOfTurbineModel = turbrineInfos.groupby(Field_MillTypeCode)
  23. returnDatas = []
  24. for turbineModelCode, group in groupedOfTurbineModel:
  25. currTurbineCodes = group[Field_CodeOfTurbine].unique().tolist()
  26. currTurbineModeInfo = self.common.getTurbineModelByCode(
  27. turbineModelCode, self.turbineModelInfo)
  28. currDataFrameOfTurbines = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine].isin(
  29. currTurbineCodes)]
  30. returnData= self.draw(currDataFrameOfTurbines, outputAnalysisDir, conf,currTurbineModeInfo)
  31. returnDatas.append(returnData)
  32. returnResult = pd.concat(returnDatas, ignore_index=True)
  33. return returnResult
  34. def draw(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract,turbineModelInfo: pd.Series):
  35. """
  36. 绘制并保存额定满发风速功率分布图,根据环境温度是否大于等于25℃。
  37. 参数:
  38. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  39. outputAnalysisDir (str): 分析输出目录。
  40. confData (ConfBusiness): 配置
  41. """
  42. # 检查所需列是否存在
  43. required_columns = {Field_EnvTemp,
  44. Field_WindSpeed, Field_ActiverPower}
  45. if not required_columns.issubset(dataFrameMerge.columns):
  46. raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
  47. y_name = '功率'
  48. upLimitOfPower = self.turbineInfo[Field_RatedPower].max() * 1.2
  49. lowLimitOfPower = self.turbineInfo[Field_RatedPower].max()*0.9
  50. field_RatedWindSpeed = self.turbineModelInfo[Field_RatedWindSpeed].max()
  51. # 根据环境温度筛选数据
  52. over_temp = dataFrameMerge[(dataFrameMerge[Field_EnvTemp] >= 25) & (
  53. dataFrameMerge[Field_WindSpeed] >= field_RatedWindSpeed) & (dataFrameMerge[Field_ActiverPower] >= lowLimitOfPower)].sort_values(by=Field_NameOfTurbine)
  54. below_temp = dataFrameMerge[(dataFrameMerge[Field_EnvTemp] < 25) & (
  55. dataFrameMerge[Field_WindSpeed] >= field_RatedWindSpeed) & (dataFrameMerge[Field_ActiverPower] >= lowLimitOfPower)].sort_values(by=Field_NameOfTurbine)
  56. # 绘制环境温度大于等于25℃的功率分布图
  57. fig = make_subplots(rows=1, cols=1)
  58. fig.add_trace(go.Box(y=over_temp[Field_ActiverPower], x=over_temp[Field_NameOfTurbine],
  59. # name='Ambient Temp >= 25°C',
  60. boxpoints='outliers',
  61. # box line color
  62. line=dict(color='black', width=1),
  63. # quartilemethod='exclusive',
  64. fillcolor='dodgerblue',
  65. showlegend=False,
  66. marker=dict(color='rgba(0, 0, 0, 0)', size=0.1)),
  67. row=1, col=1)
  68. # Calculate medians and plot them as a line for visibility
  69. medians = over_temp.groupby(Field_NameOfTurbine)[
  70. Field_ActiverPower].median()
  71. median_line = go.Scatter(
  72. x=medians.index,
  73. y=medians.values,
  74. mode='markers',
  75. marker=dict(symbol='line-ew-open', color='red', size=12),
  76. showlegend=False
  77. )
  78. fig.add_trace(median_line)
  79. # 更新布局
  80. fig.update_yaxes(title_text=y_name, row=1, col=1, range=[
  81. lowLimitOfPower, upLimitOfPower], tickfont=dict(size=10))
  82. fig.update_xaxes(title_text='机组', type='category',
  83. tickangle=-45, tickfont=dict(size=10))
  84. fig.update_layout(title={
  85. 'text': f'额定功率分布(环境温度>=25摄氏度)-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5}, boxmode='group')
  86. result_rows = []
  87. # 保存图像
  88. pngFileName = "额定满发风速功率分布(10min)(环境温度大于25度).png"
  89. pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  90. fig.write_image(pngFilePath, scale=3)
  91. # 保存HTML
  92. htmlFileName = "额定满发风速功率分布(10min)(环境温度大于25度).html"
  93. htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  94. fig.write_html(htmlFilePath)
  95. result_rows.append({
  96. Field_Return_TypeAnalyst: self.typeAnalyst(),
  97. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  98. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  99. Field_CodeOfTurbine: 'total',
  100. Field_Return_FilePath: pngFilePath,
  101. Field_Return_IsSaveDatabase: False
  102. })
  103. result_rows.append({
  104. Field_Return_TypeAnalyst: self.typeAnalyst(),
  105. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  106. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  107. Field_CodeOfTurbine: 'total',
  108. Field_Return_FilePath: htmlFilePath,
  109. Field_Return_IsSaveDatabase: True
  110. })
  111. # 绘制环境温度小于25℃的功率分布图
  112. fig = make_subplots(rows=1, cols=1)
  113. fig.add_trace(go.Box(y=below_temp[Field_ActiverPower], x=below_temp[Field_NameOfTurbine],
  114. # name='Ambient Temp < 25°C',
  115. boxpoints='outliers',
  116. # box line color
  117. line=dict(color='black', width=1),
  118. # quartilemethod='exclusive',
  119. fillcolor='dodgerblue',
  120. showlegend=False,
  121. marker=dict(color='rgba(0, 0, 0, 0)', size=0.1)),
  122. row=1, col=1)
  123. # Calculate medians and plot them as a line for visibility
  124. medians = below_temp.groupby(Field_NameOfTurbine)[
  125. Field_ActiverPower].median()
  126. median_line = go.Scatter(
  127. x=medians.index,
  128. y=medians.values,
  129. mode='markers',
  130. marker=dict(symbol='line-ew-open', color='red', size=10),
  131. showlegend=False
  132. )
  133. fig.add_trace(median_line)
  134. # 更新布局
  135. fig.update_yaxes(title_text=y_name, row=1, col=1, range=[
  136. lowLimitOfPower, upLimitOfPower], tickfont=dict(size=10))
  137. fig.update_xaxes(title_text='机组', type='category',
  138. tickangle=-45, tickfont=dict(size=10))
  139. fig.update_layout(title={
  140. 'text': f'额定功率分布(环境温度<25摄氏度)-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5}, boxmode='group')
  141. # 保存图像
  142. pngFileName = "额定满发风速功率分布(10min)(环境温度小于25度).png"
  143. pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  144. fig.write_image(pngFilePath, scale=3)
  145. # 保存HTML
  146. htmlFileName = "额定满发风速功率分布(10min)(环境温度小于25度).html"
  147. htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  148. fig.write_html(htmlFilePath)
  149. result_rows.append({
  150. Field_Return_TypeAnalyst: self.typeAnalyst(),
  151. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  152. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  153. Field_CodeOfTurbine: 'total',
  154. Field_Return_FilePath: pngFilePath,
  155. Field_Return_IsSaveDatabase: False
  156. })
  157. result_rows.append({
  158. Field_Return_TypeAnalyst: self.typeAnalyst(),
  159. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  160. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  161. Field_CodeOfTurbine: 'total',
  162. Field_Return_FilePath: htmlFilePath,
  163. Field_Return_IsSaveDatabase: True
  164. })
  165. result_df = pd.DataFrame(result_rows)
  166. return result_df
  167. """
  168. # 绘制环境温度大于等于25℃的功率分布图
  169. fig, ax = plt.subplots()
  170. sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=over_temp, fliersize=0, ax=ax,
  171. medianprops={'linestyle': '-', 'color': 'red'},
  172. boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
  173. ax.yaxis.set_major_locator(ticker.MultipleLocator(100))
  174. ax.set_ylim(lowLimitOfPower, upLimitOfPower)
  175. ax.set_ylabel(y_name)
  176. ax.set_title(
  177. 'rated wind speed and power distribute(10min)(ambient temperature>=25℃)')
  178. ax.grid(True)
  179. plt.xticks(rotation=45) # 旋转45度
  180. plt.savefig(os.path.join(outputAnalysisDir,
  181. "额定满发风速功率分布(10min)(环境温度大于25度).png"), bbox_inches='tight', dpi=120)
  182. plt.close()
  183. # 绘制环境温度小于25℃的功率分布图
  184. fig, ax = plt.subplots()
  185. sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=below_temp, fliersize=0, ax=ax,
  186. medianprops={'linestyle': '-', 'color': 'red'},
  187. boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
  188. ax.yaxis.set_major_locator(ticker.MultipleLocator(100))
  189. ax.set_ylim(lowLimitOfPower, upLimitOfPower)
  190. ax.set_ylabel(y_name)
  191. ax.set_title(
  192. 'rated wind speed and power distribute(10min)(ambient temperature<25℃)')
  193. ax.grid(True)
  194. plt.xticks(rotation=45) # 旋转45度
  195. plt.savefig(os.path.join(outputAnalysisDir,
  196. "额定满发风速功率分布(10min)(环境温度小于25度).png"), bbox_inches='tight', dpi=120)
  197. plt.close()
  198. """