yawErrorDensityAnalyst.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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. turbine_data_list=[]
  62. for name, group in grouped:
  63. df = self.calculateYawError(group)
  64. df.dropna(inplace=True)
  65. counts = df['density'].value_counts()
  66. count_0 = counts.get(0, 0) # 获取 density 为 0 的数量,如果没有 0 则返回 0
  67. count_1 = counts.get(1, 0) # 获取 density 为 1 的数量,如果没有 1 则返回 0
  68. # print(f"Density 为 0 的数量: {count_0}")
  69. # print(f"Density 为 1 的数量: {count_1}")
  70. df = df[df["density"]>0]
  71. mean_X = np.mean(df["x"])
  72. std_X = np.std(df["x"])
  73. mediann_X= np.median(df["x"])
  74. skewness_X = skew(df["x"])
  75. kurtosis_X = kurtosis(df["x"])
  76. max_X = np.max(df["x"])
  77. min_X = np.min(df["x"])
  78. result={
  79. 'mean_X':[mean_X],
  80. 'std_X': [std_X],
  81. 'mediann_X':[mediann_X],
  82. 'skewness_X':[skewness_X],
  83. 'kurtosis_X':[kurtosis_X],
  84. 'max_X':[max_X],
  85. 'min_X':[min_X]
  86. }
  87. result = pd.DataFrame(result)
  88. print(f'{name[0]}指标:',result)
  89. # 提取数据
  90. turbine_data = {
  91. "engineName": name[0],
  92. "engineCode": name[1],
  93. "title":f'动态偏航误差分析-{name[0]}',
  94. "xData": df["x"].tolist(),
  95. "yData": df["y"].tolist(),
  96. "colorbar": df["density"].tolist(),
  97. "colorbartitle": "密度"
  98. }
  99. turbine_data_list.append(turbine_data)
  100. # 用密度作为颜色绘制散点图,并限制横坐标范围为 -20 到 20
  101. fig = go.Figure()
  102. fig.add_trace(go.Scattergl(
  103. x=df["x"],
  104. y=df["y"],
  105. mode='markers',
  106. marker=dict(
  107. size=3,
  108. opacity=0.7,
  109. color=df["density"],
  110. colorscale=fixed_colorscale,
  111. showscale=True,
  112. )
  113. ))
  114. fig.update_layout(
  115. xaxis_title='对风角度',
  116. yaxis_title='风速',
  117. title=f'动态偏航误差分析-{name[0]}',
  118. xaxis=dict(range=[-20, 20]), # 限制横坐标范围为 -20 到 20
  119. yaxis=dict(range=[0, 25])
  120. )
  121. # Save to file
  122. filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  123. fig.write_image(filePathOfImage, scale=3)
  124. filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  125. fig.write_html(filePathOfHtml)
  126. result_rows.append({
  127. Field_Return_TypeAnalyst: self.typeAnalyst(),
  128. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  129. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  130. Field_CodeOfTurbine: name[1],
  131. Field_Return_FilePath: filePathOfImage,
  132. Field_Return_IsSaveDatabase: False
  133. })
  134. result_rows.append({
  135. Field_Return_TypeAnalyst: self.typeAnalyst(),
  136. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  137. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  138. Field_CodeOfTurbine: name[1],
  139. Field_Return_FilePath: filePathOfHtml,
  140. Field_Return_IsSaveDatabase: True
  141. })
  142. # 确保从 Series 中提取的是具体的值
  143. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  144. if isinstance(engineTypeCode, pd.Series):
  145. engineTypeCode = engineTypeCode.iloc[0]
  146. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  147. if isinstance(engineTypeName, pd.Series):
  148. engineTypeName = engineTypeName.iloc[0]
  149. # 构建最终的JSON对象
  150. json_output = {
  151. "analysisTypeCode": "动态偏航误差",
  152. "engineCode": engineTypeCode,
  153. "engineTypeName": engineTypeName,
  154. "xaixs": "对风角度(度)",
  155. "yaixs": "风速(m/s)",
  156. "data": turbine_data_list
  157. }
  158. # 将JSON对象保存到文件
  159. output_json_path = os.path.join(outputAnalysisDir, "yaw_error_density.json")
  160. with open(output_json_path, 'w', encoding='utf-8') as f:
  161. import json
  162. json.dump(json_output, f, ensure_ascii=False, indent=4)
  163. # 如果需要返回DataFrame,可以包含文件路径
  164. result_rows.append({
  165. Field_Return_TypeAnalyst: self.typeAnalyst(),
  166. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  167. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  168. Field_CodeOfTurbine: Const_Output_Total,
  169. Field_Return_FilePath: output_json_path,
  170. Field_Return_IsSaveDatabase: True
  171. })
  172. result_df = pd.DataFrame(result_rows)
  173. return result_df
  174. def calculateYawError(self, dataFrame: pd.DataFrame):
  175. dataFrame = dataFrame.dropna(
  176. subset=[Field_NameOfTurbine, Field_YawError, Field_ActiverPower,Field_WindSpeed])
  177. filtered_dataFrame = dataFrame[(dataFrame[Field_YawError].abs() <= 30)&(dataFrame[Field_WindSpeed] >= 0)&(dataFrame[Field_WindSpeed] <= 25)]
  178. x=filtered_dataFrame[Field_YawError]
  179. y=filtered_dataFrame[Field_WindSpeed]
  180. # data = np.column_stack((x, y)) # 合并为两列数组
  181. # 使用 binned_statistic_2d 来计算散点的密度分布
  182. binSize_x = 60
  183. binSize_y = 50
  184. counts, x_edges, y_edges, binnumber = binned_statistic_2d(x, y, values=None, statistic='count', bins=[binSize_x, binSize_y])
  185. # 将数据密度转化为颜色值
  186. binX = np.digitize(x, x_edges) - 1
  187. binY = np.digitize(y, y_edges) - 1
  188. # 删除超出范围的下标
  189. validIdx = (binX >= 0) & (binX < binSize_x) & (binY >= 0) & (binY < binSize_y)
  190. # 获取有效下标的密度
  191. density = np.zeros(len(x))
  192. density[validIdx] = counts[binX[validIdx], binY[validIdx]]
  193. # 将结果保存到 result 中
  194. result = {
  195. 'x': x,
  196. 'y': y,
  197. 'density': density
  198. }
  199. # 将 result 转换为 DataFrame
  200. result_df = pd.DataFrame(result)
  201. return result_df