powerScatter2DAnalyst.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  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. turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  26. if len(dataFrame) <= 0:
  27. self.logger.info("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
  28. return
  29. return self.drawOfPowerCurveScatter(dataFrame, turbineInfos,outputAnalysisDir, conf, self.dataFrameContractOfTurbine)
  30. def drawOfPowerCurveScatter(self, dataFrame: pd.DataFrame, turbineModelInfo: pd.Series, outputAnalysisDir, conf: Contract, dataFrameGuaranteePowerCurve: pd.DataFrame):
  31. """
  32. 绘制风速-功率分布图并保存为文件。
  33. 参数:
  34. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  35. csvPowerCurveFilePath (str): 功率曲线文件路径。
  36. outputAnalysisDir (str): 分析输出目录。
  37. confData (ConfBusiness): 配置
  38. """
  39. #机型切入风速 series
  40. cutInWsField = self.turbineModelInfo[Field_CutInWS]
  41. cut_in_ws = cutInWsField.min() - 1 if cutInWsField.notna().any() else 2
  42. # if not dataFrame.empty and Field_CutInWS in dataFrame.columns and dataFrame[Field_CutInWS].notna().any():
  43. # cut_in_ws = dataFrame[Field_CutInWS].min() - 1
  44. # else:
  45. # cut_in_ws = 2
  46. # 按设备名分组数据
  47. grouped = dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
  48. result_rows = []
  49. # 遍历每个设备的数据
  50. for name, group in grouped:
  51. #获取当前风机信息dataFrame
  52. currentEngineDataFrame = turbineModelInfo[turbineModelInfo[Field_CodeOfTurbine]==name[1]]
  53. #获取当前机型
  54. millTypeCode = currentEngineDataFrame.get(Field_MillTypeCode, "").iloc[0]
  55. #当前机型合同功率曲线dataFrame
  56. currentMillTypePowerDataFrame = dataFrameGuaranteePowerCurve[dataFrameGuaranteePowerCurve[Field_MillTypeCode] == millTypeCode]
  57. # 获取机型的名字(machine_type_code)
  58. engineTypeName = self.common.getTurbineModelByCode(millTypeCode, self.turbineModelInfo)[Field_MachineTypeCode]
  59. # 使用 apply() 对每个元素调用 datetime.fromtimestamp
  60. group['monthIntTime'] = group['monthIntTime'].apply(lambda x: datetime.fromtimestamp(x).strftime('%Y-%m'))
  61. # 定义要替换的空值类型
  62. na_values = {pd.NA, float('nan')}
  63. # 构建最终的JSON对象
  64. json_output = {
  65. "analysisTypeCode": "逐月有功功率散点2D分析",
  66. "engineCode": millTypeCode,
  67. "engineTypeName": engineTypeName,
  68. "xaixs": "风速(m/s)",
  69. "yaixs": "有功功率(kW)",
  70. "data": [
  71. {# 提取机组数据
  72. "engineName": name[0],
  73. "engineCode": name[1],
  74. "title":f' 逐月有功功率散点2D分析-机组: {name[0]}',
  75. "xData": group[Field_WindSpeed].replace(na_values, None).tolist(),
  76. "xrange":[cut_in_ws, 25],
  77. "yData": group[Field_ActiverPower].replace(na_values, None).tolist(),
  78. "yrange":[self.axisLowerLimitActivePower,self.axisUpperLimitActivePower],
  79. "colorbar": group['monthIntTime'].tolist(),
  80. "colorbartitle": "年月",
  81. "mode":"markers"
  82. },
  83. {# 提取合同功率曲线数据
  84. "enginName": "合同功率曲线",
  85. "xData":currentMillTypePowerDataFrame[Field_WindSpeed].replace(na_values, None).tolist(),
  86. "yData":currentMillTypePowerDataFrame[Field_ActiverPower].replace(na_values, None).tolist(),
  87. "zData": [],
  88. "mode":"lines+markers"
  89. }]
  90. }
  91. # 将JSON对象保存到文件
  92. output_json_path = os.path.join(outputAnalysisDir, f"{name[0]}-scatter.json")
  93. with open(output_json_path, 'w', encoding='utf-8') as f:
  94. import json
  95. json.dump(json_output, f, ensure_ascii=False, indent=4)
  96. # 如果需要返回DataFrame,可以包含文件路径
  97. result_rows.append({
  98. Field_Return_TypeAnalyst: self.typeAnalyst(),
  99. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  100. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  101. Field_CodeOfTurbine: name[1],
  102. Field_Return_FilePath: output_json_path,
  103. Field_Return_IsSaveDatabase: True
  104. })
  105. result_df = pd.DataFrame(result_rows)
  106. return result_df