123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- 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, "total_more_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_MillTypeCode: 'total_less_25',
- 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, "total_less_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_MillTypeCode: 'total_less_25',
- 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()
- """
|