temperatureLargeComponentsAnalyst.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. import os
  2. import pandas as pd
  3. from matplotlib.ticker import MultipleLocator
  4. import numpy as np
  5. import pandas as pd
  6. import plotly.graph_objects as go
  7. import matplotlib.pyplot as plt
  8. import matplotlib.ticker as ticker
  9. import seaborn as sns
  10. from behavior.analyst import Analyst
  11. from utils.directoryUtil import DirectoryUtil as dir
  12. from algorithmContract.confBusiness import *
  13. class Generator:
  14. def __init__(self) -> None:
  15. self.fieldTemperatorOfDEBearing = None
  16. self.fieldTemperatorOfNDEBearing = None
  17. class TemperatureLargeComponentsAnalyst(Analyst):
  18. """
  19. 风电机组大部件温升分析
  20. """
  21. fieldPowerFloor = 'power_floor'
  22. def typeAnalyst(self):
  23. return "temperature_large_components"
  24. def getUseColumns(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
  25. # Convert the string list of temperature columns into a list
  26. temperature_cols = self.getLargeComponentTemperatureColumns(confData)
  27. # 获取非全为空的列名
  28. non_empty_cols = self.getNoneEmptyFields(dataFrame, temperature_cols)
  29. useCols = []
  30. useCols.append(confData.field_turbine_time)
  31. useCols.append(confData.field_power)
  32. if not self.common.isNone(confData.field_env_temp) and confData.field_env_temp in dataFrame.columns:
  33. useCols.append(confData.field_env_temp)
  34. if not self.common.isNone(confData.field_nacelle_temp) and confData.field_nacelle_temp in dataFrame.columns:
  35. useCols.append(confData.field_nacelle_temp)
  36. useCols.extend(non_empty_cols)
  37. return useCols
  38. def getLargeComponentTemperatureColumns(self, confData: ConfBusiness):
  39. if self.common.isNone(confData.field_temperature_large_components):
  40. return []
  41. temperature_cols = confData.field_temperature_large_components.split(
  42. ',')
  43. return temperature_cols
  44. # def filterCustomForTurbine(self, dataFrame: pd.DataFrame, confData: ConfBusiness):
  45. # useCols = self.getUseColumns(dataFrame,confData)
  46. # # 清洗数据
  47. # dataFrameCustome = dataFrame[useCols].copy()
  48. # dataFrameCustome = dataFrameCustome.dropna(axis=1, how='all')
  49. # dataFrameCustome = dataFrameCustome.dropna(axis=0, subset=useCols)
  50. # return dataFrameCustome
  51. # def turbineAnalysis(self,
  52. # dataFrame,
  53. # outputAnalysisDir,
  54. # outputFilePath,
  55. # confData: ConfBusiness,
  56. # turbineName):
  57. # self.temp_power(dataFrame, outputFilePath, confData)
  58. def getNoneEmptyFields(self, dataFrame, temperatureFields):
  59. # 使用set和列表推导式来获取在DataFrame中存在的字段
  60. existing_fields = [
  61. field for field in temperatureFields if field in dataFrame.columns]
  62. # 检查指定列中非全为空的列
  63. non_empty_columns = dataFrame[existing_fields].apply(
  64. lambda x: x.notnull().any(), axis=0)
  65. # 获取非全为空的列名
  66. noneEmptyFields = non_empty_columns[non_empty_columns].index.tolist()
  67. return noneEmptyFields
  68. def dataReprocess(self, dataFrame: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
  69. # 获取非全为空的列名
  70. non_empty_cols = self.getUseColumns(dataFrame, confData)
  71. dataFrame = dataFrame.dropna(subset=non_empty_cols)
  72. # Calculate 'power_floor'
  73. dataFrame[self.fieldPowerFloor] = (
  74. dataFrame[confData.field_power] / 10).astype(int) * 10
  75. # Initialize an empty DataFrame for aggregation
  76. # agg_dict = {col: 'mean' for col in non_empty_cols}
  77. agg_dict = {col: 'median' for col in non_empty_cols}
  78. # Group by 'power_floor' and aggregate
  79. grouped = dataFrame.groupby(
  80. [self.fieldPowerFloor, Field_NameOfTurbine]).agg(agg_dict).reset_index()
  81. # Sort by 'power_floor'
  82. grouped.sort_values(
  83. [self.fieldPowerFloor, Field_NameOfTurbine], inplace=True)
  84. return grouped
  85. def turbinesAnalysis(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
  86. # self.plot_temperature_distribution(dataFrameMerge,
  87. # outputAnalysisDir, confData, confData.field_temperature_large_components)
  88. dataFrame = self.dataReprocess(
  89. dataFrameMerge, outputAnalysisDir, confData)
  90. self.drawTemperatureGraph(dataFrame, outputAnalysisDir, confData)
  91. def drawTemperatureGraph(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness):
  92. """
  93. 大部件温度传感器分析
  94. """
  95. y_name = 'temperature'
  96. outputDir = os.path.join(outputAnalysisDir, "GeneratorTemperature")
  97. dir.create_directory(outputDir)
  98. columns = confData.field_temperature_large_components.split(',')
  99. # 按设备名分组数据
  100. grouped = dataFrameMerge.groupby(Field_NameOfTurbine)
  101. # Create output directories if they don't exist
  102. for column in columns:
  103. if not column in dataFrameMerge.columns:
  104. continue
  105. sns.set_palette('deep')
  106. outputPath = os.path.join(outputAnalysisDir, column)
  107. dir.create_directory(outputPath)
  108. # 绘制当前温度测点的,所有机组折线图
  109. fig, ax = plt.subplots()
  110. ax = sns.lineplot(x=self.fieldPowerFloor, y=column, data=dataFrameMerge,
  111. hue=Field_NameOfTurbine)
  112. # 创建每100个单位一个刻度的定位器
  113. yloc = MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
  114. confData.graphSets["activePower"]) and not self.common.isNone(
  115. confData.graphSets["activePower"]["step"]) else 250)
  116. ax.xaxis.set_major_locator(yloc) # 将定位器应用到y轴上
  117. ax.set_xlim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
  118. confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
  119. ax.yaxis.set_major_locator(ticker.MultipleLocator(20))
  120. ax.set_ylim(0, 100)
  121. ax.set_xlabel(self.fieldPowerFloor)
  122. ax.set_ylabel(y_name)
  123. ax.set_title('Temperature-Distribute')
  124. # 获取线对象的句柄和标签
  125. lines, labels = ax.get_legend_handles_labels()
  126. # 排序标签(例如,按照字母顺序)
  127. sorted_labels = sorted(labels)
  128. sorted_lines = [l for l, lbl in zip(
  129. lines, labels) if lbl in sorted_labels]
  130. # 创建新的图例
  131. ax.legend(sorted_lines, sorted_labels, bbox_to_anchor=(
  132. 1.02, 0.5), loc='center left', ncol=2, borderaxespad=0.)
  133. # plt.legend(bbox_to_anchor=(1.02, 0.5),
  134. # loc='center left', ncol=2, borderaxespad=0.)
  135. plt.savefig(os.path.join(outputPath, "{}.png".format(
  136. column)), bbox_inches='tight', dpi=120)
  137. plt.close()
  138. for name, group in grouped:
  139. # Write to CSV
  140. # csvFileOfTurbine=os.path.join(outputAnalysisDir,f'{name}{CSVSuffix}')
  141. # group.to_csv(csvFileOfTurbine,index=False)
  142. color = ["lightgrey"] * \
  143. len(dataFrameMerge[Field_NameOfTurbine].unique())
  144. fig, ax = plt.subplots()
  145. ax = sns.lineplot(x=self.fieldPowerFloor, y=column, data=dataFrameMerge, hue=Field_NameOfTurbine,
  146. palette=sns.set_palette(color), legend=False)
  147. ax = sns.lineplot(x=self.fieldPowerFloor, y=column, data=group,
  148. color='darkblue', legend=False)
  149. ax.set_title('turbine_name={}'.format(name))
  150. ax.yaxis.set_major_locator(ticker.MultipleLocator(20))
  151. ax.set_ylim(0, 100)
  152. ax.set_ylabel(y_name)
  153. ax.xaxis.set_major_locator(ticker.MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
  154. confData.graphSets["activePower"]) and not self.common.isNone(
  155. confData.graphSets["activePower"]["step"]) else 250))
  156. ax.set_xlim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
  157. confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
  158. ax.set_xlabel(self.fieldPowerFloor)
  159. plt.savefig(os.path.join(outputPath, "{}.png".format(
  160. name)), bbox_inches='tight', dpi=120)
  161. plt.close()
  162. # 绘制每台机组发电机的,驱动轴承温度、非驱动轴承温度、发电机轴承温度BIAS、发电机轴承温度和机舱温度BIAS 均与有功功率的折线图
  163. dictConf = self.getGeneratorTemperatureConf(confData)
  164. if not self.common.isNone(dictConf):
  165. self.drawGeneratorTemperature(
  166. group, confData, dictConf["yAxisDE"], dictConf["yAxisNDE"], dictConf["diffTemperature"], self.fieldPowerFloor, name, outputDir)
  167. def plot_temperature_distribution(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, confData: ConfBusiness, field_temperature_large_componts, encoding='utf-8'):
  168. """
  169. Generates Cp distribution plots for turbines in a wind farm.
  170. Parameters:
  171. - csvFileDirOfCp: str, path to the directory containing input CSV files.
  172. - farm_name: str, name of the wind farm.
  173. - encoding: str, encoding of the input CSV files. Defaults to 'utf-8'.
  174. """
  175. field_Name_Turbine = "turbine_name"
  176. x_name = 'power_floor'
  177. y_name = 'temperature'
  178. outputDir = os.path.join(outputAnalysisDir, "GeneratorTemperature")
  179. dir.create_directory(outputDir)
  180. sns.set_palette('deep')
  181. columns = field_temperature_large_componts.split(',')
  182. # Create output directories if they don't exist
  183. for column in columns:
  184. type_name = '{}'.format(column)
  185. output_path = os.path.join(outputAnalysisDir, type_name)
  186. os.makedirs(output_path, exist_ok=True)
  187. print("current column {}".format(column))
  188. # Initialize DataFrame to store concatenated data
  189. res = pd.DataFrame()
  190. # Iterate over files in the input path
  191. for root, dir_names, file_names in dir.list_directory(outputAnalysisDir):
  192. for file_name in file_names:
  193. if not file_name.endswith(CSVSuffix):
  194. continue
  195. print(os.path.join(root, file_name))
  196. frame = pd.read_csv(os.path.join(
  197. root, file_name), encoding=encoding)
  198. if column not in frame.columns:
  199. continue
  200. # 获取输出文件名(不含split_way之后的部分)
  201. turbineName = file_name.split(CSVSuffix)[0]
  202. # 添加设备名作为新列
  203. frame[field_Name_Turbine] = confData.add_W_if_starts_with_digit(
  204. turbineName)
  205. dictConf = self.getGeneratorTemperatureConf(confData)
  206. if not self.common.isNone(dictConf):
  207. self.drawGeneratorTemperature(
  208. frame, dictConf["yAxisDE"], dictConf["yAxisNDE"], dictConf["diffTemperature"], x_name, turbineName, outputDir)
  209. res = pd.concat(
  210. [res, frame.loc[:, [field_Name_Turbine, x_name, column]]], axis=0)
  211. # Reset index and plot
  212. ress = res.reset_index()
  213. fig, ax2 = plt.subplots()
  214. ax2 = sns.lineplot(x=x_name, y=column, data=ress,
  215. hue=field_Name_Turbine)
  216. # ax2.set_xlim(-150, 2100)
  217. ax2.set_xlabel(x_name)
  218. ax2.set_ylabel(y_name)
  219. ax2.set_title('Temperature-Distribute')
  220. plt.legend(bbox_to_anchor=(1.02, 0.5),
  221. loc='center left', ncol=2, borderaxespad=0.)
  222. plt.savefig(os.path.join(output_path, "{}.png".format(
  223. column)), bbox_inches='tight', dpi=120)
  224. plt.close()
  225. # Plot individual device lines
  226. grouped = ress.groupby(field_Name_Turbine)
  227. for name, group in grouped:
  228. color = ["lightgrey"] * len(ress[field_Name_Turbine].unique())
  229. fig, ax = plt.subplots()
  230. ax = sns.lineplot(x=x_name, y=column, data=ress, hue=field_Name_Turbine,
  231. palette=sns.set_palette(color), legend=False)
  232. ax = sns.lineplot(x=x_name, y=column, data=group,
  233. color='darkblue', legend=False)
  234. ax.set_xlabel(x_name)
  235. ax.set_ylabel(y_name)
  236. ax.set_title('turbine_name={}'.format(name))
  237. # ax.set_xlim(-150, 2100)
  238. plt.savefig(os.path.join(output_path, "{}.png".format(
  239. name)), bbox_inches='tight', dpi=120)
  240. plt.close()
  241. def getGeneratorTemperatureConf(self, confData: ConfBusiness):
  242. if self.common.isNone(confData.temperature_Generator) or self.common.isNone(confData.temperature_Generator["yAxisDE"]) or self.common.isNone(confData.temperature_Generator["yAxisNDE"]):
  243. return None
  244. return confData.temperature_Generator
  245. def drawGeneratorTemperature(self, dataFrame: pd.DataFrame, confData: ConfBusiness, yAxisDE, yAxisNDE, diffTemperature, xAxis, turbineName, outputDir):
  246. # 发电机驱动轴承温度 和 发电机非驱动轴承 温差
  247. fieldBIAS_DE_NDE = 'BIAS_DE-NDE'
  248. fieldBIAS_DE = 'BIAS_DE'
  249. fieldBIAS_NDE = 'BIAS_NDE'
  250. # 绘制双y轴折线图
  251. fig, ax1 = plt.subplots()
  252. # 绘制低速轴承温度和高速轴承温度
  253. ax1.plot(dataFrame[xAxis], dataFrame[yAxisDE],
  254. label='DEBearingTemperature', color='blue')
  255. # 计算低速轴承温度和高速轴承温度的差值
  256. dataFrame[fieldBIAS_DE] = dataFrame[yAxisDE] - \
  257. dataFrame[diffTemperature]
  258. # 绘制温度差值
  259. ax1.plot(dataFrame[xAxis], dataFrame[fieldBIAS_DE],
  260. label=fieldBIAS_DE, color='blue', linestyle=':')
  261. ax1.plot(dataFrame[xAxis], dataFrame[yAxisNDE],
  262. label='NDEBearingTemperature', color='green')
  263. # 计算低速轴承温度和高速轴承温度的差值
  264. dataFrame[fieldBIAS_NDE] = dataFrame[yAxisNDE] - \
  265. dataFrame[diffTemperature]
  266. # 绘制温度差值
  267. ax1.plot(dataFrame[xAxis], dataFrame[fieldBIAS_NDE],
  268. label=fieldBIAS_NDE, color='green', linestyle=':')
  269. ax1.plot(dataFrame[xAxis], dataFrame[diffTemperature],
  270. label='NacelleTemperature', color='orange')
  271. # # 创建第二个y轴
  272. # ax2 = ax1.twinx()
  273. # 计算低速轴承温度和高速轴承温度的差值
  274. dataFrame[fieldBIAS_DE_NDE] = dataFrame[yAxisDE] - dataFrame[yAxisNDE]
  275. # 绘制温度差值
  276. ax1.plot(dataFrame[xAxis], dataFrame[fieldBIAS_DE_NDE],
  277. label=fieldBIAS_DE_NDE, color='black', linestyle='--')
  278. # 设置y2轴的上限和下限
  279. # ax2.set_ylim(-5, 5)
  280. plt.axhline(y=5, ls=":", c="red") # 添加水平直线
  281. plt.axhline(y=-5, ls=":", c="red") # 添加水平直线
  282. # 第一个图例放在右上角
  283. ax1.legend(title='Temperature & BIAS', bbox_to_anchor=(
  284. 1.3, 0.5), loc='center', borderaxespad=0.)
  285. # # 第二个图例放在第一个图例稍下的位置
  286. # ax1.legend(title='BIAS', bbox_to_anchor=(1.5, 1), loc='upper right', borderaxespad=0.)
  287. # 设置x轴和y轴标签
  288. ax1.set_xlabel("power")
  289. ax1.set_ylabel('Bearing Temperature & BIAS')
  290. # ax2.set_ylabel('Temperature BIAS')
  291. ax1.xaxis.set_major_locator(ticker.MultipleLocator(confData.graphSets["activePower"]["step"] if not self.common.isNone(
  292. confData.graphSets["activePower"]) and not self.common.isNone(
  293. confData.graphSets["activePower"]["step"]) else 250))
  294. ax1.set_xlim(confData.graphSets["activePower"]["min"] if not self.common.isNone(
  295. confData.graphSets["activePower"]["min"]) else 0, confData.graphSets["activePower"]["max"] if not self.common.isNone(confData.graphSets["activePower"]["max"]) else confData.rated_power*1.2)
  296. ax1.yaxis.set_major_locator(ticker.MultipleLocator(confData.graphSets["generatorTemperature"]["step"] if not self.common.isNone(
  297. confData.graphSets["generatorTemperature"]) and not self.common.isNone(
  298. confData.graphSets["generatorTemperature"]["step"]) else 10))
  299. ax1.set_ylim(confData.graphSets["generatorTemperature"]["min"] if not self.common.isNone(
  300. confData.graphSets["generatorTemperature"]["min"]) else -20, confData.graphSets["generatorTemperature"]["max"] if not self.common.isNone(confData.graphSets["generatorTemperature"]["max"]) else 100)
  301. plt.title(f'Generator Temperture BIAS={turbineName}')
  302. plt.savefig(os.path.join(outputDir, "{}.png".format(
  303. turbineName)), bbox_inches='tight', dpi=120)