ratedPowerWindSpeedAnalyst.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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, "额定满发风速功率分布(10min)(环境温度大于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_Return_FilePath: output_json_path,
  117. Field_Return_IsSaveDatabase: True
  118. })
  119. result_rows.append({
  120. Field_Return_TypeAnalyst: self.typeAnalyst(),
  121. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  122. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  123. Field_CodeOfTurbine: 'total',
  124. Field_Return_FilePath: pngFilePath,
  125. Field_Return_IsSaveDatabase: False
  126. })
  127. # result_rows.append({
  128. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  129. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  130. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  131. # Field_CodeOfTurbine: 'total',
  132. # Field_Return_FilePath: htmlFilePath,
  133. # Field_Return_IsSaveDatabase: True
  134. # })
  135. # 绘制环境温度小于25℃的功率分布图
  136. fig = make_subplots(rows=1, cols=1)
  137. fig.add_trace(go.Box(y=below_temp[Field_ActiverPower], x=below_temp[Field_NameOfTurbine],
  138. # name='Ambient Temp < 25°C',
  139. boxpoints='outliers',
  140. # box line color
  141. line=dict(color='black', width=1),
  142. # quartilemethod='exclusive',
  143. fillcolor='dodgerblue',
  144. showlegend=False,
  145. marker=dict(color='rgba(0, 0, 0, 0)', size=0.1)),
  146. row=1, col=1)
  147. # Calculate medians and plot them as a line for visibility
  148. medians = below_temp.groupby(Field_NameOfTurbine)[
  149. Field_ActiverPower].median()
  150. median_line = go.Scatter(
  151. x=medians.index,
  152. y=medians.values,
  153. mode='markers',
  154. marker=dict(symbol='line-ew-open', color='red', size=10),
  155. showlegend=False
  156. )
  157. fig.add_trace(median_line)
  158. # 更新布局
  159. fig.update_yaxes(title_text=y_name, row=1, col=1, range=[
  160. lowLimitOfPower, upLimitOfPower], tickfont=dict(size=10))
  161. fig.update_xaxes(title_text='机组', type='category',
  162. tickangle=-45, tickfont=dict(size=10))
  163. fig.update_layout(title={
  164. 'text': f'额定功率分布(环境温度<25摄氏度)', 'x': 0.5}, boxmode='group')
  165. # 构建最终的JSON对象2
  166. json_output2 = {
  167. "analysisTypeCode": "额定功率和风速分析",
  168. "engineCode": engineTypeCode,
  169. "engineTypeName": engineTypeName,
  170. "xaixs": "机组",
  171. "yaixs": "功率(kw)",
  172. "data": [{
  173. "title":f'额定功率分布(环境温度<25摄氏度)',
  174. "xData": below_temp[Field_NameOfTurbine].tolist(),
  175. "yData": below_temp[Field_ActiverPower].tolist(),
  176. "linecolor":'black',
  177. "linewidth":1,
  178. "fillcolor":'dodgerblue'
  179. }]
  180. }
  181. # 保存图像
  182. pngFileName = "额定满发风速功率分布(10min)(环境温度小于25度).png"
  183. pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  184. fig.write_image(pngFilePath, scale=3)
  185. # # 保存HTML
  186. # htmlFileName = "额定满发风速功率分布(10min)(环境温度小于25度).html"
  187. # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  188. # fig.write_html(htmlFilePath)
  189. # 保存Json
  190. # 将JSON对象保存到文件
  191. output_json_path2 = os.path.join(outputAnalysisDir, "额定满发风速功率分布(10min)(环境温度小于25度).json")
  192. with open(output_json_path2, 'w', encoding='utf-8') as f:
  193. import json
  194. json.dump(json_output2, f, ensure_ascii=False, indent=4)
  195. # 如果需要返回DataFrame,可以包含文件路径
  196. result_rows.append({
  197. Field_Return_TypeAnalyst: self.typeAnalyst(),
  198. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  199. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  200. Field_CodeOfTurbine: 'total',
  201. Field_Return_FilePath: output_json_path2,
  202. Field_Return_IsSaveDatabase: True
  203. })
  204. result_rows.append({
  205. Field_Return_TypeAnalyst: self.typeAnalyst(),
  206. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  207. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  208. Field_CodeOfTurbine: 'total',
  209. Field_Return_FilePath: pngFilePath,
  210. Field_Return_IsSaveDatabase: False
  211. })
  212. # result_rows.append({
  213. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  214. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  215. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  216. # Field_CodeOfTurbine: 'total',
  217. # Field_Return_FilePath: htmlFilePath,
  218. # Field_Return_IsSaveDatabase: True
  219. # })
  220. result_df = pd.DataFrame(result_rows)
  221. return result_df
  222. """
  223. # 绘制环境温度大于等于25℃的功率分布图
  224. fig, ax = plt.subplots()
  225. sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=over_temp, fliersize=0, ax=ax,
  226. medianprops={'linestyle': '-', 'color': 'red'},
  227. boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
  228. ax.yaxis.set_major_locator(ticker.MultipleLocator(100))
  229. ax.set_ylim(lowLimitOfPower, upLimitOfPower)
  230. ax.set_ylabel(y_name)
  231. ax.set_title(
  232. 'rated wind speed and power distribute(10min)(ambient temperature>=25℃)')
  233. ax.grid(True)
  234. plt.xticks(rotation=45) # 旋转45度
  235. plt.savefig(os.path.join(outputAnalysisDir,
  236. "额定满发风速功率分布(10min)(环境温度大于25度).png"), bbox_inches='tight', dpi=120)
  237. plt.close()
  238. # 绘制环境温度小于25℃的功率分布图
  239. fig, ax = plt.subplots()
  240. sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=below_temp, fliersize=0, ax=ax,
  241. medianprops={'linestyle': '-', 'color': 'red'},
  242. boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'})
  243. ax.yaxis.set_major_locator(ticker.MultipleLocator(100))
  244. ax.set_ylim(lowLimitOfPower, upLimitOfPower)
  245. ax.set_ylabel(y_name)
  246. ax.set_title(
  247. 'rated wind speed and power distribute(10min)(ambient temperature<25℃)')
  248. ax.grid(True)
  249. plt.xticks(rotation=45) # 旋转45度
  250. plt.savefig(os.path.join(outputAnalysisDir,
  251. "额定满发风速功率分布(10min)(环境温度小于25度).png"), bbox_inches='tight', dpi=120)
  252. plt.close()
  253. """