mmmapms commited on
Commit
2dae4e3
1 Parent(s): 3d3f015

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +291 -22
app.py CHANGED
@@ -47,6 +47,34 @@ def convert_European_time(data, time_zone):
47
  data.index = data.index.tz_localize(None)
48
  return data
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  github_token = st.secrets["GitHub_Token_KUL_Margarida"]
51
 
52
  if github_token:
@@ -92,8 +120,26 @@ def conformal_predictions(data, target, my_forecast):
92
  #data.reset_index(inplace=True)
93
  return data
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- st.title("Transparency++")
97
 
98
  countries = {
99
  'Belgium': 'BE',
@@ -105,9 +151,15 @@ countries = {
105
 
106
  st.sidebar.header('Filters')
107
 
 
 
 
108
  selected_country = st.sidebar.selectbox('Select Country', list(countries.keys()))
109
 
110
 
 
 
 
111
  st.write()
112
  date_range = st.sidebar.date_input("Select Date Range for Metrics Calculation:",
113
  value=(pd.to_datetime("2024-01-01"), pd.to_datetime(pd.Timestamp('today'))))
@@ -120,9 +172,12 @@ else:
120
  st.error("Please select a valid date range.")
121
  st.stop()
122
 
123
- # Sidebar with radio buttons for different sections
124
- section = st.sidebar.radio('Section', ['Data', 'Forecasts', 'Insights'])
 
125
 
 
 
126
 
127
  country_code = countries[selected_country]
128
  if country_code == 'BE':
@@ -161,7 +216,7 @@ def add_feature(df2, df_main):
161
  #df_combined.reset_index(inplace=True)
162
  return df_combined
163
  #data.index = data.index.tz_localize('UTC')
164
- data = data.loc[start_date:end_date]
165
 
166
  forecast_columns = [
167
  'Load_entsoe','Load_forecast_entsoe','Wind_onshore_entsoe','Wind_onshore_forecast_entsoe','Wind_offshore_entsoe','Wind_offshore_forecast_entsoe','Solar_entsoe','Solar_forecast_entsoe']
@@ -175,8 +230,7 @@ if section == 'Data':
175
 
176
  st.header('Data Quality')
177
 
178
- output_text = f"The below percentages 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."
179
- st.write(output_text)
180
 
181
  # Report % of missing values
182
  missing_values = data[forecast_columns].isna().mean() * 100
@@ -403,21 +457,225 @@ elif section == 'Forecasts':
403
  )
404
  )
405
  return fig
 
406
 
407
- if country_code == "BE":
408
- st.header('EDS Forecasts by Hour')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
- solar_fig = plot_category(forecast_dict, 'Solar', 'Solar Predictions')
411
- st.plotly_chart(solar_fig)
412
 
413
- wind_offshore_fig = plot_category(forecast_dict, 'Wind_offshore', 'Wind Offshore Predictions')
414
- st.plotly_chart(wind_offshore_fig)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
 
416
- wind_onshore_fig = plot_category(forecast_dict, 'Wind_onshore', 'Wind Onshore Predictions')
417
- st.plotly_chart(wind_onshore_fig)
 
 
 
 
 
 
 
 
 
 
418
 
419
- load_fig = plot_category(forecast_dict, 'Load', 'Load Predictions')
420
- st.plotly_chart(load_fig)
421
 
422
  # Scatter plots for error distribution
423
  st.subheader('Error Distribution')
@@ -426,18 +684,22 @@ elif section == 'Forecasts':
426
  actual_col = forecast_columns[i]
427
  forecast_col = forecast_columns[i + 1]
428
  if forecast_col in data.columns:
429
- obs = last_week[actual_col]
430
- pred = last_week[forecast_col]
431
  error = pred - obs
432
 
433
  fig = px.scatter(x=obs, y=pred, labels={'x': 'Observed [MW]', 'y': 'Predicted by ENTSO-E [MW]'})
434
  fig.update_layout(title=f'Error Distribution for {forecast_col}')
435
  st.plotly_chart(fig)
436
 
437
-
438
 
