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) dataFrameOfTurbines = self.userDataFrame( dictionary, conf.dataContract.configAnalysis, self) turbrineInfos = self.common.getTurbineInfos( conf.dataContract.dataFilter.powerFarmID, turbineCodes, self.turbineInfo) groupedOfTurbineModel = turbrineInfos.groupby(Field_MillTypeCode) returnDatas = [] for turbineModelCode, group in groupedOfTurbineModel: currTurbineCodes = group[Field_CodeOfTurbine].unique().tolist() currTurbineModeInfo = self.common.getTurbineModelByCode( turbineModelCode, self.turbineModelInfo) currDataFrameOfTurbines = dataFrameOfTurbines[dataFrameOfTurbines[Field_CodeOfTurbine].isin( currTurbineCodes)] returnData= self.draw(currDataFrameOfTurbines, outputAnalysisDir, conf,currTurbineModeInfo) returnDatas.append(returnData) returnResult = pd.concat(returnDatas, ignore_index=True) return returnResult 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摄氏度)-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5}, boxmode='group') 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) 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摄氏度)-{turbineModelInfo[Field_MachineTypeCode]}', 'x': 0.5}, boxmode='group') # 保存图像 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) 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() """