Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -84,19 +84,17 @@ def simplify_model_names_in_index(df):
|
|
84 |
|
85 |
return df
|
86 |
|
87 |
-
|
88 |
-
|
89 |
-
github_token = st.secrets["GitHub_Token_KUL_Margarida"]
|
90 |
|
91 |
if github_token:
|
92 |
-
forecast_dict = load_forecast(github_token
|
93 |
|
94 |
-
historical_forecast
|
95 |
|
96 |
-
Data_BE
|
97 |
-
Data_FR
|
98 |
-
Data_NL
|
99 |
-
Data_DE
|
100 |
|
101 |
Data_BE=convert_European_time(Data_BE, 'Europe/Brussels')
|
102 |
Data_FR=convert_European_time(Data_FR, 'Europe/Paris')
|
@@ -107,29 +105,6 @@ if github_token:
|
|
107 |
else:
|
108 |
print("Please enter your GitHub Personal Access Token to proceed.")
|
109 |
|
110 |
-
def conformal_predictions(data, target, my_forecast):
|
111 |
-
data['Residuals'] = data[my_forecast] - data[actual_col]
|
112 |
-
data['Hour'] = data.index.hour
|
113 |
-
|
114 |
-
min_date = data.index.min()
|
115 |
-
for date in data.index.normalize().unique():
|
116 |
-
if date >= min_date + pd.DateOffset(days=30):
|
117 |
-
start_date = date - pd.DateOffset(days=30)
|
118 |
-
end_date = date
|
119 |
-
calculation_window = data[start_date:end_date-pd.DateOffset(hours=1)]
|
120 |
-
quantiles = calculation_window.groupby('Hour')['Residuals'].quantile(0.8)
|
121 |
-
# Use .loc to safely access and modify data
|
122 |
-
if date in data.index:
|
123 |
-
current_day_data = data.loc[date.strftime('%Y-%m-%d')]
|
124 |
-
for hour in current_day_data['Hour'].unique():
|
125 |
-
if hour in quantiles.index:
|
126 |
-
hour_quantile = quantiles[hour]
|
127 |
-
idx = (data.index.normalize() == date) & (data.Hour == hour)
|
128 |
-
data.loc[idx, 'Quantile_80'] = hour_quantile
|
129 |
-
data.loc[idx, 'Lower_Interval'] = data.loc[idx, my_forecast] - hour_quantile
|
130 |
-
data.loc[idx, 'Upper_Interval'] = data.loc[idx, my_forecast] + hour_quantile
|
131 |
-
#data.reset_index(inplace=True)
|
132 |
-
return data
|
133 |
|
134 |
# Main layout of the app
|
135 |
col1, col2 = st.columns([5, 2]) # Adjust the ratio to better fit your layout needs
|
@@ -151,6 +126,7 @@ upper_space.markdown("""
|
|
151 |
""", unsafe_allow_html=True)
|
152 |
|
153 |
|
|
|
154 |
countries = {
|
155 |
'Netherlands': 'NL',
|
156 |
'Germany': 'DE',
|
@@ -242,9 +218,7 @@ if section == 'Data':
|
|
242 |
|
243 |
st.write('The table below presents the data quality metrics for various energy-related datasets, focusing on the percentage of missing values and the occurrence of extreme or nonsensical values for the selected country.')
|
244 |
data_quality=data.iloc[:-28]
|
245 |
-
|
246 |
-
data_quality=data.iloc[:-5*24]
|
247 |
-
print(data_quality.tail(48))
|
248 |
# Report % of missing values
|
249 |
missing_values = data_quality[forecast_columns].isna().mean() * 100
|
250 |
missing_values = missing_values.round(2)
|
@@ -320,61 +294,16 @@ elif section == 'Forecasts':
|
|
320 |
'Load_entsoe','Load_forecast_entsoe','Wind_onshore_entsoe','Wind_onshore_forecast_entsoe','Wind_offshore_entsoe','Wind_offshore_forecast_entsoe','Solar_entsoe','Solar_forecast_entsoe']
|
321 |
num_per_var=2
|
322 |
|
323 |
-
|
324 |
-
operation_forecast_load=forecast_dict['Predictions_10h.csv'].filter(like='Load_', axis=1)
|
325 |
-
operation_forecast_res=forecast_dict['Predictions_17h.csv'].filter(regex='^(?!Load_)')
|
326 |
-
operation_forecast_load.columns = [col.replace('_entsoe.', '_').replace('Naive.7D', 'WeeklyNaiveSeasonal') for col in operation_forecast_load.columns]
|
327 |
-
operation_forecast_res.columns = [col.replace('_entsoe.', '_').replace('Naive.1D', 'DailyNaiveSeasonal') for col in operation_forecast_res.columns]
|
328 |
-
Historical_and_Load=add_feature(operation_forecast_load, historical_forecast)
|
329 |
-
Historical_and_operational=add_feature(operation_forecast_res, Historical_and_Load)
|
330 |
-
|
331 |
-
best_forecast = Historical_and_operational.filter(like='Forecast_elia', axis=1)
|
332 |
-
df_combined = Historical_and_operational.join(Data_BE, how='inner')
|
333 |
-
last_week_best_forecast = best_forecast.loc[best_forecast.index >= (best_forecast.index[-24] - pd.Timedelta(days=7))]
|
334 |
-
num_per_var=3
|
335 |
-
forecast_columns_line=['Load_entsoe','Load_forecast_entsoe', 'Load_LightGBMModel.7D.TimeCov.Temp.Forecast_elia', 'Wind_onshore_entsoe','Wind_onshore_forecast_entsoe','Wind_onshore_LightGBMModel.1D.TimeCov.Temp.Forecast_elia','Wind_offshore_entsoe','Wind_offshore_forecast_entsoe','Wind_offshore_LightGBMModel.1D.TimeCov.Temp.Forecast_elia','Solar_entsoe','Solar_forecast_entsoe', 'Solar_LightGBMModel.1D.TimeCov.Temp.Forecast_elia']
|
336 |
-
else:
|
337 |
-
forecast_columns_line=forecast_columns
|
338 |
|
339 |
for i in range(0, len(forecast_columns_line), num_per_var):
|
340 |
actual_col = forecast_columns_line[i]
|
341 |
forecast_col = forecast_columns_line[i + 1]
|
342 |
-
if country_code=='BE':
|
343 |
-
my_forecast = forecast_columns_line[i + 2]
|
344 |
-
|
345 |
|
346 |
if forecast_col in data.columns:
|
347 |
fig = go.Figure()
|
348 |
fig.add_trace(go.Scatter(x=last_week.index, y=last_week[actual_col], mode='lines', name='Actual'))
|
349 |
fig.add_trace(go.Scatter(x=last_week.index, y=last_week[forecast_col], mode='lines', name='Forecast ENTSO-E'))
|
350 |
-
|
351 |
-
if country_code=='BE':
|
352 |
-
conformal=conformal_predictions(df_combined, actual_col, my_forecast)
|
353 |
-
last_week_conformal = conformal.loc[conformal.index >= (conformal.index[-24] - pd.Timedelta(days=7))]
|
354 |
-
if actual_col =='Load_entsoe':
|
355 |
-
last_week_conformal = conformal.loc[conformal.index >= (conformal.index[-24] - pd.Timedelta(days=5))]
|
356 |
-
fig.add_trace(go.Scatter(x=last_week_best_forecast.index, y=last_week_best_forecast[my_forecast], mode='lines', name='Forecast EDS'))
|
357 |
-
|
358 |
-
fig.add_trace(go.Scatter(
|
359 |
-
x=last_week_conformal.index,
|
360 |
-
y=last_week_conformal['Lower_Interval'],
|
361 |
-
mode='lines',
|
362 |
-
line=dict(width=0),
|
363 |
-
showlegend=False
|
364 |
-
))
|
365 |
-
|
366 |
-
# Add the upper interval trace and fill to the lower interval
|
367 |
-
fig.add_trace(go.Scatter(
|
368 |
-
x=last_week_conformal.index,
|
369 |
-
y=last_week_conformal['Upper_Interval'],
|
370 |
-
mode='lines',
|
371 |
-
line=dict(width=0),
|
372 |
-
fill='tonexty', # Fill between this trace and the previous one
|
373 |
-
fillcolor='rgba(68, 68, 68, 0.3)',
|
374 |
-
name='P10/P90 prediction intervals'
|
375 |
-
))
|
376 |
-
|
377 |
-
|
378 |
fig.update_layout(title=f'Forecasts vs Actual for {actual_col}', xaxis_title='Date', yaxis_title='Value [MW]')
|
379 |
|
380 |
st.plotly_chart(fig)
|
@@ -631,33 +560,6 @@ elif section == 'Forecasts':
|
|
631 |
)
|
632 |
|
633 |
return fig
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
if country_code == "BE":
|
639 |
-
|
640 |
-
st.header('MAE Ratio Comparison by Forecast Hour')
|
641 |
-
st.write("These clock-plots shows the relative Mean Absolute Error (rMAE) of different forecasting models compared to the ENTSO-E forecast, by the hour at which the forecast was made. "
|
642 |
-
"The rMAE is calculated as the ratio of the model's MAE to the ENTSO-E forecast's MAE.")
|
643 |
-
|
644 |
-
forecast_dict2 = forecast_dict.copy()
|
645 |
-
forecast_dict2 = {k: simplify_model_names(v) for k, v in forecast_dict.items()}
|
646 |
-
|
647 |
-
|
648 |
-
mae_comparison_fig = plot_mae_comparison_clock(forecast_dict2, 'Solar', 'rMAE Ratio Comparison for Solar', real_values_df=Data_BE)
|
649 |
-
st.plotly_chart(mae_comparison_fig)
|
650 |
-
|
651 |
-
mae_comparison_fig_wind_onshore = plot_mae_comparison_clock(forecast_dict2, 'Wind_onshore', 'MAE Ratio Comparison for Wind Onshore', real_values_df=Data_BE)
|
652 |
-
st.plotly_chart(mae_comparison_fig_wind_onshore)
|
653 |
-
|
654 |
-
mae_comparison_fig_wind_offshore = plot_mae_comparison_clock(forecast_dict2, 'Wind_offshore', 'MAE Ratio Comparison for Wind Offshore', real_values_df=Data_BE)
|
655 |
-
st.plotly_chart(mae_comparison_fig_wind_offshore)
|
656 |
-
|
657 |
-
mae_comparison_fig_load = plot_mae_comparison_clock(forecast_dict2, 'Load', 'MAE Ratio Comparison for Load', real_values_df=Data_BE)
|
658 |
-
st.plotly_chart(mae_comparison_fig_load)
|
659 |
-
|
660 |
-
|
661 |
|
662 |
|
663 |
# Scatter plots for error distribution
|
@@ -683,177 +585,59 @@ elif section == 'Forecasts':
|
|
683 |
output_text = f"The below metrics are calculated from the selected date range from {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}. This interval can be adjusted from the sidebar."
|
684 |
st.write(output_text)
|
685 |
|
|
|
|
|
686 |
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
results_solar = {}
|
699 |
-
|
700 |
-
# Mapping of variables to their corresponding naive models
|
701 |
-
naive_models = {
|
702 |
-
'Wind_onshore': 'Wind_onshore_DailyNaiveSeasonal',
|
703 |
-
'Wind_offshore': 'Wind_offshore_DailyNaiveSeasonal',
|
704 |
-
'Load': 'Load_WeeklyNaiveSeasonal',
|
705 |
-
'Solar': 'Solar_DailyNaiveSeasonal'
|
706 |
-
}
|
707 |
-
|
708 |
-
# Step 1: Calculate MAE, RMSE, and rMAE for each model
|
709 |
-
for col in model_columns:
|
710 |
-
# Extract the variable name by taking everything before the first underscore
|
711 |
-
base_variable = col.split('_')[0]
|
712 |
-
|
713 |
-
# Handle cases where variable names might be combined with multiple parts (e.g., "Load_LightGBMModel...")
|
714 |
-
if base_variable in ['Wind', 'Load', 'Solar']:
|
715 |
-
if 'onshore' in col:
|
716 |
-
variable_name = 'Wind_onshore'
|
717 |
-
results_dict = results_wind_onshore
|
718 |
-
elif 'offshore' in col:
|
719 |
-
variable_name = 'Wind_offshore'
|
720 |
-
results_dict = results_wind_offshore
|
721 |
-
else:
|
722 |
-
variable_name = base_variable
|
723 |
-
results_dict = results_load if base_variable == 'Load' else results_solar
|
724 |
else:
|
725 |
-
|
726 |
-
|
727 |
-
#
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
|
732 |
-
if entsoe_column in df_combined.columns and naive_model_col in df_combined.columns:
|
733 |
-
valid_data = df_combined[[col, entsoe_column]].dropna()
|
734 |
-
valid_naive_data = df_combined[[entsoe_column, naive_model_col]].dropna()
|
735 |
-
|
736 |
-
# Calculate MAE and RMSE for the model against the `variable_entsoe`
|
737 |
-
mae = np.mean(abs(valid_data[col] - valid_data[entsoe_column]))
|
738 |
-
rmse = np.sqrt(mean_squared_error(valid_data[col], valid_data[entsoe_column]))
|
739 |
-
|
740 |
-
# Calculate MAE for the Naive model
|
741 |
-
mae_naive = np.mean(abs(valid_naive_data[entsoe_column] - valid_naive_data[naive_model_col]))
|
742 |
-
|
743 |
-
# Calculate rMAE for the model
|
744 |
-
rMAE = mae / mae_naive if mae_naive != 0 else np.inf
|
745 |
-
|
746 |
-
# Store the results in the corresponding dictionary
|
747 |
-
results_dict[f'{col}'] = {'MAE': mae, 'RMSE': rmse, 'rMAE': rMAE}
|
748 |
-
|
749 |
-
# Step 2: Calculate MAE, RMSE, and rMAE for ENTSO-E forecasts specifically
|
750 |
-
for variable_name in naive_models.keys():
|
751 |
-
entsoe_column = f'{variable_name}_entsoe'
|
752 |
-
forecast_entsoe_column = f'{variable_name}_forecast_entsoe'
|
753 |
-
naive_model_col = naive_models[variable_name]
|
754 |
-
|
755 |
-
# Ensure that the ENTSO-E forecast is included in the results
|
756 |
-
if forecast_entsoe_column in df_combined.columns:
|
757 |
-
valid_data = df_combined[[forecast_entsoe_column, entsoe_column]].dropna()
|
758 |
-
valid_naive_data = df_combined[[entsoe_column, naive_model_col]].dropna()
|
759 |
-
|
760 |
-
# Calculate MAE and RMSE for the ENTSO-E forecast against the actuals
|
761 |
-
mae_entsoe = np.mean(abs(valid_data[forecast_entsoe_column] - valid_data[entsoe_column]))
|
762 |
-
rmse_entsoe = np.sqrt(mean_squared_error(valid_data[forecast_entsoe_column], valid_data[entsoe_column]))
|
763 |
-
|
764 |
-
# Calculate rMAE for the ENTSO-E forecast
|
765 |
-
mae_naive = np.mean(abs(valid_naive_data[entsoe_column] - valid_naive_data[naive_model_col]))
|
766 |
-
rMAE_entsoe = mae_entsoe / mae_naive if mae_naive != 0 else np.inf
|
767 |
-
|
768 |
-
# Add the ENTSO-E results to the corresponding dictionary
|
769 |
-
if variable_name == 'Wind_onshore':
|
770 |
-
results_wind_onshore[forecast_entsoe_column] = {'MAE': mae_entsoe, 'RMSE': rmse_entsoe, 'rMAE': rMAE_entsoe}
|
771 |
-
elif variable_name == 'Wind_offshore':
|
772 |
-
results_wind_offshore[forecast_entsoe_column] = {'MAE': mae_entsoe, 'RMSE': rmse_entsoe, 'rMAE': rMAE_entsoe}
|
773 |
-
elif variable_name == 'Load':
|
774 |
-
results_load[forecast_entsoe_column] = {'MAE': mae_entsoe, 'RMSE': rmse_entsoe, 'rMAE': rMAE_entsoe}
|
775 |
-
elif variable_name == 'Solar':
|
776 |
-
results_solar[forecast_entsoe_column] = {'MAE': mae_entsoe, 'RMSE': rmse_entsoe, 'rMAE': rMAE_entsoe}
|
777 |
-
|
778 |
-
# Convert the dictionaries to DataFrames and sort by rMAE
|
779 |
-
df_wind_onshore = pd.DataFrame.from_dict(results_wind_onshore, orient='index').sort_values(by='rMAE')
|
780 |
-
df_wind_offshore = pd.DataFrame.from_dict(results_wind_offshore, orient='index').sort_values(by='rMAE')
|
781 |
-
df_load = pd.DataFrame.from_dict(results_load, orient='index').sort_values(by='rMAE')
|
782 |
-
df_solar = pd.DataFrame.from_dict(results_solar, orient='index').sort_values(by='rMAE')
|
783 |
-
|
784 |
-
|
785 |
-
st.write("##### Wind Onshore:")
|
786 |
-
df_wind_onshore = simplify_model_names_in_index(df_wind_onshore)
|
787 |
-
st.dataframe(df_wind_onshore)
|
788 |
-
|
789 |
-
st.write("##### Wind Offshore:")
|
790 |
-
df_wind_offshore2 = simplify_model_names_in_index(df_wind_offshore)
|
791 |
-
st.dataframe(df_wind_offshore)
|
792 |
-
|
793 |
-
st.write("##### Load:")
|
794 |
-
df_load = simplify_model_names_in_index(df_load)
|
795 |
-
st.dataframe(df_load)
|
796 |
|
797 |
-
|
798 |
-
|
799 |
-
|
800 |
|
|
|
801 |
|
|
|
|
|
802 |
|
803 |
-
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
|
809 |
-
|
810 |
-
|
811 |
-
|
812 |
-
|
813 |
-
|
814 |
-
|
815 |
-
|
816 |
-
|
817 |
-
|
818 |
-
|
819 |
-
|
820 |
-
|
821 |
-
|
822 |
-
rmae = round(mae / np.mean(np.abs(obs - persistence)),2)
|
823 |
-
|
824 |
-
row_label = 'Load' if 'Load' in actual_col else 'Solar' if 'Solar' in actual_col else 'Wind Offshore' if 'Wind_offshore' in actual_col else 'Wind Onshore'
|
825 |
-
accuracy_metrics.loc[row_label] = [mae, rmae]
|
826 |
-
|
827 |
-
accuracy_metrics.dropna(how='all', inplace=True)# Sort by rMAE (second column)
|
828 |
-
accuracy_metrics.sort_values(by=accuracy_metrics.columns[1], ascending=True, inplace=True)
|
829 |
-
accuracy_metrics = accuracy_metrics.round(4)
|
830 |
-
|
831 |
-
col1, col2 = st.columns([3, 2])
|
832 |
-
|
833 |
-
with col1:
|
834 |
-
st.dataframe(accuracy_metrics)
|
835 |
-
|
836 |
-
with col2:
|
837 |
-
st.markdown("""
|
838 |
-
<style>
|
839 |
-
.big-font {
|
840 |
-
font-size: 20px;
|
841 |
-
font-weight: 500;
|
842 |
-
}
|
843 |
-
</style>
|
844 |
-
<div class="big-font">
|
845 |
-
Equations
|
846 |
-
</div>
|
847 |
-
""", unsafe_allow_html=True)
|
848 |
-
|
849 |
-
st.markdown(r"""
|
850 |
-
$\text{MAE} = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i|$
|
851 |
-
|
852 |
-
|
853 |
-
$\text{rMAE} = \frac{\text{MAE}}{MAE_{\text{Persistence Model}}}$
|
854 |
-
|
855 |
|
856 |
-
|
857 |
|
858 |
|
859 |
|
|
|
84 |
|
85 |
return df
|
86 |
|
87 |
+
github_token = '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'
|
|
|
|
|
88 |
|
89 |
if github_token:
|
90 |
+
forecast_dict = load_forecast(github_token)
|
91 |
|
92 |
+
historical_forecast=load_GitHub(github_token, 'Historical_forecast.csv')
|
93 |
|
94 |
+
Data_BE=load_GitHub(github_token, 'BE_Elia_Entsoe_UTC.csv')
|
95 |
+
Data_FR=load_GitHub(github_token, 'FR_Entsoe_UTC.csv')
|
96 |
+
Data_NL=load_GitHub(github_token, 'NL_Entsoe_UTC.csv')
|
97 |
+
Data_DE=load_GitHub(github_token, 'DE_Entsoe_UTC.csv')
|
98 |
|
99 |
Data_BE=convert_European_time(Data_BE, 'Europe/Brussels')
|
100 |
Data_FR=convert_European_time(Data_FR, 'Europe/Paris')
|
|
|
105 |
else:
|
106 |
print("Please enter your GitHub Personal Access Token to proceed.")
|
107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
|
109 |
# Main layout of the app
|
110 |
col1, col2 = st.columns([5, 2]) # Adjust the ratio to better fit your layout needs
|
|
|
126 |
""", unsafe_allow_html=True)
|
127 |
|
128 |
|
129 |
+
|
130 |
countries = {
|
131 |
'Netherlands': 'NL',
|
132 |
'Germany': 'DE',
|
|
|
218 |
|
219 |
st.write('The table below presents the data quality metrics for various energy-related datasets, focusing on the percentage of missing values and the occurrence of extreme or nonsensical values for the selected country.')
|
220 |
data_quality=data.iloc[:-28]
|
221 |
+
|
|
|
|
|
222 |
# Report % of missing values
|
223 |
missing_values = data_quality[forecast_columns].isna().mean() * 100
|
224 |
missing_values = missing_values.round(2)
|
|
|
294 |
'Load_entsoe','Load_forecast_entsoe','Wind_onshore_entsoe','Wind_onshore_forecast_entsoe','Wind_offshore_entsoe','Wind_offshore_forecast_entsoe','Solar_entsoe','Solar_forecast_entsoe']
|
295 |
num_per_var=2
|
296 |
|
297 |
+
forecast_columns_line=forecast_columns
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
298 |
|
299 |
for i in range(0, len(forecast_columns_line), num_per_var):
|
300 |
actual_col = forecast_columns_line[i]
|
301 |
forecast_col = forecast_columns_line[i + 1]
|
|
|
|
|
|
|
302 |
|
303 |
if forecast_col in data.columns:
|
304 |
fig = go.Figure()
|
305 |
fig.add_trace(go.Scatter(x=last_week.index, y=last_week[actual_col], mode='lines', name='Actual'))
|
306 |
fig.add_trace(go.Scatter(x=last_week.index, y=last_week[forecast_col], mode='lines', name='Forecast ENTSO-E'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
307 |
fig.update_layout(title=f'Forecasts vs Actual for {actual_col}', xaxis_title='Date', yaxis_title='Value [MW]')
|
308 |
|
309 |
st.plotly_chart(fig)
|
|
|
560 |
)
|
561 |
|
562 |
return fig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
563 |
|
564 |
|
565 |
# Scatter plots for error distribution
|
|
|
585 |
output_text = f"The below metrics are calculated from the selected date range from {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}. This interval can be adjusted from the sidebar."
|
586 |
st.write(output_text)
|
587 |
|
588 |
+
data = data.loc[start_date:end_date]
|
589 |
+
accuracy_metrics = pd.DataFrame(columns=['MAE', 'rMAE'], index=['Load', 'Solar', 'Wind Onshore', 'Wind Offshore'])
|
590 |
|
591 |
+
for i in range(0, len(forecast_columns), 2):
|
592 |
+
actual_col = forecast_columns[i]
|
593 |
+
forecast_col = forecast_columns[i + 1]
|
594 |
+
if forecast_col in data.columns:
|
595 |
+
obs = data[actual_col]
|
596 |
+
pred = data[forecast_col]
|
597 |
+
error = pred - obs
|
598 |
+
|
599 |
+
mae = round(np.mean(np.abs(error)),2)
|
600 |
+
if 'Load' in actual_col:
|
601 |
+
persistence = obs.shift(168) # Weekly persistence
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
602 |
else:
|
603 |
+
persistence = obs.shift(24) # Daily persistence
|
604 |
+
|
605 |
+
# Using the whole year's data for rMAE calculations
|
606 |
+
rmae = round(mae / np.mean(np.abs(obs - persistence)),2)
|
607 |
+
|
608 |
+
row_label = 'Load' if 'Load' in actual_col else 'Solar' if 'Solar' in actual_col else 'Wind Offshore' if 'Wind_offshore' in actual_col else 'Wind Onshore'
|
609 |
+
accuracy_metrics.loc[row_label] = [mae, rmae]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
610 |
|
611 |
+
accuracy_metrics.dropna(how='all', inplace=True)# Sort by rMAE (second column)
|
612 |
+
accuracy_metrics.sort_values(by=accuracy_metrics.columns[1], ascending=True, inplace=True)
|
613 |
+
accuracy_metrics = accuracy_metrics.round(4)
|
614 |
|
615 |
+
col1, col2 = st.columns([3, 2])
|
616 |
|
617 |
+
with col1:
|
618 |
+
st.dataframe(accuracy_metrics)
|
619 |
|
620 |
+
with col2:
|
621 |
+
st.markdown("""
|
622 |
+
<style>
|
623 |
+
.big-font {
|
624 |
+
font-size: 20px;
|
625 |
+
font-weight: 500;
|
626 |
+
}
|
627 |
+
</style>
|
628 |
+
<div class="big-font">
|
629 |
+
Equations
|
630 |
+
</div>
|
631 |
+
""", unsafe_allow_html=True)
|
632 |
+
|
633 |
+
st.markdown(r"""
|
634 |
+
$\text{MAE} = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i|$
|
635 |
+
|
636 |
+
|
637 |
+
$\text{rMAE} = \frac{\text{MAE}}{MAE_{\text{Persistence Model}}}$
|
638 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
639 |
|
640 |
+
""")
|
641 |
|
642 |
|
643 |
|