Spaces:
Runtime error
Runtime error
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 |
|