439
  st.subheader('Accuracy Metrics (Sorted by rMAE):')
440
 
 
 
 
 
441
  if country_code == "BE":
442
 
443
  # Combine the two DataFrames on their index
@@ -531,26 +793,32 @@ elif section == 'Forecasts':
531
 
532
  # Convert the dictionaries to DataFrames and sort by rMAE
533
  df_wind_onshore = pd.DataFrame.from_dict(results_wind_onshore, orient='index').sort_values(by='rMAE')
 
534
  df_wind_offshore = pd.DataFrame.from_dict(results_wind_offshore, orient='index').sort_values(by='rMAE')
535
  df_load = pd.DataFrame.from_dict(results_load, orient='index').sort_values(by='rMAE')
536
  df_solar = pd.DataFrame.from_dict(results_solar, orient='index').sort_values(by='rMAE')
537
 
538
 
539
  st.write("##### Wind Onshore:")
 
540
  st.dataframe(df_wind_onshore)
541
 
542
  st.write("##### Wind Offshore:")
 
543
  st.dataframe(df_wind_offshore)
544
 
545
  st.write("##### Load:")
 
546
  st.dataframe(df_load)
547
 
548
  st.write("##### Solar:")
 
549
  st.dataframe(df_solar)
550
 
551
 
552
 
553
  else:
 
554
  accuracy_metrics = pd.DataFrame(columns=['MAE', 'rMAE'], index=['Load', 'Solar', 'Wind Onshore', 'Wind Offshore'])
555
 
556
  for i in range(0, len(forecast_columns), 2):
@@ -607,7 +875,7 @@ elif section == 'Forecasts':
607
 
608
 
609
  st.subheader('ACF plots of Errors')
610
- st.write('The below plots show the ACF (Auto-Correlation Function) for the errors of all three fields: Solar, Wind and Load.')
611
 
612
  for i in range(0, len(forecast_columns), 2):
613
  actual_col = forecast_columns[i]
@@ -634,7 +902,7 @@ elif section == 'Insights':
634
 
635
  # Scatter plots for correlation between wind, solar, and load
636
  st.subheader('Correlation between Wind, Solar, and Load')
637
- st.write('The below scatter plots for correlation between all three fields: Solar, Wind and Load.')
638
 
639
  combinations = [('Solar_entsoe', 'Load_entsoe'), ('Wind_onshore_entsoe', 'Load_entsoe'), ('Wind_offshore_entsoe', 'Load_entsoe'), ('Solar_entsoe', 'Wind_onshore_entsoe'), ('Solar_entsoe', 'Wind_offshore_entsoe')]
640
 
@@ -659,12 +927,13 @@ elif section == 'Insights':
659
 
660
 
661
  st.subheader('Weather vs. Generation/Demand')
662
- st.write('The below scatter plots show the relation between weather parameters (i.e., Temperature, Wind Speed) and generation/demand.')
663
 
664
  for weather_col in weather_columns:
665
  for actual_col in ['Load_entsoe', 'Solar_entsoe', 'Wind_onshore_entsoe', 'Wind_offshore_entsoe']:
666
  if weather_col in data.columns and actual_col in data.columns:
667
  clean_label = actual_col.replace('_entsoe', '')
 
668
  if weather_col == 'Temperature':
669
  fig = px.scatter(x=data[weather_col], y=data[actual_col], labels={'x': f'{weather_col} (°C)', 'y': f'{clean_label} Generation [MW]'}, color_discrete_sequence=['orange'])
670
  else:
 
47
  data.index = data.index.tz_localize(None)
48
  return data
49
 
