mmmapms commited on
Commit
389e1b0
·
verified ·
1 Parent(s): fd22562

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +156 -40
app.py CHANGED
@@ -100,18 +100,23 @@ if github_token:
100
  Data_FR=load_GitHub(github_token, 'FR_Entsoe_UTC.csv', hour, after_10_min)
101
  Data_NL=load_GitHub(github_token, 'NL_Entsoe_UTC.csv', hour, after_10_min)
102
  Data_DE=load_GitHub(github_token, 'DE_Entsoe_UTC.csv', hour, after_10_min)
 
 
 
103
 
104
  Data_BE=convert_European_time(Data_BE, 'Europe/Brussels')
105
  Data_FR=convert_European_time(Data_FR, 'Europe/Paris')
106
  Data_NL=convert_European_time(Data_NL, 'Europe/Amsterdam')
107
  Data_DE=convert_European_time(Data_DE, 'Europe/Berlin')
 
 
 
108
 
109
 
110
  else:
111
  print("Please enter your GitHub Personal Access Token to proceed.")
112
 
113
 
114
- # Main layout of the app
115
  col1, col2 = st.columns([5, 2]) # Adjust the ratio to better fit your layout needs
116
  with col1:
117
  st.title("Transparency++")
@@ -125,19 +130,23 @@ with col2:
125
  with col2_2:
126
  st.image("energyville_logo.png", width=100)
127
 
 
 
 
128
  upper_space.markdown("""
129
   
130
   
131
  """, unsafe_allow_html=True)
132
 
133
-
134
-
135
  countries = {
136
  'Overall': 'Overall',
137
- 'Netherlands': 'NL',
138
- 'Germany': 'DE',
139
- 'France': 'FR',
140
  'Belgium': 'BE',
 
 
 
 
 
141
  }
142
 
143
 
@@ -159,12 +168,20 @@ if selected_country != 'Overall':
159
  else:
160
  section = None # No section is shown when "Overall" is selected
161
 
 
 
 
 
 
 
 
162
  if selected_country == 'Overall':
163
  data = None # You can set data to None or a specific dataset based on your logic
164
  section = None # No section selected when "Overall" is chosen
165
  else:
166
  country_code = countries[selected_country]
167
  if country_code == 'BE':
 
168
  data = Data_BE
169
  weather_columns = ['Temperature', 'Wind Speed Onshore', 'Wind Speed Offshore']
170
  data['Temperature'] = data['temperature_2m_8']
@@ -172,23 +189,45 @@ else:
172
  data['Wind Speed Onshore'] = data['wind_speed_100m_8']
173
 
174
  elif country_code == 'DE':
 
175
  data = Data_DE
176
  weather_columns = ['Temperature', 'Wind Speed']
177
  data['Temperature'] = data['temperature_2m']
178
  data['Wind Speed'] = data['wind_speed_100m']
179
 
180
  elif country_code == 'NL':
 
181
  data = Data_NL
182
  weather_columns = ['Temperature', 'Wind Speed']
183
  data['Temperature'] = data['temperature_2m']
184
  data['Wind Speed'] = data['wind_speed_100m']
185
 
186
  elif country_code == 'FR':
 
187
  data = Data_FR
188
  weather_columns = ['Temperature', 'Wind Speed']
189
  data['Temperature'] = data['temperature_2m']
190
  data['Wind Speed'] = data['wind_speed_100m']
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  def add_feature(df2, df_main):
193
  #df_main.index = pd.to_datetime(df_main.index)
194
  #df2.index = pd.to_datetime(df2.index)
@@ -202,16 +241,13 @@ def add_feature(df2, df_main):
202
  #data.index = data.index.tz_localize('UTC')
203
 
204
 
205
- forecast_columns = [
206
- 'Load_entsoe','Load_forecast_entsoe','Wind_onshore_entsoe','Wind_onshore_forecast_entsoe','Wind_offshore_entsoe','Wind_offshore_forecast_entsoe','Solar_entsoe','Solar_forecast_entsoe']
207
-
208
  if section == 'Data Quality':
209
 
210
  st.header('Data Quality')
211
 
212
  st.write('The table below presents the data quality metrics focusing on the percentage of missing values and the occurrence of extreme or nonsensical values for the selected country.')
213
 
