yawErrorDensityAnalyst.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import os
  2. import pandas as pd
  3. import numpy as np
  4. import plotly.graph_objects as go
  5. from algorithmContract.confBusiness import *
  6. from algorithmContract.contract import Contract
  7. from behavior.analystWithGoodPoint import AnalystWithGoodPoint
  8. from scipy.stats import binned_statistic_2d
  9. from scipy.stats import skew, kurtosis
  10. from utils.jsonUtil import JsonUtil
  11. class YawErrorDensityAnalyst(AnalystWithGoodPoint):
  12. """
  13. 风电机组动态偏航策略分析
  14. """
  15. def typeAnalyst(self):
  16. return "yaw_error_density"
  17. def selectColumns(self):
  18. return [Field_DeviceCode,Field_Time,Field_WindSpeed,Field_ActiverPower,Field_YawError]
  19. def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes):
  20. dictionary = self.processTurbineData(turbineCodes,conf,self.selectColumns())
  21. dataFrameMerge = self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self)
  22. turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo)
  23. results=self.yawErrorAnalysis(dataFrameMerge, turbineInfos,outputAnalysisDir, conf)
  24. return results
  25. def yawErrorAnalysis(self, dataFrameMerge: pd.DataFrame, turbineModelInfo: pd.Series, outputAnalysisDir, conf: Contract):
  26. # 检查所需列是否存在
  27. required_columns = {Field_ActiverPower, Field_YawError,Field_WindSpeed}
  28. if not required_columns.issubset(dataFrameMerge.columns):
  29. raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}")
  30. result_rows = []
  31. grouped = dataFrameMerge.groupby(
  32. [Field_NameOfTurbine, Field_CodeOfTurbine])
  33. # 定义固定的颜色映射列表
  34. fixed_colors = [
  35. "#3E409C",
  36. "#476CB9",
  37. "#3586BF",
  38. "#4FA4B5",
  39. "#52A3AE",
  40. "#60C5A3",
  41. "#85D0AE",
  42. "#A8DCA2",
  43. "#CFEE9E",
  44. "#E4F39E",
  45. "#EEF9A7",
  46. "#FBFFBE",
  47. "#FDF1A9",
  48. "#FFE286",
  49. "#FFC475",
  50. "#FCB06C",
  51. "#F78F4F",
  52. "#F96F4A",
  53. "#E4574C",
  54. "#CA3756",
  55. "#AF254F"
  56. ]
  57. # 将 fixed_colors 转换为 Plotly 的 colorscale 格式
  58. fixed_colorscale = [
  59. [i / (len(fixed_colors) - 1), color] for i, color in enumerate(fixed_colors)
  60. ]
  61. for name, group in grouped:
  62. df = self.calculateYawError(group)
  63. df.dropna(inplace=True)
  64. counts = df['density'].value_counts()
  65. count_0 = counts.get(0, 0) # 获取 density 为 0 的数量,如果没有 0 则返回 0
  66. count_1 = counts.get(1, 0) # 获取 density 为 1 的数量,如果没有 1 则返回 0
  67. # print(f"Density 为 0 的数量: {count_0}")
  68. # print(f"Density 为 1 的数量: {count_1}")
  69. df = df[df["density"]>0]
  70. mean_X = np.mean(df["x"])
  71. std_X = np.std(df["x"])
  72. mediann_X= np.median(df["x"])
  73. skewness_X = skew(df["x"])
  74. kurtosis_X = kurtosis(df["x"])
  75. max_X = np.max(df["x"])
  76. min_X = np.min(df["x"])
  77. result={
  78. 'mean_X':[mean_X],
  79. 'std_X': [std_X],
  80. 'mediann_X':[mediann_X],
  81. 'skewness_X':[skewness_X],
  82. 'kurtosis_X':[kurtosis_X],
  83. 'max_X':[max_X],
  84. 'min_X':[min_X]
  85. }
  86. result = pd.DataFrame(result)
  87. # 用密度作为颜色绘制散点图,并限制横坐标范围为 -20 到 20
  88. fig = go.Figure()
  89. fig.add_trace(go.Scattergl(
  90. x=df["x"],
  91. y=df["y"],
  92. mode='markers',
  93. marker=dict(
  94. size=3,
  95. opacity=0.7,
  96. color=df["density"],
  97. colorscale=fixed_colorscale,
  98. showscale=True,
  99. )
  100. ))
  101. fig.update_layout(
  102. xaxis_title='对风角度',
  103. yaxis_title='风速',
  104. title=f'动态偏航误差分析-{name[0]}',
  105. xaxis=dict(range=[-20, 20]), # 限制横坐标范围为 -20 到 20
  106. yaxis=dict(range=[0, 25])
  107. )
  108. # 确保从 Series 中提取的是具体的值
  109. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  110. if isinstance(engineTypeCode, pd.Series):
  111. engineTypeCode = engineTypeCode.iloc[0]
  112. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  113. if isinstance(engineTypeName, pd.Series):
  114. engineTypeName = engineTypeName.iloc[0]
  115. # 构建最终的JSON对象
  116. json_output = {
  117. "analysisTypeCode": "动态偏航误差",
  118. "engineCode": engineTypeCode,
  119. "engineTypeName": engineTypeName,
  120. "xaixs": "对风角度(度)",
  121. "yaixs": "风速(m/s)",
  122. "data": [{
  123. "engineName": name[0],
  124. "engineCode": name[1],
  125. "title":f'动态偏航误差分析-{name[0]}',
  126. "xData": df["x"].tolist(),
  127. "yData": df["y"].tolist(),
  128. "colorbar": df["density"].tolist(),
  129. "colorbartitle": "密度"
  130. }]
  131. }
  132. # Save to file
  133. filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  134. fig.write_image(filePathOfImage, scale=3)
  135. # filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  136. # fig.write_html(filePathOfHtml)
  137. # 将JSON对象保存到文件
  138. output_json_path = os.path.join(outputAnalysisDir, f"{name[0]}.json")
  139. with open(output_json_path, 'w', encoding='utf-8') as f:
  140. import json
  141. json.dump(json_output, f, ensure_ascii=False, indent=4)
  142. # 如果需要返回DataFrame,可以包含文件路径
  143. result_rows.append({
  144. Field_Return_TypeAnalyst: self.typeAnalyst(),
  145. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  146. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  147. Field_CodeOfTurbine: name[1],
  148. Field_Return_FilePath: output_json_path,
  149. Field_Return_IsSaveDatabase: True
  150. })
  151. result_rows.append({
  152. Field_Return_TypeAnalyst: self.typeAnalyst(),
  153. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  154. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  155. Field_CodeOfTurbine: name[1],
  156. Field_Return_FilePath: filePathOfImage,
  157. Field_Return_IsSaveDatabase: False
  158. })
  159. # result_rows.append({
  160. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  161. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  162. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  163. # Field_CodeOfTurbine: name[1],
  164. # Field_Return_FilePath: filePathOfHtml,
  165. # Field_Return_IsSaveDatabase: True
  166. # })
  167. result_df = pd.DataFrame(result_rows)
  168. return result_df
  169. def calculateYawError(self, dataFrame: pd.DataFrame):
  170. dataFrame = dataFrame.dropna(
  171. subset=[Field_NameOfTurbine, Field_YawError, Field_ActiverPower,Field_WindSpeed])
  172. filtered_dataFrame = dataFrame[(dataFrame[Field_YawError].abs() <= 30)&(dataFrame[Field_WindSpeed] >= 0)&(dataFrame[Field_WindSpeed] <= 25)]
  173. x=filtered_dataFrame[Field_YawError]
  174. y=filtered_dataFrame[Field_WindSpeed]
  175. # data = np.column_stack((x, y)) # 合并为两列数组
  176. # 使用 binned_statistic_2d 来计算散点的密度分布
  177. binSize_x = 60
  178. binSize_y = 50
  179. counts, x_edges, y_edges, binnumber = binned_statistic_2d(x, y, values=None, statistic='count', bins=[binSize_x, binSize_y])
  180. # 将数据密度转化为颜色值
  181. binX = np.digitize(x, x_edges) - 1
  182. binY = np.digitize(y, y_edges) - 1
  183. # 删除超出范围的下标
  184. validIdx = (binX >= 0) & (binX < binSize_x) & (binY >= 0) & (binY < binSize_y)
  185. # 获取有效下标的密度
  186. density = np.zeros(len(x))
  187. density[validIdx] = counts[binX[validIdx], binY[validIdx]]
  188. # 将结果保存到 result 中
  189. result = {
  190. 'x': x,
  191. 'y': y,
  192. 'density': density
  193. }
  194. # 将 result 转换为 DataFrame
  195. result_df = pd.DataFrame(result)
  196. return result_df