50
+ def simplify_model_names(df):
51
+ # Define the mapping of complex names to simpler ones
52
+ replacements = {
53
+ r'\.LightGBMModel\.\dD\.TimeCov\.Temp\.Forecast_elia': '.LightGBM_with_Forecast_elia',
54
+ r'\.LightGBMModel\.\dD\.TimeCov\.Temp': '.LightGBM',
55
+ r'\.Naive\.\dD': '.Naive',
56
+ }
57
+
58
+ # Apply the replacements
59
+ for original, simplified in replacements.items():
60
+ df.columns = df.columns.str.replace(original, simplified, regex=True)
61
+
62
+ return df
63
+
64
+ def simplify_model_names_in_index(df):
65
+ # Define the mapping of complex names to simpler ones
66
+ replacements = {
67
+ r'\.LightGBMModel\.\dD\.TimeCov\.Temp\.Forecast_elia': '.LightGBM_with_Forecast_elia',
68
+ r'\.LightGBMModel\.\dD\.TimeCov\.Temp': '.LightGBM',
69
+ r'\.Naive\.\dD': '.Naive',
70
+ }
71
+
72
+ # Apply the replacements to the DataFrame index
73
+ for original, simplified in replacements.items():
74
+ df.index = df.index.str.replace(original, simplified, regex=True)
75
+
76
+ return df
77
+
78
  github_token = st.secrets["GitHub_Token_KUL_Margarida"]
79
 
80
  if github_token:
 
120
  #data.reset_index(inplace=True)
121
  return data
122
 
123
+ # Main layout of the app
124
+ col1, col2 = st.columns([5, 2]) # Adjust the ratio to better fit your layout needs
125
+ with col1:
126
+ st.title("Transparency++")
127
+
128
+ with col2:
129
+ upper_space = col2.empty()
130
+ upper_space = col2.empty()
131
+ col2_1, col2_2 = st.columns(2) # Create two columns within the right column for side-by-side images
132
+ with col2_1:
133
+ st.image("KU_Leuven_logo.png", width=100) # Adjust the path and width as needed
134
+ with col2_2:
135
+ st.image("energyville_logo.png", width=100)
136
+
137
+ upper_space.markdown("""
138
+  
139
+  
140
+ """, unsafe_allow_html=True)
141
+
142
 
 
143
 
144
  countries = {
145
  'Belgium': 'BE',
 
151
 
152
  st.sidebar.header('Filters')
153
 
154
+ st.sidebar.subheader("Select Country")
155
+ st.sidebar.caption("Choose the country for which you want to display data or forecasts.")
156
+
157
  selected_country = st.sidebar.selectbox('Select Country', list(countries.keys()))
158
 
159
 
160
+ st.sidebar.subheader("Select Date Range ")
161
+ st.sidebar.caption("Define the time period over which the accuracy metrics will be calculated.")
162
+
163
  st.write()
164
  date_range = st.sidebar.date_input("Select Date Range for Metrics Calculation:",
165
  value=(pd.to_datetime("2024-01-01"), pd.to_datetime(pd.Timestamp('today'))))
 
172
  st.error("Please select a valid date range.")
173
  st.stop()
174
 
175
+ st.sidebar.subheader("Section")
176
+ st.sidebar.caption("Select the type of information you want to explore.")
177
+
178
 
179
+ # Sidebar with radio buttons for different sections
180
+ section = st.sidebar.radio('', ['Data', 'Forecasts', 'Insights'],index=1)
181
 
182
  country_code = countries[selected_country]
183
  if country_code == 'BE':
 
216
  #df_combined.reset_index(inplace=True)
217
  return df_combined
218
  #data.index = data.index.tz_localize('UTC')
219
+
220
 
221
  forecast_columns = [
222
  'Load_entsoe','Load_forecast_entsoe','Wind_onshore_entsoe','Wind_onshore_forecast_entsoe','Wind_offshore_entsoe','Wind_offshore_forecast_entsoe','Solar_entsoe','Solar_forecast_entsoe']
 
230
 
231
  st.header('Data Quality')
232
 
233
+ 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.')
 
234
 
235
  # Report % of missing values
236
  missing_values = data[forecast_columns].isna().mean() * 100
 
457
  )
458
  )
459
  return fig
460
+
461
 