214
- yesterday_midnight = pd.Timestamp(datetime.now().date() - pd.Timedelta(days=1)).replace(hour=23, minute=59, second=59)
215
 
216
  # Filter data until the end of yesterday (midnight)
217
  data_quality = data[data.index <= yesterday_midnight]
@@ -224,7 +260,10 @@ if section == 'Data Quality':
224
  'FR': { 'Solar': 17419, 'Wind Offshore': 1483, 'Wind Onshore': 22134},
225
  'DE': { 'Solar': 73821, 'Wind Offshore': 8386, 'Wind Onshore': 59915},
226
  'BE': { 'Solar': 8789, 'Wind Offshore': 2262, 'Wind Onshore': 3053},
227
- 'NL': { 'Solar': 22590, 'Wind Offshore': 3220, 'Wind Onshore': 6190},
 
 
 
228
  }
229
 
230
  if country_code not in installed_capacities:
@@ -287,13 +326,21 @@ elif section == 'Forecasts Quality':
287
  st.write('The below plot shows the time series of forecasts vs. observations provided by the ENTSO-E Transparency platform from the past week.')
288
 
289
  # Options for selecting the data to display
290
- variable_options = {
291
- "Load": ("Load_entsoe", "Load_forecast_entsoe"),
292
- "Solar": ("Solar_entsoe", "Solar_forecast_entsoe"),
293
- "Wind Onshore": ("Wind_onshore_entsoe", "Wind_onshore_forecast_entsoe"),
294
- "Wind Offshore": ("Wind_offshore_entsoe", "Wind_offshore_forecast_entsoe")
295
- }
296
-
 
 
 
 
 
 
 
 
297
  # Dropdown to select the variable
298
  selected_variable = st.selectbox("Select Variable for Line PLot", list(variable_options.keys()))
299
 
@@ -310,7 +357,7 @@ elif section == 'Forecasts Quality':
310
 
311
  # Scatter plots for error distribution
312
  st.subheader('Error Distribution')
313
- st.write('The below scatter plots show the error distribution of all four fields: Solar, Wind Onshore, Wind Offshore and Load.')
314
  selected_variable = st.selectbox("Select Variable for Error Distribution", list(variable_options.keys()))
315
 
316
  # Get the corresponding columns for the selected variable
@@ -329,7 +376,7 @@ elif section == 'Forecasts Quality':
329
 
330
  st.plotly_chart(fig)
331
 
332
-
333
 
334
  st.subheader('Accuracy Metrics (Sorted by rMAE):')
335
 
@@ -351,7 +398,11 @@ elif section == 'Forecasts Quality':
351
  st.write(output_text)
352
 
353
  data = data.loc[start_date:end_date]
354
- accuracy_metrics = pd.DataFrame(columns=['MAE', 'rMAE'], index=['Load', 'Solar', 'Wind Onshore', 'Wind Offshore'])
 
 
 
 
355
 
356
  for i in range(0, len(forecast_columns), 2):
357
  actual_col = forecast_columns[i]
@@ -433,7 +484,7 @@ elif section == 'Forecasts Quality':
433
  st.plotly_chart(fig, use_container_width=True, config={'displayModeBar': False}, className="small-chart")
434
 
435
  st.subheader('ACF plots of Errors')
436
- 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 Onshore, Wind Offshore and Load.')
437
 
438
  # Dropdown to select the variable
439
  selected_variable = st.selectbox("Select Variable for ACF of Errors", list(variable_options.keys()))
@@ -459,7 +510,7 @@ elif section == 'Forecasts Quality':
459
  elif section == 'Insights':
460
  st.header("Insights")
461
 
462
- st.write('The scatter plots below are created to explore possible correlations between the data fields: Solar, Wind Onshore, Wind Offshore, Load, and Weather Features.')
463
  # Add a selection box for the data resolution (weekly, daily, hourly)
464
  data_2024 = data[data.index.year == 2024]
465
 
@@ -472,7 +523,11 @@ elif section == 'Insights':
472
  resampled_data = data_2024.resample('D').mean() # Resample to daily mean
473
 
474
  # Select the necessary columns for the scatter plot
475
- selected_columns = ['Load_entsoe', 'Solar_entsoe', 'Wind_offshore_entsoe', 'Wind_onshore_entsoe'] + weather_columns
 
 
 
 
476
  selected_df = resampled_data[selected_columns]
