yawErrorDensityAnalyst.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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. # 如果返回的是空的,跳过
  68. if df.empty:
  69. print(f"Warning: Turbine {name[0]} has no valid data after screening, skip the analysis.")
  70. continue
  71. counts = df['density'].value_counts()
  72. count_0 = counts.get(0, 0) # 获取 density 为 0 的数量,如果没有 0 则返回 0
  73. count_1 = counts.get(1, 0) # 获取 density 为 1 的数量,如果没有 1 则返回 0
  74. # print(f"Density 为 0 的数量: {count_0}")
  75. # print(f"Density 为 1 的数量: {count_1}")
  76. df = df[df["density"]>0]
  77. mean_X = np.mean(df["x"])
  78. std_X = np.std(df["x"])
  79. mediann_X= np.median(df["x"])
  80. skewness_X = skew(df["x"])
  81. kurtosis_X = kurtosis(df["x"])
  82. max_X = np.max(df["x"])
  83. min_X = np.min(df["x"])
  84. result={
  85. 'mean_X':[mean_X],
  86. 'std_X': [std_X],
  87. 'mediann_X':[mediann_X],
  88. 'skewness_X':[skewness_X],
  89. 'kurtosis_X':[kurtosis_X],
  90. 'max_X':[max_X],
  91. 'min_X':[min_X]
  92. }
  93. result = pd.DataFrame(result)
  94. # 用密度作为颜色绘制散点图,并限制横坐标范围为 -20 到 20
  95. fig = go.Figure()
  96. fig.add_trace(go.Scattergl(
  97. x=df["x"],
  98. y=df["y"],
  99. mode='markers',
  100. marker=dict(
  101. size=3,
  102. opacity=0.7,
  103. color=df["density"],
  104. colorscale=fixed_colorscale,
  105. showscale=True,
  106. )
  107. ))
  108. fig.update_layout(
  109. xaxis_title='对风角度',
  110. yaxis_title='风速',
  111. title=f'动态偏航误差分析-{name[0]}',
  112. xaxis=dict(range=[-20, 20]), # 限制横坐标范围为 -20 到 20
  113. yaxis=dict(range=[0, 25])
  114. )
  115. # 确保从 Series 中提取的是具体的值
  116. engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "")
  117. if isinstance(engineTypeCode, pd.Series):
  118. engineTypeCode = engineTypeCode.iloc[0]
  119. engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "")
  120. if isinstance(engineTypeName, pd.Series):
  121. engineTypeName = engineTypeName.iloc[0]
  122. # 构建最终的JSON对象
  123. json_output = {
  124. "analysisTypeCode": "动态偏航误差",
  125. "engineCode": engineTypeCode,
  126. "engineTypeName": engineTypeName,
  127. "xaixs": "对风角度(°)",
  128. "yaixs": "风速(m/s)",
  129. "data": [{
  130. "engineName": name[0],
  131. "engineCode": name[1],
  132. "title":f'动态偏航误差分析-{name[0]}',
  133. "xData": df["x"].tolist(),
  134. "yData": df["y"].tolist(),
  135. "colorbar": df["density"].tolist(),
  136. "colorbartitle": "密度"
  137. }]
  138. }
  139. # 使用 gaussian_kde 估计数据的概率密度函数
  140. kde = gaussian_kde(df["x"])
  141. x = np.linspace(-30, 30, 1000) # 生成 x 轴数据
  142. pdf_data = kde(x) # 数据的概率密度函数
  143. # 构建最终的JSON对象2
  144. json_output2 = {
  145. "analysisTypeCode": "动态偏航误差",
  146. "engineCode": engineTypeCode,
  147. "engineTypeName": engineTypeName,
  148. "xaixs": "对风角度(°)",
  149. "yaixs": "概率密度函数",
  150. "data": [{
  151. "engineName": name[0],
  152. "engineCode": name[1],
  153. "title":f'概率密度函数-{name[0]}',
  154. "xData": x .tolist(),
  155. "yData": pdf_data.tolist(),
  156. "xrange":[-30,30]
  157. }]
  158. }
  159. # Save to file
  160. # filePathOfImage = os.path.join(outputAnalysisDir, f"{name[0]}.png")
  161. # fig.write_image(filePathOfImage, scale=3)
  162. # filePathOfHtml = os.path.join(outputAnalysisDir, f"{name[0]}.html")
  163. # fig.write_html(filePathOfHtml)
  164. # 将JSON对象保存到文件np.mean(dataFrame[Field_YawError])
  165. output_json_path = os.path.join(outputAnalysisDir, f"{name[0]}.json")
  166. with open(output_json_path, 'w', encoding='utf-8') as f:
  167. import json
  168. json.dump(json_output, f, ensure_ascii=False, indent=4)
  169. # 将JSON对象2保存到文件
  170. output_json_path2 = os.path.join(outputAnalysisDir, f"PDF-{name[0]}.json")
  171. with open(output_json_path2, 'w', encoding='utf-8') as f:
  172. import json
  173. json.dump(json_output2, f, ensure_ascii=False, indent=4)
  174. # 如果需要返回DataFrame,可以包含文件路径
  175. result_rows.append({
  176. Field_Return_TypeAnalyst: self.typeAnalyst(),
  177. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  178. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  179. Field_CodeOfTurbine: name[1],
  180. Field_Return_FilePath: output_json_path,
  181. Field_Return_IsSaveDatabase: True
  182. })
  183. result_rows.append({
  184. Field_Return_TypeAnalyst: self.typeAnalyst(),
  185. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  186. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  187. Field_CodeOfTurbine: name[1],
  188. Field_Return_FilePath: output_json_path2,
  189. Field_Return_IsSaveDatabase: True
  190. })
  191. # result_rows.append({
  192. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  193. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  194. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  195. # Field_CodeOfTurbine: name[1],
  196. # Field_Return_FilePath: filePathOfImage,
  197. # Field_Return_IsSaveDatabase: False
  198. # })
  199. # result_rows.append({
  200. # Field_Return_TypeAnalyst: self.typeAnalyst(),
  201. # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  202. # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  203. # Field_CodeOfTurbine: name[1],
  204. # Field_Return_FilePath: filePathOfHtml,
  205. # Field_Return_IsSaveDatabase: True
  206. # })
  207. result_df = pd.DataFrame(result_rows)
  208. return result_df
  209. def calculateYawError(self, dataFrame: pd.DataFrame):
  210. dataFrame = dataFrame.dropna(
  211. subset=[Field_NameOfTurbine, Field_YawError, Field_ActiverPower,Field_WindSpeed])
  212. filtered_dataFrame = dataFrame[(dataFrame[Field_YawError].abs() <= 30)&(dataFrame[Field_WindSpeed] >= 0)&(dataFrame[Field_WindSpeed] <= 25)]
  213. # 如果筛选结果为空,直接返回 None,不再进行后续计算,防止 binned_statistic_2d 报错
  214. if filtered_dataFrame.empty:
  215. return pd.DataFrame()
  216. x=filtered_dataFrame[Field_YawError]
  217. y=filtered_dataFrame[Field_WindSpeed]
  218. # data = np.column_stack((x, y)) # 合并为两列数组
  219. # 使用 binned_statistic_2d 来计算散点的密度分布
  220. binSize_x = 60
  221. binSize_y = 50
  222. counts, x_edges, y_edges, binnumber = binned_statistic_2d(x, y, values=None, statistic='count', bins=[binSize_x, binSize_y])
  223. # 将数据密度转化为颜色值
  224. binX = np.digitize(x, x_edges) - 1
  225. binY = np.digitize(y, y_edges) - 1
  226. # 删除超出范围的下标
  227. validIdx = (binX >= 0) & (binX < binSize_x) & (binY >= 0) & (binY < binSize_y)
  228. # 获取有效下标的密度
  229. density = np.zeros(len(x))
  230. density[validIdx] = counts[binX[validIdx], binY[validIdx]]
  231. # 将结果保存到 result 中
  232. result = {
  233. 'x': x,
  234. 'y': y,
  235. 'density': density
  236. }
  237. # 将 result 转换为 DataFrame
  238. result_df = pd.DataFrame(result)
  239. return result_df