powerScatter2DAnalyst.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. if len(dataFrame) <= 0:
  26. print("After screening for blade pitch angle less than the configured value, plot power curve scatter points without data")
  27. return
  28. grouped = self.dataFrameContractOfTurbine.groupby(
  29. [Field_PowerFarmCode, Field_MillTypeCode])
  30. for groupByKey, contractPowerCurveOfMillType in grouped:
  31. break
  32. return self.drawOfPowerCurveScatter(dataFrame, outputAnalysisDir, conf, contractPowerCurveOfMillType)
  33. def drawOfPowerCurveScatter(self, dataFrame: pd.DataFrame, outputAnalysisDir, conf: Contract, dataFrameGuaranteePowerCurve: pd.DataFrame):
  34. """
  35. 绘制风速-功率分布图并保存为文件。
  36. 参数:
  37. dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。
  38. csvPowerCurveFilePath (str): 功率曲线文件路径。
  39. outputAnalysisDir (str): 分析输出目录。
  40. confData (ConfBusiness): 配置
  41. """
  42. x_name = '风速'
  43. y_name = '功率'
  44. #机型切入风速 series
  45. cutInWsField = self.turbineModelInfo[Field_CutInWS]
  46. cut_in_ws = cutInWsField.min() - 1 if cutInWsField.notna().any() else 2
  47. # if not dataFrame.empty and Field_CutInWS in dataFrame.columns and dataFrame[Field_CutInWS].notna().any():
  48. # cut_in_ws = dataFrame[Field_CutInWS].min() - 1
  49. # else:
  50. # cut_in_ws = 2
  51. '''
  52. # 按设备名分组数据
  53. grouped = dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
  54. result_rows = []
  55. # 定义固定的颜色映射列表
  56. fixed_colors =[
  57. 'rgb(255, 0, 0)', # 红色
  58. 'rgb(0, 255, 0)', # 绿色
  59. 'rgb(0, 0, 255)', # 蓝色
  60. 'rgb(255, 255, 0)', # 黄色
  61. 'rgb(255, 0, 255)', # 紫色
  62. 'rgb(0, 255, 255)' # 青色
  63. ]
  64. # 遍历每个设备的数据
  65. for name, group in grouped:
  66. fig = make_subplots()
  67. # 提取月份
  68. group['month'] = group['monthIntTime'].apply(lambda x: datetime.fromtimestamp(x).month)
  69. unique_months = group['month'].unique()
  70. # 计算时间跨度
  71. time_span_months = len(unique_months)
  72. if time_span_months >= 6:
  73. # 绘制散点图(时间跨度大于等于6个月)
  74. scatter = go.Scatter(x=group[Field_WindSpeed],
  75. y=group[Field_ActiverPower],
  76. mode='markers',
  77. marker=dict(
  78. color=group['monthIntTime'],
  79. colorscale='Rainbow',
  80. size=3,
  81. opacity=0.7,
  82. colorbar=dict(
  83. tickvals=np.linspace(
  84. group['monthIntTime'].min(), group['monthIntTime'].max(), 6),
  85. ticktext=[datetime.fromtimestamp(ts).strftime(
  86. '%Y-%m') for ts in np.linspace(group['monthIntTime'].min(), group['monthIntTime'].max(), 6)],
  87. thickness=18,
  88. len=1, # 设置颜色条的长度,使其占据整个图的高度
  89. outlinecolor='rgba(255,255,255,0)'
  90. ),
  91. showscale=True
  92. ),
  93. showlegend=False) # 不显示散点图的legend,用colorbar代替
  94. fig.add_trace(scatter)
  95. else:
  96. # 绘制散点图(时间跨度小于6个月)
  97. for i,month in enumerate(unique_months):
  98. month_data = group[group['month'] == month]
  99. # 使用固定的颜色列表
  100. color = fixed_colors[i % len(fixed_colors)]
  101. scatter = go.Scatter(x=month_data[Field_WindSpeed],
  102. y=month_data[Field_ActiverPower],
  103. mode='markers',
  104. marker=dict(
  105. color=color,
  106. size=3,
  107. opacity=0.7
  108. ),
  109. name=f'{datetime.fromtimestamp(month_data["monthIntTime"].iloc[0]).strftime("%Y-%m")}',
  110. showlegend=True)
  111. fig.add_trace(scatter)
  112. '''
  113. # 按设备名分组数据
  114. grouped = dataFrame.groupby([Field_NameOfTurbine, Field_CodeOfTurbine])
  115. result_rows = []
  116. # 定义固定的颜色映射列表
  117. fixed_colors = [
  118. "#3E409C",
  119. "#476CB9",
  120. "#3586BF",
  121. "#4FA4B5",
  122. "#52A3AE",
  123. "#60C5A3",
  124. "#85D0AE",
  125. "#A8DCA2",
  126. "#CFEE9E",
  127. "#E4F39E",
  128. "#EEF9A7",
  129. "#FBFFBE",
  130. "#FDF1A9",
  131. "#FFE286",
  132. "#FFC475",
  133. "#FCB06C",
  134. "#F78F4F",
  135. "#F96F4A",
  136. "#E4574C",
  137. "#CA3756",
  138. "#AF254F"
  139. ]
  140. # 将 fixed_colors 转换为 Plotly 的 colorscale 格式
  141. fixed_colorscale = [
  142. [i / (len(fixed_colors) - 1), color] for i, color in enumerate(fixed_colors)
  143. ]
  144. fixed_colors_points = [
  145. "#F96F4A",
  146. "#FFC475",
  147. "#FBFFBE",
  148. "#85D0AE",
  149. "#3586BF",
  150. "#3E409C"
  151. ]
  152. # 遍历每个设备的数据
  153. for name, group in grouped:
  154. fig = make_subplots()
  155. # 提取月份
  156. group['month'] = group['monthIntTime'].apply(lambda x: datetime.fromtimestamp(x).month)
  157. unique_months = group['month'].unique()
  158. # 计算时间跨度
  159. time_span_months = len(unique_months)
  160. if time_span_months >= 6:
  161. # 绘制散点图(时间跨度大于等于6个月)
  162. scatter = go.Scatter(x=group[Field_WindSpeed],
  163. y=group[Field_ActiverPower],
  164. mode='markers',
  165. marker=dict(
  166. color=group['monthIntTime'],
  167. colorscale=fixed_colorscale, # 使用自定义的 colorscale
  168. size=3,
  169. opacity=0.7,
  170. colorbar=dict(
  171. tickvals=np.linspace(
  172. group['monthIntTime'].min(), group['monthIntTime'].max(), 6),
  173. ticktext=[datetime.fromtimestamp(ts).strftime(
  174. '%Y-%m') for ts in np.linspace(group['monthIntTime'].min(), group['monthIntTime'].max(), 6)],
  175. thickness=18,
  176. len=1, # 设置颜色条的长度,使其占据整个图的高度
  177. outlinecolor='rgba(255,255,255,0)'
  178. ),
  179. showscale=True
  180. ),
  181. showlegend=False) # 不显示散点图的 legend,用 colorbar 代替
  182. fig.add_trace(scatter)
  183. else:
  184. # 绘制散点图(时间跨度小于6个月)
  185. for i, month in enumerate(unique_months):
  186. month_data = group[group['month'] == month]
  187. # 使用固定的颜色列表
  188. color = fixed_colors_points[i % len(fixed_colors_points)]
  189. scatter = go.Scatter(x=month_data[Field_WindSpeed],
  190. y=month_data[Field_ActiverPower],
  191. mode='markers',
  192. marker=dict(
  193. color=color,
  194. size=3,
  195. opacity=0.7
  196. ),
  197. name=f'{datetime.fromtimestamp(month_data["monthIntTime"].iloc[0]).strftime("%Y-%m")}',
  198. showlegend=True)
  199. fig.add_trace(scatter)
  200. # 绘制合同功率曲线
  201. line = go.Scatter(x=dataFrameGuaranteePowerCurve[Field_WindSpeed],
  202. y=dataFrameGuaranteePowerCurve[Field_ActiverPower],
  203. mode='lines+markers',
  204. marker=dict(color='gray', size=7),
  205. name='合同功率曲线')
  206. fig.add_trace(line, secondary_y=False)
  207. # 设置图形布局
  208. fig.update_layout(
  209. title=f'机组: {name[0]}',
  210. xaxis=dict(title=x_name,
  211. range=[cut_in_ws, 25],
  212. tickmode='linear', tick0=0, dtick=1,
  213. tickangle=-45),
  214. yaxis=dict(title=y_name,
  215. dtick=self.axisStepActivePower,
  216. range=[self.axisLowerLimitActivePower,
  217. self.axisUpperLimitActivePower]
  218. ),
  219. legend=dict(yanchor="bottom", y=0, xanchor="right", x=1, font=dict(
  220. size=10), bgcolor='rgba(255,255,255,0)')
  221. )
  222. # 保存图像
  223. pngFileName = f"{name[0]}-scatter.png"
  224. pngFilePath = os.path.join(outputAnalysisDir, pngFileName)
  225. fig.write_image(pngFilePath, scale=3)
  226. # 保存HTML
  227. htmlFileName = f"{name[0]}-scatter.html"
  228. htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName)
  229. fig.write_html(htmlFilePath)
  230. result_rows.append({
  231. Field_Return_TypeAnalyst: self.typeAnalyst(),
  232. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  233. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  234. Field_CodeOfTurbine: name[1],
  235. Field_Return_FilePath: pngFilePath,
  236. Field_Return_IsSaveDatabase: False
  237. })
  238. result_rows.append({
  239. Field_Return_TypeAnalyst: self.typeAnalyst(),
  240. Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID,
  241. Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum,
  242. Field_CodeOfTurbine: name[1],
  243. Field_Return_FilePath: htmlFilePath,
  244. Field_Return_IsSaveDatabase: True
  245. })
  246. result_df = pd.DataFrame(result_rows)
  247. return result_df