477
  selected_df.columns = [col.replace('_entsoe', '').replace('_', ' ') for col in selected_df.columns]
478
 
@@ -491,22 +546,42 @@ elif selected_country == 'Overall':
491
  st.subheader("Net Load Error Map")
492
  st.write("""
493
  The net load error map highlights the error in the forecasted versus actual net load for each country.
494
- Hover over each country to see details on the latest net load error and the timestamp of the last recorded data.
495
  """)
496
 
 
 
 
 
 
 
497
  def plot_net_load_error_map(data_dict):
498
  # Define forecast columns used in calculation
499
 
500
- def calculate_net_load_error(df):
 
501
  filter_df = df[forecast_columns].dropna()
502
- net_load = filter_df['Load_entsoe'] - filter_df['Wind_onshore_entsoe'] - filter_df['Wind_offshore_entsoe'] - filter_df['Solar_entsoe']
503
- net_load_forecast = filter_df['Load_forecast_entsoe'] - filter_df['Wind_onshore_forecast_entsoe'] - filter_df['Wind_offshore_forecast_entsoe'] - filter_df['Solar_forecast_entsoe']
 
 
 
 
 
 
 
 
 
 
 
 
504
  error = (net_load_forecast - net_load).iloc[-1]
505
  date = filter_df.index[-1].strftime("%Y-%m-%d %H:%M") # Get the latest date in string format
 
506
  return error, date
507
 
508
  # Calculate net load errors and dates for each country
509
- net_load_errors = {country_name: calculate_net_load_error(data) for country_name, data in data_dict.items()}
510
 
511
  # Create DataFrame for Folium with additional date column
