import os import pandas as pd import plotly.graph_objects as go from algorithmContract.confBusiness import * from algorithmContract.contract import Contract from behavior.analystWithGoodBadPoint import AnalystWithGoodBadPoint from plotly.subplots import make_subplots class RatedPowerWindSpeedAnalyst(AnalystWithGoodBadPoint): """ 风电机组额定功率风速分析。 秒级scada数据运算太慢,建议使用分钟级scada数据 """ def typeAnalyst(self): return "rated_power_windspeed" def turbinesAnalysis(self, outputAnalysisDir, conf: Contract, turbineCodes): dictionary=self.processTurbineData(turbineCodes,conf,[Field_DeviceCode,Field_Time,Field_EnvTemp,Field_WindSpeed,Field_ActiverPower]) dataFrameMerge=self.userDataFrame(dictionary,conf.dataContract.configAnalysis,self) turbineInfos = self.common.getTurbineInfos(conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo) return self.draw(dataFrameMerge, outputAnalysisDir, conf,turbineInfos) def draw(self, dataFrameMerge: pd.DataFrame, outputAnalysisDir, conf: Contract,turbineModelInfo: pd.Series): """ 绘制并保存额定满发风速功率分布图,根据环境温度是否大于等于25℃。 参数: dataFrameMerge (pd.DataFrame): 包含数据的DataFrame,需要包含设备名、风速和功率列。 outputAnalysisDir (str): 分析输出目录。 confData (ConfBusiness): 配置 """ # 检查所需列是否存在 required_columns = {Field_EnvTemp, Field_WindSpeed, Field_ActiverPower} if not required_columns.issubset(dataFrameMerge.columns): raise ValueError(f"DataFrame缺少必要的列。需要的列有: {required_columns}") y_name = '功率' upLimitOfPower = self.turbineInfo[Field_RatedPower].max() * 1.2 lowLimitOfPower = self.turbineInfo[Field_RatedPower].max()*0.9 field_RatedWindSpeed = self.turbineModelInfo[Field_RatedWindSpeed].max() # 根据环境温度筛选数据 over_temp = dataFrameMerge[(dataFrameMerge[Field_EnvTemp] >= 25) & ( dataFrameMerge[Field_WindSpeed] >= field_RatedWindSpeed) & (dataFrameMerge[Field_ActiverPower] >= lowLimitOfPower)].sort_values(by=Field_NameOfTurbine) below_temp = dataFrameMerge[(dataFrameMerge[Field_EnvTemp] < 25) & ( dataFrameMerge[Field_WindSpeed] >= field_RatedWindSpeed) & (dataFrameMerge[Field_ActiverPower] >= lowLimitOfPower)].sort_values(by=Field_NameOfTurbine) # 绘制环境温度大于等于25℃的功率分布图 fig = make_subplots(rows=1, cols=1) fig.add_trace(go.Box(y=over_temp[Field_ActiverPower], x=over_temp[Field_NameOfTurbine], # name='Ambient Temp >= 25°C', boxpoints='outliers', # box line color line=dict(color='black', width=1), # quartilemethod='exclusive', fillcolor='dodgerblue', showlegend=False, marker=dict(color='rgba(0, 0, 0, 0)', size=0.1)), row=1, col=1) # Calculate medians and plot them as a line for visibility medians = over_temp.groupby(Field_NameOfTurbine)[ Field_ActiverPower].median() median_line = go.Scatter( x=medians.index, y=medians.values, mode='markers', marker=dict(symbol='line-ew-open', color='red', size=12), showlegend=False ) fig.add_trace(median_line) # 更新布局 fig.update_yaxes(title_text=y_name, row=1, col=1, range=[ lowLimitOfPower, upLimitOfPower], tickfont=dict(size=10)) fig.update_xaxes(title_text='机组', type='category', tickangle=-45, tickfont=dict(size=10)) fig.update_layout(title={ 'text': f'额定功率分布(环境温度>=25摄氏度)', 'x': 0.5}, boxmode='group') # 确保从 Series 中提取的是具体的值 engineTypeCode = turbineModelInfo.get(Field_MillTypeCode, "") if isinstance(engineTypeCode, pd.Series): engineTypeCode = engineTypeCode.iloc[0] engineTypeName = turbineModelInfo.get(Field_MachineTypeCode, "") if isinstance(engineTypeName, pd.Series): engineTypeName = engineTypeName.iloc[0] # 构建最终的JSON对象 json_output = { "analysisTypeCode": "额定功率和风速分析", "engineCode": engineTypeCode, "engineTypeName": engineTypeName, "xaixs": "机组", "yaixs": "功率(kw)", "data": [{ "title":f'额定功率分布(环境温度>=25摄氏度)', "xData": over_temp[Field_NameOfTurbine].tolist(), "yData": over_temp[Field_ActiverPower].tolist(), "linecolor":'black', "linewidth":1, "fillcolor":'dodgerblue' }] } result_rows = [] # 保存图像 pngFileName = "额定满发风速功率分布(10min)(环境温度大于25度).png" pngFilePath = os.path.join(outputAnalysisDir, pngFileName) fig.write_image(pngFilePath, scale=3) # # 保存HTML # htmlFileName = "额定满发风速功率分布(10min)(环境温度大于25度).html" # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName) # fig.write_html(htmlFilePath) # 保存Json # 将JSON对象保存到文件 output_json_path = os.path.join(outputAnalysisDir, "额定满发风速功率分布(10min)(环境温度大于25度).json") with open(output_json_path, 'w', encoding='utf-8') as f: import json json.dump(json_output, f, ensure_ascii=False, indent=4) # 如果需要返回DataFrame,可以包含文件路径 result_rows.append({ Field_Return_TypeAnalyst: self.typeAnalyst(), Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, Field_CodeOfTurbine: 'total', Field_Return_FilePath: output_json_path, Field_Return_IsSaveDatabase: True }) result_rows.append({ Field_Return_TypeAnalyst: self.typeAnalyst(), Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, Field_CodeOfTurbine: 'total', Field_Return_FilePath: pngFilePath, Field_Return_IsSaveDatabase: False }) # result_rows.append({ # Field_Return_TypeAnalyst: self.typeAnalyst(), # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, # Field_CodeOfTurbine: 'total', # Field_Return_FilePath: htmlFilePath, # Field_Return_IsSaveDatabase: True # }) # 绘制环境温度小于25℃的功率分布图 fig = make_subplots(rows=1, cols=1) fig.add_trace(go.Box(y=below_temp[Field_ActiverPower], x=below_temp[Field_NameOfTurbine], # name='Ambient Temp < 25°C', boxpoints='outliers', # box line color line=dict(color='black', width=1), # quartilemethod='exclusive', fillcolor='dodgerblue', showlegend=False, marker=dict(color='rgba(0, 0, 0, 0)', size=0.1)), row=1, col=1) # Calculate medians and plot them as a line for visibility medians = below_temp.groupby(Field_NameOfTurbine)[ Field_ActiverPower].median() median_line = go.Scatter( x=medians.index, y=medians.values, mode='markers', marker=dict(symbol='line-ew-open', color='red', size=10), showlegend=False ) fig.add_trace(median_line) # 更新布局 fig.update_yaxes(title_text=y_name, row=1, col=1, range=[ lowLimitOfPower, upLimitOfPower], tickfont=dict(size=10)) fig.update_xaxes(title_text='机组', type='category', tickangle=-45, tickfont=dict(size=10)) fig.update_layout(title={ 'text': f'额定功率分布(环境温度<25摄氏度)', 'x': 0.5}, boxmode='group') # 构建最终的JSON对象2 json_output2 = { "analysisTypeCode": "额定功率和风速分析", "engineCode": engineTypeCode, "engineTypeName": engineTypeName, "xaixs": "机组", "yaixs": "功率(kw)", "data": [{ "title":f'额定功率分布(环境温度<25摄氏度)', "xData": below_temp[Field_NameOfTurbine].tolist(), "yData": below_temp[Field_ActiverPower].tolist(), "linecolor":'black', "linewidth":1, "fillcolor":'dodgerblue' }] } # 保存图像 pngFileName = "额定满发风速功率分布(10min)(环境温度小于25度).png" pngFilePath = os.path.join(outputAnalysisDir, pngFileName) fig.write_image(pngFilePath, scale=3) # # 保存HTML # htmlFileName = "额定满发风速功率分布(10min)(环境温度小于25度).html" # htmlFilePath = os.path.join(outputAnalysisDir, htmlFileName) # fig.write_html(htmlFilePath) # 保存Json # 将JSON对象保存到文件 output_json_path2 = os.path.join(outputAnalysisDir, "额定满发风速功率分布(10min)(环境温度小于25度).json") with open(output_json_path2, 'w', encoding='utf-8') as f: import json json.dump(json_output2, f, ensure_ascii=False, indent=4) # 如果需要返回DataFrame,可以包含文件路径 result_rows.append({ Field_Return_TypeAnalyst: self.typeAnalyst(), Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, Field_CodeOfTurbine: 'total', Field_Return_FilePath: output_json_path2, Field_Return_IsSaveDatabase: True }) result_rows.append({ Field_Return_TypeAnalyst: self.typeAnalyst(), Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, Field_CodeOfTurbine: 'total', Field_Return_FilePath: pngFilePath, Field_Return_IsSaveDatabase: False }) # result_rows.append({ # Field_Return_TypeAnalyst: self.typeAnalyst(), # Field_PowerFarmCode: conf.dataContract.dataFilter.powerFarmID, # Field_Return_BatchCode: conf.dataContract.dataFilter.dataBatchNum, # Field_CodeOfTurbine: 'total', # Field_Return_FilePath: htmlFilePath, # Field_Return_IsSaveDatabase: True # }) result_df = pd.DataFrame(result_rows) return result_df """ # 绘制环境温度大于等于25℃的功率分布图 fig, ax = plt.subplots() sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=over_temp, fliersize=0, ax=ax, medianprops={'linestyle': '-', 'color': 'red'}, boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'}) ax.yaxis.set_major_locator(ticker.MultipleLocator(100)) ax.set_ylim(lowLimitOfPower, upLimitOfPower) ax.set_ylabel(y_name) ax.set_title( 'rated wind speed and power distribute(10min)(ambient temperature>=25℃)') ax.grid(True) plt.xticks(rotation=45) # 旋转45度 plt.savefig(os.path.join(outputAnalysisDir, "额定满发风速功率分布(10min)(环境温度大于25度).png"), bbox_inches='tight', dpi=120) plt.close() # 绘制环境温度小于25℃的功率分布图 fig, ax = plt.subplots() sns.boxplot(y=confData.field_power, x=Field_NameOfTurbine, data=below_temp, fliersize=0, ax=ax, medianprops={'linestyle': '-', 'color': 'red'}, boxprops={'color': 'dodgerblue', 'facecolor': 'dodgerblue'}) ax.yaxis.set_major_locator(ticker.MultipleLocator(100)) ax.set_ylim(lowLimitOfPower, upLimitOfPower) ax.set_ylabel(y_name) ax.set_title( 'rated wind speed and power distribute(10min)(ambient temperature<25℃)') ax.grid(True) plt.xticks(rotation=45) # 旋转45度 plt.savefig(os.path.join(outputAnalysisDir, "额定满发风速功率分布(10min)(环境温度小于25度).png"), bbox_inches='tight', dpi=120) plt.close() """