powerScatterAnalyst.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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. class PowerScatterAnalyst(AnalystWithGoodBadPoint):
  8. """
  9. 风电机组功率曲线散点分析。
  10. 秒级scada数据运算太慢,建议使用分钟级scada数据
  11. """
  12. def typeAnalyst(self):
  13. return "power_scatter"
  14. def selectColumns(self):
  15. return [Field_DeviceCode, Field_Time, Field_WindSpeed, Field_ActiverPower]
  16. def processDateTime(self, dataFrame: pd.DataFrame, fieldTime:str = None):
  17. super().processDateTime(dataFrame,Field_YearMonth)
  18. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  19. dictionary = self.processTurbineData(turbineCodes, conf, self.selectColumns())
  20. dataFrame = self.userDataFrame(dictionary, conf.dataContract.configAnalysis, self)
  21. if len(dataFrame) <= 0:
  22. print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
  23. return
  24. # dataFrameGuaranteePowerCurve=self.powerFarmInfo[Field_PowerContractURL]
  25. # grouped=self.dataFrameContractOfTurbine.groupby([Field_PowerFarmCode, Field_MillTypeCode])
  26. # for groupByKey,contractPowerCurveOfMillType in grouped:
  27. # break
  28. turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  29. return self.drawOfPowerCurveScatter(dataFrame,turbineInfos, outputAnalysisDir, conf)
  30. """
  31. def contractGuaranteePowerCurveData(self, csvPowerCurveFilePath):
  32. dataFrameGuaranteePowerCurve = pd.read_csv(
  33. csvPowerCurveFilePath, encoding=charset_unify)
  34. return dataFrameGuaranteePowerCurve
  35. """
  36. def drawOfPowerCurveScatter(self, dataFrame: pd.DataFrame, turbineModelInfo: pd.Series, outputAnalysisDir, conf: Contract):
  37. """
  38. 绘制风速-功率分布图并保存为文件。
  39. 参数:
  40. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  41. csvPowerCurveFilePath (str): 功率曲线文件路径。
  42. outputAnalysisDir (str): 分析输出目录。
  43. conf (ConfBusiness): 配置
  44. """
  45. colorsList = [
  46. "#3E409C",
  47. "#3586BF",
  48. "#52A3AE",
  49. "#85D0AE",
  50. "#A8DCA2",
  51. "#FBFFBE",
  52. "#FDF1A9",
  53. "#FFE286",
  54. "#FCB06C",
  55. "#F96F4A",
  56. "#E4574C",
  57. "#AF254F"]
  58. cutInWsField = self.turbineModelInfo[Field_CutInWS]
  59. cut_in_ws = cutInWsField.min() - 1 if cutInWsField.notna().any() else 2
  60. # if not dataFrame.empty and Field_CutInWS in dataFrame.columns and dataFrame[Field_CutInWS].notna().any():
  61. # cut_in_ws = dataFrame[Field_CutInWS].min() - 1
  62. # else:
  63. # cut_in_ws = 2
  64. grouped = dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
  65. result_rows = []
  66. # 遍历每个设备的数据
  67. for name, group in grouped:
  68. # 创建颜色映射,将每个年月映射到一个唯一的颜色
  69. unique_months = group[Field_YearMonth].unique()
  70. colors = [
  71. colorsList[i % 12] for i in range(len(unique_months))]
  72. color_map = dict(zip(unique_months, colors))
  73. # 使用go.Scatter3d创建3D散点图
  74. trace = go.Scatter3d(
  75. x=group[Field_WindSpeed],
  76. y=group[Field_YearMonth],
  77. z=group[Field_ActiverPower],
  78. mode='markers',
  79. marker=dict(
  80. color=[color_map[month]
  81. for month in group[Field_YearMonth]],
  82. size=1.5,
  83. line=dict(
  84. color='rgba(0, 0, 0, 0)', # 设置边框颜色为透明,以去掉白色边框
  85. width=0 # 设置边框宽度为0,进一步确保没有边框
  86. ),
  87. opacity=0.8 # 调整散点的透明度,增加透视效果
  88. )
  89. )
  90. # 创建图形
  91. fig = go.Figure(data=[trace])
  92. # 更新图形的布局
  93. fig.update_layout(
  94. title={
  95. "text": f'当月发电量: {name[0]}',
  96. "x": 0.5
  97. },
  98. scene=dict(
  99. xaxis=dict(
  100. title='风速',
  101. range=[cut_in_ws, 25],
  102. ),
  103. yaxis=dict(
  104. title='时间',
  105. tickmode='array',
  106. tickvals=unique_months,
  107. ticktext=unique_months,
  108. categoryorder='category ascending'
  109. ),
  110. zaxis=dict(
  111. title='功率',
  112. range=[self.axisLowerLimitActivePower, self.axisUpperLimitActivePower],
  113. dtick=self.axisStepActivePower,
  114. )
  115. ),
  116. scene_camera=dict(
  117. up=dict(x=0, y=0, z=1), # 保持相机向上方向不变
  118. center=dict(x=0, y=0, z=0), # 保持相机中心位置不变
  119. eye=dict(x=-1.8, y=-1.8, z=1.2) # 调整eye属性以实现水平旋转180°
  120. ),
  121. margin=dict(t=50, b=10) # t为顶部(top)间距,b为底部(bottom)间距
  122. )
  123. # 确保从 Series 中提取的是具体的值
  124. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  125. if isinstance(engineTypeCode, pd.Series):
  126. engineTypeCode = engineTypeCode.iloc[0]
  127. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  128. if isinstance(engineTypeName, pd.Series):
  129. engineTypeName = engineTypeName.iloc[0]
  130. # 构建最终的JSON对象
  131. json_output = {
  132. "analysisTypeCode": "逐月有功功率散点3D分析",
  133. "engineCode": engineTypeCode,
  134. "engineTypeName": engineTypeName,
  135. "xaixs": "风速(m/s)",
  136. "yaixs": "时间",
  137. "zaixs": "有功功率(kw)",
  138. "data": [{
  139. "engineName": name[0],
  140. "engineCode": name[1],
  141. "title":f'当月发电量: {name[0]}',
  142. "xData": group[Field_WindSpeed].tolist(),
  143. "xrange":[cut_in_ws, 25],
  144. "yData":group[Field_YearMonth].tolist(),
  145. "zData":group[Field_ActiverPower].tolist(),
  146. "zrange":[self.axisLowerLimitActivePower, self.axisUpperLimitActivePower]
  147. }]
  148. }
  149. # # Save plot
  150. # filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  151. # fig.write_html(filePathOfHtml)
  152. # 将JSON对象保存到文件
  153. output_json_path = os.path.join(outputAnalysisDir, f"power_scatter{name[0]}.json")
  154. with open(output_json_path, 'w', encoding='utf-8') as f:
  155. import json
  156. json.dump(json_output, f, ensure_ascii=False, indent=4)
  157. # 如果需要返回DataFrame,可以包含文件路径
  158. result_rows.append({
  159. Field_Return_TypeAnalyst: self.typeAnalyst(),
  160. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  161. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  162. Field_CodeOfTurbine: name[1],
  163. Field_Return_FilePath: output_json_path,
  164. Field_Return_IsSaveDatabase: True
  165. })
  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: name[1],
  171. # Field_Return_FilePath: filePathOfHtml,
  172. # Field_Return_IsSaveDatabase: True
  173. # })
  174. result_df = pd.DataFrame(result_rows)
  175. return result_df