462
+ def calculate_mae(y_true, y_pred):
463
+ return np.mean(np.abs(y_true - y_pred))
464
+ def plot_mae_comparison(df_dict, category_prefix, title, real_values_df):
465
+ hours = list(range(24))
466
+ if category_prefix=='Load':
467
+ model_colors = {
468
+ 'LightGBMModel.7D.TimeCov.Temp.Forecast_elia': '#1F77B4', # Blue
469
+ 'LightGBMModel.7D.TimeCov.Temp': '#2CA02C', # Green
470
+ 'Naive': '#FF7F0E' # Orange
471
+ }
472
+ else:
473
+ model_colors = {
474
+ 'LightGBMModel.1D.TimeCov.Temp.Forecast_elia': '#1F77B4', # Blue
475
+ 'LightGBMModel.1D.TimeCov.Temp': '#2CA02C', # Green
476
+ 'Naive': '#FF7F0E' # Orange
477
+ }
478
+ fig = go.Figure()
479
+ for model_key, base_color in model_colors.items():
480
+ hours_with_data = []
481
+ mae_ratios = []
482
+ for hour in hours:
483
+ file_name = f'Predictions_{hour}h.csv'
484
+ df = df_dict.get(file_name, None)
485
+ if df is None:
486
+ continue
487
+ if isinstance(df.index, pd.DatetimeIndex):
488
+ first_day = df.index.min().normalize()
489
+ last_day = df.index.max().normalize()
490
+ df = df[df.index.normalize() != first_day]
491
+ df = df[df.index.normalize() != last_day]
492
+ # Adjusted filtering logic based on actual column names
493
+ filtered_columns = [col for col in df.columns if col.startswith(f"{category_prefix}_entsoe") and model_key in col]
494
+ if not filtered_columns:
495
+ continue
496
+ # Assuming only one column matches, otherwise refine the selection logic
497
+ model_predictions = df[filtered_columns[0]]
498
+ actual_values = real_values_df[f'{category_prefix}_entsoe']
499
+ actual_values = actual_values.dropna()
500
+ # Align both series by their common indices
501
+ common_indices = model_predictions.index.intersection(actual_values.index)
502
+ aligned_model_predictions = model_predictions.loc[common_indices]
503
+ aligned_actual_values = actual_values.loc[common_indices]
504
+ # Calculate MAE for the model
505
+ model_mae = calculate_mae(aligned_actual_values, aligned_model_predictions)
506
+ # Calculate MAE for the entsoe forecast
507
+ entsoe_forecast = real_values_df[f'{category_prefix}_forecast_entsoe'].loc[common_indices]
508
+ #print(entsoe_forecast.index)
509
+ entsoe_mae = calculate_mae(aligned_actual_values, entsoe_forecast)
510
+ # Calculate MAE ratio
511
+ mae_ratio = model_mae / entsoe_mae
512
+ mae_ratios.append(mae_ratio)
513
+ hours_with_data.append(hour)
514
+ # Plot the MAE ratio for this model as points
515
+ if mae_ratios: # Only plot if there's data
516
+ fig.add_trace(go.Scatter(
517
+ x=hours_with_data, # The hours where we have data
518
+ y=mae_ratios,
519
+ mode='markers+lines', # Plot as points connected by lines
520
+ name=model_key,
521
+ line=dict(color=base_color),
522
+ marker=dict(color=base_color, size=8) # Customize marker size
523
+ ))
524
+ # Update layout
525
+ fig.update_layout(
526
+ title=f'{category_prefix}: rMAE<span style="font-size:11px;">ENTSO-E</span> by hour of Forecasting.',
527
+ xaxis_title='Hour of Forecast',
528
+ yaxis_title='MAE Ratio (Model / entsoe)',
529
+ legend=dict(
530
+ orientation="h",
531
+ yanchor="bottom",
532
+ y=1.02,
533
+ xanchor="center",
534
+ x=0.5
535
+ )
536
+ )
537
+ return fig
538
+
539
+
540
+
541
+ def plot_mae_comparison_clock(df_dict, category_prefix, title, real_values_df):
542
+ hours = list(range(24))
543
+ if category_prefix=='Load':
544
+ model_colors = {
545
+ 'LightGBM_with_Forecast_elia': '#1F77B4', # Blue
546
+ 'LightGBM': '#2CA02C', # Green
547
+ 'Naive': '#FF7F0E' # Orange
548
+ }
549
+ else:
550
+ model_colors = {
551
+ 'LightGBM_with_Forecast_elia': '#1F77B4', # Blue
552
+ 'LightGBM': '#2CA02C', # Green
553
+ 'Naive': '#FF7F0E' # Orange
554
+ }
555
 
