powerScatter2DAnalyst.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import os
  2. from datetime import datetime
  3. import numpy as np
  4. import pandas as pd
  5. import plotly.graph_objects as go
  6. from algorithmContract.confBusiness import *
  7. from algorithmContract.contract import Contract
  8. from behavior.analystWithGoodBadPoint import AnalystWithGoodBadPoint
  9. from plotly.subplots import make_subplots
  10. class PowerScatter2DAnalyst(AnalystWithGoodBadPoint):
  11. """
  12. 风电机组功率曲线散点分析。
  13. 秒级scada数据运算太慢,建议使用分钟级scada数据
  14. """
  15. def typeAnalyst(self):
  16. return "power_scatter_2D"
  17. def selectColumns(self):
  18. return [Field_DeviceCode, Field_Time, Field_WindSpeed, Field_ActiverPower]
  19. def addPropertyToDataFrame(self,dataFrameOfTurbine : pd.DataFrame, currTurbineInfo : pd.Series, currTurbineModelInfo : pd.Series):
  20. dataFrameOfTurbine[Field_PowerFarmCode] = self.currPowerFarmInfo[Field_PowerFarmCode]
  21. dataFrameOfTurbine[Field_MillTypeCode] = currTurbineModelInfo[Field_MillTypeCode]
  22. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  23. dictionary = self.processTurbineData(turbineCodes, conf, self.selectColumns())
  24. dataFrame = self.userDataFrame(dictionary, conf.dataContract.configAnalysis, self)
  25. if len(dataFrame) <= 0:
  26. print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
  27. return
  28. grouped = self.dataFrameContractOfTurbine.groupby(
  29. [Field_PowerFarmCode, Field_MillTypeCode])
  30. for groupByKey, contractPowerCurveOfMillType in grouped:
  31. break
  32. return self.drawOfPowerCurveScatter(dataFrame, outputAnalysisDir, conf, contractPowerCurveOfMillType)
  33. def drawOfPowerCurveScatter(self, dataFrame: pd.DataFrame, outputAnalysisDir, conf: Contract, dataFrameGuaranteePowerCurve: pd.DataFrame):
  34. """
  35. 绘制风速-功率分布图并保存为文件。
  36. 参数:
  37. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  38. csvPowerCurveFilePath (str): 功率曲线文件路径。
  39. outputAnalysisDir (str): 分析输出目录。
  40. confData (ConfBusiness): 配置
  41. """
  42. x_name = '风速'
  43. y_name = '功率'
  44. #机型切入风速 series
  45. cutInWsField = self.turbineModelInfo[Field_CutInWS]
  46. cut_in_ws = cutInWsField.min() - 1 if cutInWsField.notna().any() else 2
  47. # if not dataFrame.empty and Field_CutInWS in dataFrame.columns and dataFrame[Field_CutInWS].notna().any():
  48. # cut_in_ws = dataFrame[Field_CutInWS].min() - 1
  49. # else:
  50. # cut_in_ws = 2
  51. # 按设备名分组数据
  52. grouped = dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
  53. result_rows = []
  54. # 定义固定的颜色映射列表
  55. fixed_colors =[
  56. 'rgb(255, 0, 0)', # 红色
  57. 'rgb(0, 255, 0)', # 绿色
  58. 'rgb(0, 0, 255)', # 蓝色
  59. 'rgb(255, 255, 0)', # 黄色
  60. 'rgb(255, 0, 255)', # 紫色
  61. 'rgb(0, 255, 255)' # 青色
  62. ]
  63. # 遍历每个设备的数据
  64. for name, group in grouped:
  65. fig = make_subplots()
  66. # 提取月份
  67. group['month'] = group['monthIntTime'].apply(lambda x: datetime.fromtimestamp(x).month)
  68. unique_months = group['month'].unique()
  69. # 计算时间跨度
  70. time_span_months = len(unique_months)
  71. if time_span_months >= 6:
  72. # 绘制散点图(时间跨度大于等于6个月)
  73. scatter = go.Scatter(x=group[Field_WindSpeed],
  74. y=group[Field_ActiverPower],
  75. mode='markers',
  76. marker=dict(
  77. color=group['monthIntTime'],
  78. colorscale='Rainbow',
  79. size=3,
  80. opacity=0.7,
  81. colorbar=dict(
  82. tickvals=np.linspace(
  83. group['monthIntTime'].min(), group['monthIntTime'].max(), 6),
  84. ticktext=[datetime.fromtimestamp(ts).strftime(
  85. '%Y-%m') for ts in np.linspace(group['monthIntTime'].min(), group['monthIntTime'].max(), 6)],
  86. thickness=18,
  87. len=1, # 设置颜色条的长度,使其占据整个图的高度
  88. outlinecolor='rgba(255,255,255,0)'
  89. ),
  90. showscale=True
  91. ),
  92. showlegend=False) # 不显示散点图的legend,用colorbar代替
  93. fig.add_trace(scatter)
  94. else:
  95. # 绘制散点图(时间跨度小于6个月)
  96. for i,month in enumerate(unique_months):
  97. month_data = group[group['month'] == month]
  98. # 使用固定的颜色列表
  99. color = fixed_colors[i % len(fixed_colors)]
  100. scatter = go.Scatter(x=month_data[Field_WindSpeed],
  101. y=month_data[Field_ActiverPower],
  102. mode='markers',
  103. marker=dict(
  104. color=color,
  105. size=3,
  106. opacity=0.7
  107. ),
  108. name=f'{datetime.fromtimestamp(month_data["monthIntTime"].iloc[0]).strftime("%Y-%m")}',
  109. showlegend=True)
  110. fig.add_trace(scatter)
  111. # 绘制合同功率曲线
  112. line = go.Scatter(x=dataFrameGuaranteePowerCurve[Field_WindSpeed],
  113. y=dataFrameGuaranteePowerCurve[Field_ActiverPower],
  114. mode='lines+markers',
  115. marker=dict(color='gray', size=7),
  116. name='合同功率曲线')
  117. fig.add_trace(line, secondary_y=False)
  118. # 设置图形布局
  119. fig.update_layout(
  120. title=f'机组: {name[0]}',
  121. xaxis=dict(title=x_name,
  122. range=[cut_in_ws, 25],
  123. tickmode='linear', tick0=0, dtick=1,
  124. tickangle=-45),
  125. yaxis=dict(title=y_name,
  126. dtick=self.axisStepActivePower,
  127. range=[self.axisLowerLimitActivePower,
  128. self.axisUpperLimitActivePower]
  129. ),
  130. legend=dict(yanchor="bottom", y=0, xanchor="right", x=1, font=dict(
  131. size=10), bgcolor='rgba(255,255,255,0)')
  132. )
  133. # 保存图像
  134. pngFileName = f"{name[0]}-scatter.png"
  135. pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  136. fig.write_image(pngFilePath, scale=3)
  137. # 保存HTML
  138. htmlFileName = f"{name[0]}-scatter.html"
  139. htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  140. fig.write_html(htmlFilePath)
  141. result_rows.append({
  142. Field_Return_TypeAnalyst: self.typeAnalyst(),
  143. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  144. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  145. Field_CodeOfTurbine: name[1],
  146. Field_Return_FilePath: pngFilePath,
  147. Field_Return_IsSaveDatabase: False
  148. })
  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: name[1],
  154. Field_Return_FilePath: htmlFilePath,
  155. Field_Return_IsSaveDatabase: True
  156. })
  157. result_df = pd.DataFrame(result_rows)
  158. return result_df