512
  df_net_load_error = pd.DataFrame({
@@ -535,7 +610,7 @@ elif selected_country == 'Overall':
535
  feature["properties"]["date"] = row.iloc[0]["date"]
536
 
537
  # Initialize the Folium map centered on Central Europe
538
- m = folium.Map(location=[51, 10], zoom_start=5, tiles="cartodb positron")
539
 
540
  # Add choropleth layer to map net load errors by country
541
  folium.Choropleth(
@@ -544,7 +619,7 @@ elif selected_country == 'Overall':
544
  data=df_net_load_error,
545
  columns=["country", "net_load_error"],
546
  key_on="feature.properties.name",
547
- fill_color="RdYlBu", # Use a more vibrant color palette
548
  fill_opacity=0.7,
549
  line_opacity=0.5,
550
  line_color="black", # Neutral border color
@@ -565,18 +640,22 @@ elif selected_country == 'Overall':
565
  # Display Folium map in Streamlit
566
  st_folium(m, width=700, height=600)
567
 
568
- # Data dictionary with full country names
569
  data_dict = {
570
  'Belgium': Data_BE,
571
  'France': Data_FR,
572
  'Germany': Data_DE,
573
- 'Netherlands': Data_NL
 
 
 
574
  }
 
575
  plot_net_load_error_map(data_dict)
576
 
577
  st.subheader("rMAE of Forecasts published on ENTSO-E TP")
578
- st.write("""The rMAE of Forecasts chart compares the forecast accuracy of the predictions published by ENTSO-E Transparency Platform for Belgium, Germany, France, and the Netherlands. It shows the rMAE for onshore wind, offshore wind, solar, and load demand, highlighting how well forecasts perform relative to a basic persistence model across these countries and energy sectors.""")
579
 
 
580
  def calculate_mae(actual, forecast):
581
  return np.mean(np.abs(actual - forecast))
582
 
@@ -589,32 +668,69 @@ elif selected_country == 'Overall':
589
  rmae = {}
590
  rmae['Load'] = calculate_mae(df['Load_entsoe'], df['Load_forecast_entsoe']) / calculate_persistence_mae(df['Load_entsoe'], 168)
591
  rmae['Wind_onshore'] = calculate_mae(df['Wind_onshore_entsoe'], df['Wind_onshore_forecast_entsoe']) / calculate_persistence_mae(df['Wind_onshore_entsoe'], 24)
592
- rmae['Wind_offshore'] = calculate_mae(df['Wind_offshore_entsoe'], df['Wind_offshore_forecast_entsoe']) / calculate_persistence_mae(df['Wind_offshore_entsoe'], 24)
 
 
 
 
 
 
593
  rmae['Solar'] = calculate_mae(df['Solar_entsoe'], df['Solar_forecast_entsoe']) / calculate_persistence_mae(df['Solar_entsoe'], 24)
 
594
  return rmae
595
 
596
  # Function to create rMAE DataFrame
597
  def create_rmae_dataframe(data_dict):
 
598
  rmae_values = {'Country': [], 'Load': [], 'Wind_onshore': [], 'Wind_offshore': [], 'Solar': []}
 
599
  for country_name, df in data_dict.items():
 
600
  df_filtered = df[forecast_columns].dropna()
601
  rmae = calculate_rmae_for_country(df_filtered)
 
602
  rmae_values['Country'].append(country_name)
603
- for key in rmae:
604
- rmae_values[key].append(rmae[key])
 
 
 
 
 
 
 
 
605
  return pd.DataFrame(rmae_values)
606
 
607
  # Function to plot radar chart
608
  def plot_rmae_radar_chart(rmae_df):
609
  fig = go.Figure()
610
- angles = ['Load', 'Wind_onshore', 'Wind_offshore', 'Solar']
 
 
 
 
 
611
  for _, row in rmae_df.iterrows():
612
- fig.add_trace(go.Scatterpolar(r=[row[angle] for angle in angles], theta=angles, fill='toself', name=row['Country']))
613
- fig.update_layout(polar=dict(radialaxis=dict(visible=True, range=[0, 1.2])), showlegend=True, title="rMAE Radar Chart by Country")
 
 
 
 
 
 
 
 
 
 
 
 
614
  st.plotly_chart(fig)
615
 
616
  # Main execution to create and display radar plot
617
  rmae_df = create_rmae_dataframe(data_dict)
618
  plot_rmae_radar_chart(rmae_df)
619
 
 
620
 
 
100
  Data_FR=load_GitHub(github_token, 'FR_Entsoe_UTC.csv', hour, after_10_min)
101
  Data_NL=load_GitHub(github_token, 'NL_Entsoe_UTC.csv', hour, after_10_min)
102
  Data_DE=load_GitHub(github_token, 'DE_Entsoe_UTC.csv', hour, after_10_min)
103
+ Data_PT=load_GitHub(github_token, 'PT_Entsoe_UTC.csv', hour, after_10_min)
104
+ Data_ES=load_GitHub(github_token, 'ES_Entsoe_UTC.csv', hour, after_10_min)
105
+ Data_AT=load_GitHub(github_token, 'AT_Entsoe_UTC.csv', hour, after_10_min)
106
 
107
  Data_BE=convert_European_time(Data_BE, 'Europe/Brussels')
108
  Data_FR=convert_European_time(Data_FR, 'Europe/Paris')
109
  Data_NL=convert_European_time(Data_NL, 'Europe/Amsterdam')
110
  Data_DE=convert_European_time(Data_DE, 'Europe/Berlin')
111
+ Data_PT=convert_European_time(Data_PT, 'Europe/Lisbon')
112
+ Data_ES=convert_European_time(Data_ES, 'Europe/Madrid')
113
+ Data_AT=convert_European_time(Data_AT, 'Europe/Vienna')
114
 
115
 
116
  else:
117
  print("Please enter your GitHub Personal Access Token to proceed.")
118
 
119
 
 
120
  col1, col2 = st.columns([5, 2]) # Adjust the ratio to better fit your layout needs
121
  with col1:
122
  st.title("Transparency++")
 
130
  with col2_2:
131
  st.image("energyville_logo.png", width=100)
132
 
133
+
134
+ st.write("**Evaluate and analyze ENTSO-E Transparency Platform data quality, forecast accuracy, and energy trends for Portugal, Spain, Belgium, France, Germany, Austria, and the Netherlands.**")
135
+
136
  upper_space.markdown("""
137
  &nbsp;
138
  &nbsp;
139
  """, unsafe_allow_html=True)
140
 
 
 
141
  countries = {
142
  'Overall': 'Overall',
143
+ 'Austria': 'AT',
 
 
144
  'Belgium': 'BE',
145
+ 'France': 'FR',
146
+ 'Germany': 'DE',
147
+ 'Netherlands': 'NL',
148
+ 'Portugal': 'PT',
149
+ 'Spain': 'ES',
150
  }
151
 
152
 
 
168
  else:
169
  section = None # No section is shown when "Overall" is selected
170
 
171
+
172
+ forecast_columns_with_wind_offshore = [
173
+ 'Load_entsoe','Load_forecast_entsoe','Wind_onshore_entsoe','Wind_onshore_forecast_entsoe','Wind_offshore_entsoe','Wind_offshore_forecast_entsoe','Solar_entsoe','Solar_forecast_entsoe']
174
+
175
+ forecast_columns_no_wind_offshore = [
176
+ 'Load_entsoe','Load_forecast_entsoe','Wind_onshore_entsoe','Wind_onshore_forecast_entsoe','Solar_entsoe','Solar_forecast_entsoe']
177
+
178
  if selected_country == 'Overall':
179
  data = None # You can set data to None or a specific dataset based on your logic
180
  section = None # No section selected when "Overall" is chosen
181
  else:
182
  country_code = countries[selected_country]
183
  if country_code == 'BE':
184
+ forecast_columns=forecast_columns_with_wind_offshore
185
  data = Data_BE
186
  weather_columns = ['Temperature', 'Wind Speed Onshore', 'Wind Speed Offshore']
187
  data['Temperature'] = data['temperature_2m_8']
 
189
  data['Wind Speed Onshore'] = data['wind_speed_100m_8']
190
 
191
  elif country_code == 'DE':
192
+ forecast_columns=forecast_columns_with_wind_offshore
193
  data = Data_DE
194
  weather_columns = ['Temperature', 'Wind Speed']
195
  data['Temperature'] = data['temperature_2m']
196
  data['Wind Speed'] = data['wind_speed_100m']
197
 
198
  elif country_code == 'NL':
199
+ forecast_columns=forecast_columns_with_wind_offshore
200
  data = Data_NL
201
  weather_columns = ['Temperature', 'Wind Speed']
202
  data['Temperature'] = data['temperature_2m']
203
  data['Wind Speed'] = data['wind_speed_100m']
204
 
205
  elif country_code == 'FR':
206
+ forecast_columns=forecast_columns_with_wind_offshore
207
  data = Data_FR
208
  weather_columns = ['Temperature', 'Wind Speed']
209
  data['Temperature'] = data['temperature_2m']
210
  data['Wind Speed'] = data['wind_speed_100m']
211
 
212
+ elif country_code == 'PT':
213
+ forecast_columns=forecast_columns_with_wind_offshore
214
+ data = Data_PT
215
+ weather_columns = ['Temperature', 'Wind Speed']
216
+ data['Temperature'] = data['temperature_2m']
217
+ data['Wind Speed'] = data['wind_speed_100m']
218
+ elif country_code == 'AT':
219
+ forecast_columns=forecast_columns_no_wind_offshore
220
+ data = Data_AT
221
+ weather_columns = ['Temperature', 'Wind Speed']
222
+ data['Temperature'] = data['temperature_2m']
223
+ data['Wind Speed'] = data['wind_speed_100m']
224
+ elif country_code == 'ES':
225
+ forecast_columns=forecast_columns_no_wind_offshore
226
+ data = Data_ES
227
+ weather_columns = ['Temperature', 'Wind Speed']
228
+ data['Temperature'] = data['temperature_2m']
229
+ data['Wind Speed'] = data['wind_speed_100m']
230
+
231
  def add_feature(df2, df_main):
232
  #df_main.index = pd.to_datetime(df_main.index)
233
  #df2.index = pd.to_datetime(df2.index)
 
241
  #data.index = data.index.tz_localize('UTC')
242
 
243
 
 
 
 
244
  if section == 'Data Quality':
245
 
246
  st.header('Data Quality')
247
 
248
  st.write('The table below presents the data quality metrics focusing on the percentage of missing values and the occurrence of extreme or nonsensical values for the selected country.')
249
 
250
+ yesterday_midnight = pd.Timestamp(datetime.datetime.now().date() - pd.Timedelta(days=1)).replace(hour=23, minute=59, second=59)
251
 
252
  # Filter data until the end of yesterday (midnight)
253
  data_quality = data[data.index <= yesterday_midnight]
 
260
  'FR': { 'Solar': 17419, 'Wind Offshore': 1483, 'Wind Onshore': 22134},
261
  'DE': { 'Solar': 73821, 'Wind Offshore': 8386, 'Wind Onshore': 59915},
262
  'BE': { 'Solar': 8789, 'Wind Offshore': 2262, 'Wind Onshore': 3053},
263
+ 'NL': { 'Solar': 22590, 'Wind Offshore': 3220, 'Wind Onshore': 6190},
264
+ 'PT': { 'Solar': 1811, 'Wind Offshore': 25, 'Wind Onshore': 5333},
265
+ 'ES': { 'Solar': 23867, 'Wind Onshore': 30159},
266
+ 'AT': { 'Solar': 7294, 'Wind Onshore': 4021 }
267
  }
268
 
269
  if country_code not in installed_capacities:
 
326
  st.write('The below plot shows the time series of forecasts vs. observations provided by the ENTSO-E Transparency platform from the past week.')
327
 
328
  # Options for selecting the data to display
329
+ if country_code!='ES' and country_code!='AT':
330
+
331
+ variable_options = {
332
+ "Load": ("Load_entsoe", "Load_forecast_entsoe"),
333
+ "Solar": ("Solar_entsoe", "Solar_forecast_entsoe"),
334
+ "Wind Onshore": ("Wind_onshore_entsoe", "Wind_onshore_forecast_entsoe"),
335
+ "Wind Offshore": ("Wind_offshore_entsoe", "Wind_offshore_forecast_entsoe")
336
+ }
337
+ else:
338
+ variable_options = {
339
+ "Load": ("Load_entsoe", "Load_forecast_entsoe"),
340
+ "Solar": ("Solar_entsoe", "Solar_forecast_entsoe"),
341
+ "Wind Onshore": ("Wind_onshore_entsoe", "Wind_onshore_forecast_entsoe"),
342
+ }
343
+
344
  # Dropdown to select the variable
345
  selected_variable = st.selectbox("Select Variable for Line PLot", list(variable_options.keys()))
346
 
 
357
 
358
  # Scatter plots for error distribution
359
  st.subheader('Error Distribution')
360
+ st.write('The below scatter plots show the error distribution of all fields: Solar, Wind and Load.')
361
  selected_variable = st.selectbox("Select Variable for Error Distribution", list(variable_options.keys()))
362
 
363
  # Get the corresponding columns for the selected variable
 
376
 
377
  st.plotly_chart(fig)
378
 
379
+
380
 
381
  st.subheader('Accuracy Metrics (Sorted by rMAE):')
382
 
 
398
  st.write(output_text)
399
 
400
  data = data.loc[start_date:end_date]
401
+
402
+ if country_code!='ES' and country_code!='AT':
403
+ accuracy_metrics = pd.DataFrame(columns=['MAE', 'rMAE'], index=['Load', 'Solar', 'Wind Onshore', 'Wind Offshore'])
404
+ else:
405
+ accuracy_metrics = pd.DataFrame(columns=['MAE', 'rMAE'], index=['Load', 'Solar', 'Wind Onshore'])
406
 
407
  for i in range(0, len(forecast_columns), 2):
408
  actual_col = forecast_columns[i]
 
484
  st.plotly_chart(fig, use_container_width=True, config={'displayModeBar': False}, className="small-chart")
485
 
486
  st.subheader('ACF plots of Errors')
487
+ 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.')
488
 
489
  # Dropdown to select the variable
490
  selected_variable = st.selectbox("Select Variable for ACF of Errors", list(variable_options.keys()))
 
510
  elif section == 'Insights':
511
  st.header("Insights")
512
 
513
+ st.write('The scatter plots below are created to explore possible correlations between the data fields: Solar, Wind Onshore, Wind Offshore (if any), Load, and Weather Features.')
514
  # Add a selection box for the data resolution (weekly, daily, hourly)
515
  data_2024 = data[data.index.year == 2024]
516
 
 
523
  resampled_data = data_2024.resample('D').mean() # Resample to daily mean
524
 
525
  # Select the necessary columns for the scatter plot
526
+ if country_code!='ES' and country_code!='AT':
527
+ selected_columns = ['Load_entsoe', 'Solar_entsoe', 'Wind_offshore_entsoe', 'Wind_onshore_entsoe'] + weather_columns
528
+ else:
529
+ selected_columns = ['Load_entsoe', 'Solar_entsoe', 'Wind_onshore_entsoe'] + weather_columns
530
+
531
  selected_df = resampled_data[selected_columns]
532
  selected_df.columns = [col.replace('_entsoe', '').replace('_', ' ') for col in selected_df.columns]
533
 
 
546
  st.subheader("Net Load Error Map")
547
  st.write("""
548
  The net load error map highlights the error in the forecasted versus actual net load for each country.
549
+ Hover over each country to see details on the latest net load error and the timestamp (with the time zone of the corresponding country) of the last recorded data.
550
  """)
551
 
552
+ def get_forecast_columns(country_code):
553
+ if country_code in ['Belgium', 'Germany', 'Netherlands', 'France', 'Portugal']:
554
+ return ['Load_entsoe', 'Wind_onshore_entsoe', 'Solar_entsoe', 'Load_forecast_entsoe', 'Wind_onshore_forecast_entsoe', 'Solar_forecast_entsoe', 'Wind_offshore_entsoe', 'Wind_offshore_forecast_entsoe']
555
+ else:
556
+ return ['Load_entsoe', 'Wind_onshore_entsoe', 'Solar_entsoe', 'Load_forecast_entsoe', 'Wind_onshore_forecast_entsoe', 'Solar_forecast_entsoe']
557
+
558
  def plot_net_load_error_map(data_dict):
559
  # Define forecast columns used in calculation
560
 
561
+ def calculate_net_load_error(df, country_code):
562
+ forecast_columns = get_forecast_columns(country_code)
563
  filter_df = df[forecast_columns].dropna()
564
+
565
+ # Initialize net_load and net_load_forecast with Load and other available data
566
+ net_load = filter_df['Load_entsoe'] - filter_df['Wind_onshore_entsoe'] - filter_df['Solar_entsoe']
567
+ net_load_forecast = filter_df['Load_forecast_entsoe'] - filter_df['Wind_onshore_forecast_entsoe'] - filter_df['Solar_forecast_entsoe']
568
+
569
+ # Subtract Wind_offshore_entsoe if the column exists
570
+ if 'Wind_offshore_entsoe' in filter_df.columns:
571
+ net_load -= filter_df['Wind_offshore_entsoe']
572
+
573
+ # Subtract Wind_offshore_forecast_entsoe if the column exists
574
+ if 'Wind_offshore_forecast_entsoe' in filter_df.columns:
575
+ net_load_forecast -= filter_df['Wind_offshore_forecast_entsoe']
576
+
577
+ # Calculate the error based on the latest values
578
  error = (net_load_forecast - net_load).iloc[-1]
579
  date = filter_df.index[-1].strftime("%Y-%m-%d %H:%M") # Get the latest date in string format
580
+
581
  return error, date
582
 
583
  # Calculate net load errors and dates for each country
584
+ net_load_errors = {country_name: calculate_net_load_error(data, country_name) for country_name, data in data_dict.items()}
585
 
586
  # Create DataFrame for Folium with additional date column
587
  df_net_load_error = pd.DataFrame({
 
610
  feature["properties"]["date"] = row.iloc[0]["date"]
611
 
612
  # Initialize the Folium map centered on Central Europe
613
+ m = folium.Map(location=[46.6034, 1.8883], zoom_start=4.5, tiles="cartodb positron")
614
 
615
  # Add choropleth layer to map net load errors by country
616
  folium.Choropleth(
 
619
  data=df_net_load_error,
620
  columns=["country", "net_load_error"],
621
  key_on="feature.properties.name",
622
+ fill_color= "RdYlBu", #"RdYlBu", # Use a more vibrant color palette
623
  fill_opacity=0.7,
624
  line_opacity=0.5,
625
  line_color="black", # Neutral border color
 
640
  # Display Folium map in Streamlit
641
  st_folium(m, width=700, height=600)
642
 
 
643
  data_dict = {
644
  'Belgium': Data_BE,
645
  'France': Data_FR,
646
  'Germany': Data_DE,
647
+ 'Netherlands': Data_NL,
648
+ 'Portugal': Data_PT,
649
+ 'Austria': Data_AT,
650
+ 'Spain': Data_ES,
651
  }
652
+
653
  plot_net_load_error_map(data_dict)
654
 
655
  st.subheader("rMAE of Forecasts published on ENTSO-E TP")
656
+ st.write("""The rMAE of Forecasts chart compares the forecast accuracy of the predictions published by ENTSO-E Transparency Platform for Portugal, Spain, Belgium, France, Germany, Austria, and the Netherlands. It shows the rMAE for onshore wind, offshore wind (if any), solar, and load demand, highlighting how well forecasts perform relative to a basic persistence model across these countries and energy sectors.""")
657
 
658
+ # Function to calculate MAE
659
  def calculate_mae(actual, forecast):
660
  return np.mean(np.abs(actual - forecast))
661
 
 
668
  rmae = {}
669
  rmae['Load'] = calculate_mae(df['Load_entsoe'], df['Load_forecast_entsoe']) / calculate_persistence_mae(df['Load_entsoe'], 168)
670
  rmae['Wind_onshore'] = calculate_mae(df['Wind_onshore_entsoe'], df['Wind_onshore_forecast_entsoe']) / calculate_persistence_mae(df['Wind_onshore_entsoe'], 24)
671
+
672
+ # Only calculate Wind_offshore rMAE if the columns exist
673
+ if 'Wind_offshore_entsoe' in df.columns and 'Wind_offshore_forecast_entsoe' in df.columns:
674
+ rmae['Wind_offshore'] = calculate_mae(df['Wind_offshore_entsoe'], df['Wind_offshore_forecast_entsoe']) / calculate_persistence_mae(df['Wind_offshore_entsoe'], 24)
675
+ else:
676
+ rmae['Wind_offshore'] = None # Mark as None if not applicable
677
+
678
  rmae['Solar'] = calculate_mae(df['Solar_entsoe'], df['Solar_forecast_entsoe']) / calculate_persistence_mae(df['Solar_entsoe'], 24)
679
+
680
  return rmae
681
 
682
  # Function to create rMAE DataFrame
683
  def create_rmae_dataframe(data_dict):
684
+
685
  rmae_values = {'Country': [], 'Load': [], 'Wind_onshore': [], 'Wind_offshore': [], 'Solar': []}
686
+
687
  for country_name, df in data_dict.items():
688
+ forecast_columns=get_forecast_columns(country_name)
689
  df_filtered = df[forecast_columns].dropna()
690
  rmae = calculate_rmae_for_country(df_filtered)
691
+
692
  rmae_values['Country'].append(country_name)
693
+ rmae_values['Load'].append(rmae['Load'])
694
+ rmae_values['Wind_onshore'].append(rmae['Wind_onshore'])
695
+ rmae_values['Solar'].append(rmae['Solar'])
696
+
697
+ # Append Wind_offshore rMAE only if it's not None (i.e., the country has offshore wind data)
698
+ if rmae['Wind_offshore'] is not None:
699
+ rmae_values['Wind_offshore'].append(rmae['Wind_offshore'])
700
+ else:
701
+ rmae_values['Wind_offshore'].append(np.nan) # Insert NaN for countries without offshore wind
702
+
703
  return pd.DataFrame(rmae_values)
704
 
705
  # Function to plot radar chart
706
  def plot_rmae_radar_chart(rmae_df):
707
  fig = go.Figure()
708
+
709
+ # Dynamically adjust angles to exclude Wind_offshore if all values are NaN
710
+ angles = ['Load', 'Wind_onshore', 'Solar']
711
+ if not rmae_df['Wind_offshore'].isna().all(): # Only include Wind_offshore if it's not NaN for all countries
712
+ angles.append('Wind_offshore')
713
+
714
  for _, row in rmae_df.iterrows():
715
+ fig.add_trace(go.Scatterpolar(
716
+ r=[row[angle] for angle in angles],
717
+ theta=angles,
718
+ fill='toself',
719
+ name=row['Country']
720
+ ))
721
+
722
+ fig.update_layout(
723
+ polar=dict(
724
+ radialaxis=dict(visible=True, range=[0, 1.2])
725
+ ),
726
+ showlegend=True,
727
+ title="rMAE Radar Chart by Country"
728
+ )
729
  st.plotly_chart(fig)
730
 
731
  # Main execution to create and display radar plot
732
  rmae_df = create_rmae_dataframe(data_dict)
733
  plot_rmae_radar_chart(rmae_df)
734
 
735
+
736