556
+ fig = go.Figure()
 
557
 
558
+ for model_key, base_color in model_colors.items():
559
+ hours_with_data = []
560
+ mae_ratios = []
561
+
562
+ #print(f"Processing {model_key}...") # Debugging print
563
+
564
+ for hour in hours:
565
+ file_name = f'Predictions_{hour}h.csv'
566
+ df = df_dict.get(file_name, None)
567
+ if df is None:
568
+ #print(f"No data for hour {hour}. Skipping...")
569
+ continue
570
+
571
+ if isinstance(df.index, pd.DatetimeIndex):
572
+ first_day = df.index.min().normalize()
573
+ last_day = df.index.max().normalize()
574
+ df = df[df.index.normalize() != first_day]
575
+ df = df[df.index.normalize() != last_day]
576
+
577
+ filtered_columns = [col for col in df.columns if col.startswith(f"{category_prefix}_entsoe") and model_key in col]
578
+ if not filtered_columns:
579
+ print(f"No matching columns for {model_key} at hour {hour}. Skipping...")
580
+ continue
581
+
582
+ model_predictions = df[filtered_columns[0]]
583
+ actual_values = real_values_df[f'{category_prefix}_entsoe']
584
+ actual_values = actual_values.dropna()
585
+
586
+ common_indices = model_predictions.index.intersection(actual_values.index)
587
+ aligned_model_predictions = model_predictions.loc[common_indices]
588
+ aligned_actual_values = actual_values.loc[common_indices]
589
+
590
+ model_mae = calculate_mae(aligned_actual_values, aligned_model_predictions)
591
+ entsoe_forecast = real_values_df[f'{category_prefix}_forecast_entsoe'].loc[common_indices]
592
+ entsoe_mae = calculate_mae(aligned_actual_values, entsoe_forecast)
593
+
594
+ mae_ratio = model_mae / entsoe_mae
595
+ mae_ratios.append(mae_ratio)
596
+ hours_with_data.append(hour)
597
+
598
+ if mae_ratios:
599
+ print(f"Adding {model_key} to the plot with {len(mae_ratios)} points.") # Debugging print
600
+ fig.add_trace(go.Scatterpolar(
601
+ r=mae_ratios + [mae_ratios[0]], # Ensure closure of the polar plot
602
+ theta=[h * 15 for h in hours_with_data] + [0], # Ensure closure at 0 degrees
603
+ mode='lines+markers',
604
+ name=model_key,
605
+ line=dict(color=base_color),
606
+ marker=dict(color=base_color, size=8)
607
+ ))
608
+ else:
609
+ print(f"No data to plot for {model_key}.") # Debugging print
610
+
611
+ fig.update_layout(
612
+ polar=dict(
613
+ radialaxis=dict(visible=True, range=[0, max(max(mae_ratios), 1.0) * 1.1] if mae_ratios else [0, 1.0]),
614
+ angularaxis=dict(tickmode='array', tickvals=[h * 15 for h in hours], ticktext=hours)
615
+ ),
616
+ title=f'{category_prefix}: rMAE<span style="font-size:11px;">ENTSO-E</span> by Hour of Forecasting',
617
+ showlegend=True
618
+ )
619
+
620
+ return fig
621
+
622
+
623
+
624
+
625
+ if country_code == "BE":
626
+ #-------------------------------------------------
627
+ #st.header('EDS Forecasts by Hour')
628
+
629
+ #solar_fig = plot_category(forecast_dict, 'Solar', 'Solar Predictions')
630
+ #st.plotly_chart(solar_fig)
631
+
632
+ #wind_offshore_fig = plot_category(forecast_dict, 'Wind_offshore', 'Wind Offshore Predictions')
633
+ #st.plotly_chart(wind_offshore_fig)
634
+
635
+ #wind_onshore_fig = plot_category(forecast_dict, 'Wind_onshore', 'Wind Onshore Predictions')
636
+ #st.plotly_chart(wind_onshore_fig)
637
+
638
+ #load_fig = plot_category(forecast_dict, 'Load', 'Load Predictions')
639
+ #st.plotly_chart(load_fig)
640
+
641
+ #-------------------------------------------------
642
+ #st.header('MAE Ratio Comparison by Forecast Hour')
643
+ #st.write("This graph shows the relative Mean Absolute Error (rMAE) of different forecasting models "
644
+ #"compared to the ENTSO-E forecast, by the hour at which the forecast was made. "
645
+ #"The rMAE is calculated as the ratio of the model's MAE to the ENTSO-E forecast's MAE.")
646
+ #mae_comparison_fig = plot_mae_comparison(forecast_dict, 'Solar', 'rMAE Ratio Comparison for Solar', real_values_df=Data_BE)
647
+ #st.plotly_chart(mae_comparison_fig)
648
+ # Similarly for Wind_onshore, Wind_offshore, and Load
649
+ #mae_comparison_fig_wind_onshore = plot_mae_comparison(forecast_dict, 'Wind_onshore', 'MAE Ratio Comparison for Wind Onshore', real_values_df=Data_BE)
650
+ #st.plotly_chart(mae_comparison_fig_wind_onshore)
651
+ #mae_comparison_fig_wind_offshore = plot_mae_comparison(forecast_dict, 'Wind_offshore', 'MAE Ratio Comparison for Wind Offshore', real_values_df=Data_BE)
652
+ #st.plotly_chart(mae_comparison_fig_wind_offshore)
653
+ #mae_comparison_fig_load = plot_mae_comparison(forecast_dict, 'Load', 'MAE Ratio Comparison for Load', real_values_df=Data_BE)
654
+ #st.plotly_chart(mae_comparison_fig_load)
655
+ #-------------------------------------------------
656
+
657
+ st.header('MAE Ratio Comparison by Forecast Hour')
658
+ 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. "
659
+ "The rMAE is calculated as the ratio of the model's MAE to the ENTSO-E forecast's MAE.")
660
+
661
+ forecast_dict2 = forecast_dict.copy()
662
+ forecast_dict2 = {k: simplify_model_names(v) for k, v in forecast_dict.items()}
663
 
