minPitchAnalyst.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import os
  2. import pandas as pd
  3. import plotly.express as px
  4. from algorithmContract.confBusiness import *
  5. from algorithmContract.contract import Contract
  6. from behavior.analystWithGoodBadPoint import AnalystWithGoodBadPoint
  7. class MinPitchAnalyst(AnalystWithGoodBadPoint):
  8. """
  9. 风电机组最小桨距角分析
  10. """
  11. def typeAnalyst(self):
  12. return "min_pitch"
  13. def selectColums(self):
  14. return [Field_DeviceCode,Field_Time,Field_WindSpeed,Field_ActiverPower,Field_PitchAngel1]
  15. def processDateTime(self, dataFrame: pd.DataFrame, fieldTime:str = None):
  16. super().processDateTime(dataFrame,Field_YearMonthDay)
  17. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  18. dictionary = self.processTurbineData(turbineCodes,conf,self.selectColums())
  19. turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  20. dataFrameMerge = self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self)
  21. return self.drawTrendGraph(dataFrameMerge,turbineInfos, outputAnalysisDir, conf)
  22. def drawTrendGraph(self, dataFrameMerge: pd.DataFrame, turbineModelInfo: pd.Series,outputAnalysisDir, conf: Contract):
  23. """
  24. Generates pitch angle distribution scatter plots for turbines in a wind farm using plotly.
  25. Parameters:
  26. - dataFrameMerge: pd.DataFrame, DataFrame containing turbine data.
  27. - outputAnalysisDir: str, path to save the output plots.
  28. - conf: Contract, configuration object containing field names.
  29. """
  30. # 检查所需列是否存在
  31. required_columns = {Field_YearMonthDay, Field_PitchAngel1}
  32. if not required_columns.issubset(dataFrameMerge.columns):
  33. raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
  34. pitchAngleRate = 'pitch_angle_rate'
  35. fieldPitchAngleBin = 'pitch_angle_bin'
  36. # Custom color scale: Blue (high pitch_angle_rate) to Light Grey (low pitch_angle_rate)
  37. # custom_color_scale = [
  38. # (0.0, "rgb(255, 255, 255)"), # white for the lowest values
  39. # (0.05, "rgb(240, 240, 240)"), # Light grey for the lowest values
  40. # (0.5, "rgba(55.0, 135.0, 192.33333333333334, 1.0)"), # Medium blue-grey
  41. # # (0.75, "rgba(55.0, 135.0, 192.33333333333334, 1.0)"), # Medium blue-grey
  42. # # Dark blue for the highest values
  43. # (1.0, "rgba(55.0, 135.0, 192.33333333333334, 1.0)")
  44. # ]
  45. custom_color_scale = [
  46. (0.0, "rgb(255, 255, 255)"), # White for the lowest values (0%)
  47. # (0.05, "rgb(135, 206, 235)"),# Sky blue for low but non-zero values
  48. (0.05, "rgb(30, 144, 255)"), # Dodger blue for for low but non-zero values
  49. (0.5, "rgb(0, 0, 255)"), # Blue for higher values
  50. (1.0, "rgb(0, 0, 139)") # Dark blue (Dark slate blue)
  51. ]
  52. # Group data by turbine identifier
  53. # dataFrameMerge = dataFrameMerge[dataFrameMerge[Field_PitchAngel1] <= 5]
  54. grouped = dataFrameMerge.groupby(
  55. [Field_NameOfTurbine, Field_CodeOfTurbine])
  56. # 创建一个列表来存储各个风电机组的数据
  57. turbine_data_list = []
  58. result_rows = []
  59. for name, group in grouped:
  60. # Convert the date column to datetime type
  61. group[Field_YearMonthDay] = pd.to_datetime(
  62. group[Field_YearMonthDay])
  63. # Creating bins of 0.2 intervals for pitch angles
  64. bins = pd.interval_range(start=group[Field_PitchAngel1].min(),
  65. end=group[Field_PitchAngel1].max(
  66. ) + 0.1, freq=0.2, closed='right')
  67. group[fieldPitchAngleBin] = pd.cut(
  68. group[Field_PitchAngel1], bins=bins)
  69. group[fieldPitchAngleBin] = group[fieldPitchAngleBin].apply(
  70. lambda x: x.left) # 提取每个区间的左端点作为值
  71. # Calculate the pitch angle rate within each day
  72. # df = group.groupby([Field_YearMonthDay, Field_PitchAngel1]
  73. # ).size().reset_index(name='count')
  74. df = group.groupby([Field_YearMonthDay, fieldPitchAngleBin]
  75. ).size().reset_index(name='count')
  76. total_counts = group.groupby(Field_YearMonthDay
  77. ).size().reset_index(name='total_count')
  78. df = df.merge(total_counts, on=Field_YearMonthDay)
  79. # df[pitchAngleRate] = df['count'] / df['total_count'] * 100
  80. # df[pitchAngleRate] = (df['count'] / df['total_count']).apply(lambda x: x ** 0.5)*100
  81. df[pitchAngleRate] = (df['count'] / df['total_count']) * 100
  82. # 提取数据
  83. turbine_data = {
  84. "engineName": name[0],
  85. "engineCode": name[1],
  86. "title":f' {name[0]}的最小桨距角分布',
  87. "xData": df[Field_YearMonthDay].dt.strftime('%Y-%m-%d').tolist(),
  88. "yData": df[fieldPitchAngleBin].tolist(),
  89. "colorbar": df[pitchAngleRate].tolist(),
  90. "colorbartitle": "百分比(%)"
  91. }
  92. turbine_data_list.append(turbine_data)
  93. # Plotting using plotly
  94. fig = px.scatter(df,
  95. x=Field_YearMonthDay,
  96. # y=Field_PitchAngel1, # 桨距角不分仓方式
  97. y=fieldPitchAngleBin, # 桨距角分仓方式
  98. size='count',
  99. color=pitchAngleRate,
  100. # color_continuous_scale='Blues',
  101. color_continuous_scale=custom_color_scale
  102. )
  103. # Set date format on x-axis
  104. fig.update_xaxes(
  105. title='时间', tickformat='%Y-%m-%d', tickangle=-45)
  106. fig.update_yaxes(title='桨距角',
  107. dtick=self.axisStepPitchAngle,
  108. range=[self.axisLowerLimitPitchAngle, 10],
  109. )
  110. # Customizing legend
  111. fig.update_layout(
  112. title={
  113. "text": f' {name[0]}的最小桨距角分布',
  114. "x": 0.5
  115. },
  116. coloraxis_colorbar=dict(title='百分比'),
  117. margin=dict(t=50, b=10), # t为顶部(top)间距,b为底部(bottom)间距
  118. # plot_bgcolor='rgb(240, 240, 240)'
  119. )
  120. # Set marker size if fixed size is needed
  121. # Fixed size for all points
  122. fig.update_traces(marker=dict(size=3, opacity=0.9))
  123. # Save plot
  124. filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  125. fig.write_image(filePathOfImage, scale=3)
  126. filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  127. fig.write_html(filePathOfHtml)
  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: name[1],
  133. Field_Return_FilePath: filePathOfImage,
  134. Field_Return_IsSaveDatabase: False
  135. })
  136. result_rows.append({
  137. Field_Return_TypeAnalyst: self.typeAnalyst(),
  138. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  139. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  140. Field_CodeOfTurbine: name[1],
  141. Field_Return_FilePath: filePathOfHtml,
  142. Field_Return_IsSaveDatabase: True
  143. })
  144. # 确保从 Series 中提取的是具体的值
  145. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  146. if isinstance(engineTypeCode, pd.Series):
  147. engineTypeCode = engineTypeCode.iloc[0]
  148. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  149. if isinstance(engineTypeName, pd.Series):
  150. engineTypeName = engineTypeName.iloc[0]
  151. # 构建最终的JSON对象
  152. json_output = {
  153. "analysisTypeCode": "最小桨距角分析",
  154. "engineCode": engineTypeCode,
  155. "engineTypeName": engineTypeName,
  156. "xaixs": "时间",
  157. "yaixs": "桨距角(度)",
  158. "data": turbine_data_list
  159. }
  160. # 将JSON对象保存到文件
  161. output_json_path = os.path.join(outputAnalysisDir, "min_pitch_analysis.json")
  162. with open(output_json_path, 'w', encoding='utf-8') as f:
  163. import json
  164. json.dump(json_output, f, ensure_ascii=False, indent=4)
  165. # 如果需要返回DataFrame,可以包含文件路径
  166. result_rows.append({
  167. Field_Return_TypeAnalyst: self.typeAnalyst(),
  168. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  169. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  170. Field_CodeOfTurbine: Const_Output_Total,
  171. Field_Return_FilePath: output_json_path,
  172. Field_Return_IsSaveDatabase: True
  173. })
  174. result_df = pd.DataFrame(result_rows)
  175. return result_df