yawErrorDensityAnalyst.py 11 KB

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