ratedPowerWindSpeedAnalyst.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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. turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  19. return self.draw(dataFrameMerge, outputAnalysisDir, conf,turbineInfos)
  20. def draw(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract,turbineModelInfo: pd.Series):
  21. """
  22. 绘制并保存额定满发风速功率分布图,根据环境温度是否大于等于25℃。
  23. 参数:
  24. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  25. outputAnalysisDir (str): 分析输出目录。
  26. confData (ConfBusiness): 配置
  27. """
  28. # 检查所需列是否存在
  29. required_columns = {Field_EnvTemp,
  30. Field_WindSpeed, Field_ActiverPower}
  31. if not required_columns.issubset(dataFrameMerge.columns):
  32. raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
  33. y_name = '功率'
  34. upLimitOfPower = self.turbineInfo[Field_RatedPower].max() * 1.2
  35. lowLimitOfPower = self.turbineInfo[Field_RatedPower].max()*0.9
  36. field_RatedWindSpeed = self.turbineModelInfo[Field_RatedWindSpeed].max()
  37. # 根据环境温度筛选数据
  38. over_temp = dataFrameMerge[(dataFrameMerge[Field_EnvTemp] >= 25) & (
  39. dataFrameMerge[Field_WindSpeed] >= field_RatedWindSpeed) & (dataFrameMerge[Field_ActiverPower] >= lowLimitOfPower)].sort_values(by=Field_NameOfTurbine)
  40. below_temp = dataFrameMerge[(dataFrameMerge[Field_EnvTemp] < 25) & (
  41. dataFrameMerge[Field_WindSpeed] >= field_RatedWindSpeed) & (dataFrameMerge[Field_ActiverPower] >= lowLimitOfPower)].sort_values(by=Field_NameOfTurbine)
  42. # 绘制环境温度大于等于25℃的功率分布图
  43. fig = make_subplots(rows=1, cols=1)
  44. fig.add_trace(go.Box(y=over_temp[Field_ActiverPower], x=over_temp[Field_NameOfTurbine],
  45. # name='Ambient Temp >= 25°C',
  46. boxpoints='outliers',
  47. # box line color
  48. line=dict(color='black', width=1),
  49. # quartilemethod='exclusive',
  50. fillcolor='dodgerblue',
  51. showlegend=False,
  52. marker=dict(color='rgba(0, 0, 0, 0)', size=0.1)),
  53. row=1, col=1)
  54. # Calculate medians and plot them as a line for visibility
  55. medians = over_temp.groupby(Field_NameOfTurbine)[
  56. Field_ActiverPower].median()
  57. median_line = go.Scatter(
  58. x=medians.index,
  59. y=medians.values,
  60. mode='markers',
  61. marker=dict(symbol='line-ew-open', color='red', size=12),
  62. showlegend=False
  63. )
  64. fig.add_trace(median_line)
  65. # 更新布局
  66. fig.update_yaxes(title_text=y_name, row=1, col=1, range=[
  67. lowLimitOfPower, upLimitOfPower], tickfont=dict(size=10))
  68. fig.update_xaxes(title_text='机组', type='category',
  69. tickangle=-45, tickfont=dict(size=10))
  70. fig.update_layout(title={
  71. 'text': f'额定功率分布(环境温度>=25摄氏度)', 'x': 0.5}, boxmode='group')
  72. # 确保从 Series 中提取的是具体的值
  73. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  74. if isinstance(engineTypeCode, pd.Series):
  75. engineTypeCode = engineTypeCode.iloc[0]
  76. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  77. if isinstance(engineTypeName, pd.Series):
  78. engineTypeName = engineTypeName.iloc[0]
  79. # 构建最终的JSON对象
  80. json_output = {
  81. "analysisTypeCode": "额定功率和风速分析",
  82. "engineCode": engineTypeCode,
  83. "engineTypeName": engineTypeName,
  84. "xaixs": "机组",
  85. "yaixs": "功率(kw)",
  86. "data": [{
  87. "title":f'额定功率分布(环境温度>=25摄氏度)',
  88. "xData": over_temp[Field_NameOfTurbine].tolist(),
  89. "yData": over_temp[Field_ActiverPower].tolist(),
  90. "linecolor":'black',
  91. "linewidth":1,
  92. "fillcolor":'dodgerblue'
  93. }]
  94. }
  95. result_rows = []
  96. # 保存图像
  97. pngFileName = "额定满发风速功率分布(10min)(环境温度大于25度).png"
  98. pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  99. fig.write_image(pngFilePath, scale=3)
  100. # # 保存HTML
  101. # htmlFileName = "额定满发风速功率分布(10min)(环境温度大于25度).html"
  102. # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  103. # fig.write_html(htmlFilePath)
  104. # 保存Json
  105. # 将JSON对象保存到文件
  106. output_json_path = os.path.join(outputAnalysisDir, "total_more_25.json")
  107. with open(output_json_path, 'w', encoding='utf-8') as f:
  108. import json
  109. json.dump(json_output, f, ensure_ascii=False, indent=4)
  110. # 如果需要返回DataFrame,可以包含文件路径
  111. result_rows.append({
  112. Field_Return_TypeAnalyst: self.typeAnalyst(),
  113. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  114. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  115. Field_CodeOfTurbine: 'total',
  116. Field_MillTypeCode: 'total_less_25',
  117. Field_Return_FilePath: output_json_path,
  118. Field_Return_IsSaveDatabase: True
  119. })
  120. result_rows.append({
  121. Field_Return_TypeAnalyst: self.typeAnalyst(),
  122. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  123. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  124. Field_CodeOfTurbine: 'total',
  125. Field_Return_FilePath: pngFilePath,
  126. Field_Return_IsSaveDatabase: False
  127. })
  128. # result_rows.append({
  129. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  130. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  131. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  132. # Field_CodeOfTurbine: 'total',
  133. # Field_Return_FilePath: htmlFilePath,
  134. # Field_Return_IsSaveDatabase: True
  135. # })
  136. # 绘制环境温度小于25℃的功率分布图
  137. fig = make_subplots(rows=1, cols=1)
  138. fig.add_trace(go.Box(y=below_temp[Field_ActiverPower], x=below_temp[Field_NameOfTurbine],
  139. # name='Ambient Temp < 25°C',
  140. boxpoints='outliers',
  141. # box line color
  142. line=dict(color='black', width=1),
  143. # quartilemethod='exclusive',
  144. fillcolor='dodgerblue',
  145. showlegend=False,
  146. marker=dict(color='rgba(0, 0, 0, 0)', size=0.1)),
  147. row=1, col=1)
  148. # Calculate medians and plot them as a line for visibility
  149. medians = below_temp.groupby(Field_NameOfTurbine)[
  150. Field_ActiverPower].median()
  151. median_line = go.Scatter(
  152. x=medians.index,
  153. y=medians.values,
  154. mode='markers',
  155. marker=dict(symbol='line-ew-open', color='red', size=10),
  156. showlegend=False
  157. )
  158. fig.add_trace(median_line)
  159. # 更新布局
  160. fig.update_yaxes(title_text=y_name, row=1, col=1, range=[
  161. lowLimitOfPower, upLimitOfPower], tickfont=dict(size=10))
  162. fig.update_xaxes(title_text='机组', type='category',
  163. tickangle=-45, tickfont=dict(size=10))
  164. fig.update_layout(title={
  165. 'text': f'额定功率分布(环境温度<25摄氏度)', 'x': 0.5}, boxmode='group')
  166. # 构建最终的JSON对象2
  167. json_output2 = {
  168. "analysisTypeCode": "额定功率和风速分析",
  169. "engineCode": engineTypeCode,
  170. "engineTypeName": engineTypeName,
  171. "xaixs": "机组",
  172. "yaixs": "功率(kw)",
  173. "data": [{
  174. "title":f'额定功率分布(环境温度<25摄氏度)',
  175. "xData": below_temp[Field_NameOfTurbine].tolist(),
  176. "yData": below_temp[Field_ActiverPower].tolist(),
  177. "linecolor":'black',
  178. "linewidth":1,
  179. "fillcolor":'dodgerblue'
  180. }]
  181. }
  182. # 保存图像
  183. pngFileName = "额定满发风速功率分布(10min)(环境温度小于25度).png"
  184. pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  185. fig.write_image(pngFilePath, scale=3)
  186. # # 保存HTML
  187. # htmlFileName = "额定满发风速功率分布(10min)(环境温度小于25度).html"
  188. # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  189. # fig.write_html(htmlFilePath)
  190. # 保存Json
  191. # 将JSON对象保存到文件
  192. output_json_path2 = os.path.join(outputAnalysisDir, "total_less_25.json")
  193. with open(output_json_path2, 'w', encoding='utf-8') as f:
  194. import json
  195. json.dump(json_output2, f, ensure_ascii=False, indent=4)
  196. # 如果需要返回DataFrame,可以包含文件路径
  197. result_rows.append({
  198. Field_Return_TypeAnalyst: self.typeAnalyst(),
  199. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  200. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  201. Field_CodeOfTurbine: 'total',
  202. Field_MillTypeCode: 'total_less_25',
  203. Field_Return_FilePath: output_json_path2,
  204. Field_Return_IsSaveDatabase: True
  205. })
  206. result_rows.append({
  207. Field_Return_TypeAnalyst: self.typeAnalyst(),
  208. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  209. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  210. Field_CodeOfTurbine: 'total',
  211. Field_Return_FilePath: pngFilePath,
  212. Field_Return_IsSaveDatabase: False
  213. })
  214. # result_rows.append({
  215. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  216. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  217. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  218. # Field_CodeOfTurbine: 'total',
  219. # Field_Return_FilePath: htmlFilePath,
  220. # Field_Return_IsSaveDatabase: True
  221. # })
  222. result_df = pd.DataFrame(result_rows)
  223. return result_df
  224. """
  225. # 绘制环境温度大于等于25℃的功率分布图
  226. fig, ax = plt.subplots()
  227. sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=over_temp, fliersize=0, ax=ax,
  228. medianprops={'linestyle': '-', 'color': 'red'},
  229. boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
  230. ax.yaxis.set_major_locator(ticker.MultipleLocator(100))
  231. ax.set_ylim(lowLimitOfPower, upLimitOfPower)
  232. ax.set_ylabel(y_name)
  233. ax.set_title(
  234. 'rated wind speed and power distribute(10min)(ambient temperature>=25℃)')
  235. ax.grid(True)
  236. plt.xticks(rotation=45) # 旋转45度
  237. plt.savefig(os.path.join(outputAnalysisDir,
  238. "额定满发风速功率分布(10min)(环境温度大于25度).png"), bbox_inches='tight', dpi=120)
  239. plt.close()
  240. # 绘制环境温度小于25℃的功率分布图
  241. fig, ax = plt.subplots()
  242. sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=below_temp, fliersize=0, ax=ax,
  243. medianprops={'linestyle': '-', 'color': 'red'},
  244. boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
  245. ax.yaxis.set_major_locator(ticker.MultipleLocator(100))
  246. ax.set_ylim(lowLimitOfPower, upLimitOfPower)
  247. ax.set_ylabel(y_name)
  248. ax.set_title(
  249. 'rated wind speed and power distribute(10min)(ambient temperature<25℃)')
  250. ax.grid(True)
  251. plt.xticks(rotation=45) # 旋转45度
  252. plt.savefig(os.path.join(outputAnalysisDir,
  253. "额定满发风速功率分布(10min)(环境温度小于25度).png"), bbox_inches='tight', dpi=120)
  254. plt.close()
  255. """