664
+
665
+ mae_comparison_fig = plot_mae_comparison_clock(forecast_dict2, 'Solar', 'rMAE Ratio Comparison for Solar', real_values_df=Data_BE)
666
+ st.plotly_chart(mae_comparison_fig)
667
+
668
+ mae_comparison_fig_wind_onshore = plot_mae_comparison_clock(forecast_dict2, 'Wind_onshore', 'MAE Ratio Comparison for Wind Onshore', real_values_df=Data_BE)
669
+ st.plotly_chart(mae_comparison_fig_wind_onshore)
670
+
671
+ mae_comparison_fig_wind_offshore = plot_mae_comparison_clock(forecast_dict2, 'Wind_offshore', 'MAE Ratio Comparison for Wind Offshore', real_values_df=Data_BE)
672
+ st.plotly_chart(mae_comparison_fig_wind_offshore)
673
+
674
+ mae_comparison_fig_load = plot_mae_comparison_clock(forecast_dict2, 'Load', 'MAE Ratio Comparison for Load', real_values_df=Data_BE)
675
+ st.plotly_chart(mae_comparison_fig_load)
676
 
677
+
678
+
679
 
680
  # Scatter plots for error distribution
681
  st.subheader('Error Distribution')
 
684
  actual_col = forecast_columns[i]
685
  forecast_col = forecast_columns[i + 1]
686
  if forecast_col in data.columns:
687
+ obs = data[actual_col]
688
+ pred = data[forecast_col]
689
  error = pred - obs
690
 
691
  fig = px.scatter(x=obs, y=pred, labels={'x': 'Observed [MW]', 'y': 'Predicted by ENTSO-E [MW]'})
692
  fig.update_layout(title=f'Error Distribution for {forecast_col}')
693
  st.plotly_chart(fig)
694
 
695
+
696
 
697
  st.subheader('Accuracy Metrics (Sorted by rMAE):')
698
 
699
+ 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."
700
+ st.write(output_text)
701
+
702
+
703
  if country_code == "BE":
704
 
705
  # Combine the two DataFrames on their index
 
793
 
794
  # Convert the dictionaries to DataFrames and sort by rMAE
795
  df_wind_onshore = pd.DataFrame.from_dict(results_wind_onshore, orient='index').sort_values(by='rMAE')
796
+ print(df_wind_onshore)
797
  df_wind_offshore = pd.DataFrame.from_dict(results_wind_offshore, orient='index').sort_values(by='rMAE')
798
  df_load = pd.DataFrame.from_dict(results_load, orient='index').sort_values(by='rMAE')
799
  df_solar = pd.DataFrame.from_dict(results_solar, orient='index').sort_values(by='rMAE')
800
 
801
 
802
  st.write("##### Wind Onshore:")
803
+ df_wind_onshore = simplify_model_names_in_index(df_wind_onshore)
804
  st.dataframe(df_wind_onshore)
805
 
806
  st.write("##### Wind Offshore:")
807
+ df_wind_offshore2 = simplify_model_names_in_index(df_wind_offshore)
808
  st.dataframe(df_wind_offshore)
809
 
810
  st.write("##### Load:")
811
+ df_load = simplify_model_names_in_index(df_load)
812
  st.dataframe(df_load)
813
 
814
  st.write("##### Solar:")
815
+ df_solar = simplify_model_names_in_index(df_solar)
816
  st.dataframe(df_solar)
817
 
818
 
819
 
820
  else:
821
+ data = data.loc[start_date:end_date]
822
  accuracy_metrics = pd.DataFrame(columns=['MAE', 'rMAE'], index=['Load', 'Solar', 'Wind Onshore', 'Wind Offshore'])
823
 
824
  for i in range(0, len(forecast_columns), 2):
 
875
 
876
 
877
  st.subheader('ACF plots of Errors')
878
+ st.write('The below plots show the ACF (Auto-Correlation Function) for the errors of all three data fields obtained from ENTSO-E: Solar, Wind and Load.')
879
 
880
  for i in range(0, len(forecast_columns), 2):
881
  actual_col = forecast_columns[i]
 
902
 
903
  # Scatter plots for correlation between wind, solar, and load
904
  st.subheader('Correlation between Wind, Solar, and Load')
905
+ st.write('The below scatter plots are made for checking whether there exists a correlation between all three data fields obtained from ENTSO-E: Solar, Wind and Load.')
906
 
907
  combinations = [('Solar_entsoe', 'Load_entsoe'), ('Wind_onshore_entsoe', 'Load_entsoe'), ('Wind_offshore_entsoe', 'Load_entsoe'), ('Solar_entsoe', 'Wind_onshore_entsoe'), ('Solar_entsoe', 'Wind_offshore_entsoe')]
908
 
 
927
 
928
 
929
  st.subheader('Weather vs. Generation/Demand')
930
+ st.write('The below scatter plots show the relation between weather parameters (i.e., Temperature, Wind Speed) and the generation/demand data from ENTSO-E.')
931
 
932
  for weather_col in weather_columns:
933
  for actual_col in ['Load_entsoe', 'Solar_entsoe', 'Wind_onshore_entsoe', 'Wind_offshore_entsoe']:
934
  if weather_col in data.columns and actual_col in data.columns:
935
  clean_label = actual_col.replace('_entsoe', '')
936
+
937
  if weather_col == 'Temperature':
938
  fig = px.scatter(x=data[weather_col], y=data[actual_col], labels={'x': f'{weather_col} (°C)', 'y': f'{clean_label} Generation [MW]'}, color_discrete_sequence=['orange'])
939
  else: