attila-balint-kul commited on
Commit
f7b117b
1 Parent(s): fa64f07

Upload 8 files

Browse files
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .streamlit/
.streamlit/secrets.toml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ wandb_entity = "attila-balint-kul"
2
+ wandb_api_key = "70458ee5feafed530c7656bada194778e034813b"
app.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ from components import (
4
+ buildings_view,
5
+ models_view,
6
+ performance_view,
7
+ computation_view,
8
+ logos,
9
+ model_selector,
10
+ header,
11
+ overview_view,
12
+ )
13
+ import utils
14
+
15
+ PAGES = [
16
+ "Overview",
17
+ "Buildings",
18
+ "Models",
19
+ "Performance",
20
+ "Computational Resources",
21
+ ]
22
+
23
+
24
+ st.set_page_config(page_title="Gas Demand Dashboard", layout="wide")
25
+
26
+
27
+ @st.cache_data(ttl=86400)
28
+ def fetch_data():
29
+ return utils.get_wandb_data(
30
+ entity=st.secrets["wandb_entity"],
31
+ project="enfobench-gas-demand",
32
+ api_key=st.secrets["wandb_api_key"],
33
+ job_type="metrics",
34
+ )
35
+
36
+
37
+ # Load data
38
+ data = fetch_data()
39
+
40
+ # Extract models
41
+ models = sorted(data["model"].unique().tolist())
42
+
43
+
44
+ with st.sidebar:
45
+ logos()
46
+ view = st.selectbox("View", PAGES, index=0)
47
+
48
+ if view == "Performance" or view == "Computational Resources":
49
+ models_to_plot = model_selector(models)
50
+
51
+ if view == "Overview":
52
+ st.header("Sources")
53
+ st.link_button("GitHub Repository", url="https://github.com/attila-balint-kul/energy-forecast-benchmark-toolkit", use_container_width=True)
54
+ st.link_button("Documentation", url="https://attila-balint-kul.github.io/energy-forecast-benchmark-toolkit/", use_container_width=True)
55
+ st.link_button("Electricity Demand Dataset", url="https://huggingface.co/datasets/EDS-lab/electricity-demand", use_container_width=True)
56
+ st.link_button("HuggingFace Organization", url="https://huggingface.co/EDS-lab", use_container_width=True)
57
+
58
+ st.header("Other Dashboards")
59
+ st.link_button("Electricity Demand", url="https://huggingface.co/spaces/EDS-lab/EnFoBench-ElectricityDemand", use_container_width=True)
60
+ st.link_button("PV Generation", url="https://huggingface.co/spaces/EDS-lab/EnFoBench-PVGeneration", use_container_width=True)
61
+
62
+ st.header("Refresh data")
63
+ refresh = st.button(
64
+ "Refresh", use_container_width=True, help="Fetch the latest data from W&B"
65
+ )
66
+ if refresh:
67
+ fetch_data.clear()
68
+ st.rerun()
69
+
70
+
71
+ header()
72
+
73
+ if view == "Overview":
74
+ overview_view(data)
75
+ elif view == "Buildings":
76
+ buildings_view(data)
77
+ elif view == "Models":
78
+ models_view(data)
79
+ elif view == "Performance":
80
+ performance_view(data, models_to_plot)
81
+ elif view == "Computational Resources":
82
+ computation_view(data, models_to_plot)
83
+ else:
84
+ st.write("Not implemented yet")
components.py ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ import plotly.express as px
4
+
5
+ from utils import get_leaderboard
6
+
7
+
8
+ def header() -> None:
9
+ st.title("EnFoBench - Gas Demand")
10
+ st.divider()
11
+
12
+
13
+ def logos() -> None:
14
+ left, right = st.columns(2)
15
+ with left:
16
+ st.image("./images/ku_leuven_logo.png")
17
+ with right:
18
+ st.image("./images/energyville_logo.png")
19
+
20
+
21
+ def model_selector(models: list[str]) -> set[str]:
22
+ # Group models by their prefix
23
+ model_groups: dict[str, list[str]] = {}
24
+ for model in models:
25
+ group, model_name = model.split(".", maxsplit=1)
26
+ if group not in model_groups:
27
+ model_groups[group] = []
28
+ model_groups[group].append(model_name)
29
+
30
+ models_to_plot = set()
31
+
32
+ st.header("Models to include")
33
+ left, right = st.columns(2)
34
+ with left:
35
+ select_none = st.button("Select None", use_container_width=True)
36
+ if select_none:
37
+ for model in models:
38
+ st.session_state[model] = False
39
+ with right:
40
+ select_all = st.button("Select All", use_container_width=True)
41
+ if select_all:
42
+ for model in models:
43
+ st.session_state[model] = True
44
+
45
+ for model_group, models in model_groups.items():
46
+ st.text(model_group)
47
+ for model_name in models:
48
+ to_plot = st.checkbox(
49
+ model_name, value=True, key=f"{model_group}.{model_name}"
50
+ )
51
+ if to_plot:
52
+ models_to_plot.add(f"{model_group}.{model_name}")
53
+ return models_to_plot
54
+
55
+
56
+ def overview_view(data):
57
+ st.markdown(
58
+ """
59
+ [EnFoBench](https://github.com/attila-balint-kul/energy-forecast-benchmark-toolkit)
60
+ is a community driven benchmarking framework for energy forecasting models.
61
+
62
+ This dashboard presents the results of the gas demand forecasting usecase. All models were cross-validated
63
+ on **365 days** of day ahead forecasting horizon *(10AM until midnight of the next day)*.
64
+ """
65
+ )
66
+
67
+ st.divider()
68
+ st.markdown("## Leaderboard")
69
+
70
+ leaderboard = get_leaderboard(data, ["MAE.mean", "RMSE.mean", "rMAE.mean"])
71
+
72
+ left, middle, right = st.columns(3)
73
+ with left:
74
+ best_models_mae = (
75
+ leaderboard.sort_values("MAE.mean", ascending=False)
76
+ .head(10)
77
+ .sort_values("MAE.mean")
78
+ )
79
+ fig = px.bar(best_models_mae, x="MAE.mean", y=best_models_mae.index)
80
+ fig.update_layout(
81
+ title="Top 10 models by MAE", xaxis_title="", yaxis_title="Model"
82
+ )
83
+ st.plotly_chart(fig, use_container_width=True)
84
+
85
+ with middle:
86
+ best_models_mae = (
87
+ leaderboard.sort_values("RMSE.mean", ascending=False)
88
+ .head(10)
89
+ .sort_values("RMSE.mean")
90
+ )
91
+ fig = px.bar(best_models_mae, x="RMSE.mean", y=best_models_mae.index)
92
+ fig.update_layout(title="Top 10 models by RMSE", xaxis_title="", yaxis_title="")
93
+ st.plotly_chart(fig, use_container_width=True)
94
+
95
+ with right:
96
+ best_models_mae = (
97
+ leaderboard.sort_values("rMAE.mean", ascending=False)
98
+ .head(10)
99
+ .sort_values("rMAE.mean")
100
+ )
101
+ fig = px.bar(best_models_mae, x="rMAE.mean", y=best_models_mae.index)
102
+ fig.update_layout(title="Top 10 models by rMAE", xaxis_title="", yaxis_title="")
103
+ st.plotly_chart(fig, use_container_width=True)
104
+
105
+ st.dataframe(leaderboard, use_container_width=True)
106
+
107
+
108
+ def buildings_view(data):
109
+ buildings = (
110
+ data[
111
+ [
112
+ "unique_id",
113
+ "metadata.cluster_size",
114
+ "metadata.building_class",
115
+ "metadata.location_id",
116
+ "metadata.timezone",
117
+ "dataset.available_history.days",
118
+ ]
119
+ ]
120
+ .groupby("unique_id")
121
+ .first()
122
+ .rename(
123
+ columns={
124
+ "metadata.cluster_size": "Cluster size",
125
+ "metadata.building_class": "Building class",
126
+ "metadata.location_id": "Location ID",
127
+ "metadata.timezone": "Timezone",
128
+ "dataset.available_history.days": "Available history (days)",
129
+ }
130
+ )
131
+ )
132
+
133
+ st.metric("Number of buildings", len(buildings))
134
+ st.divider()
135
+
136
+ st.markdown("### Buildings")
137
+ st.dataframe(
138
+ buildings,
139
+ use_container_width=True,
140
+ column_config={
141
+ "Available history (days)": st.column_config.ProgressColumn(
142
+ "Available history (days)",
143
+ help="Available training data during the first prediction.",
144
+ format="%f",
145
+ min_value=0,
146
+ max_value=float(buildings["Available history (days)"].max()),
147
+ ),
148
+ },
149
+ )
150
+
151
+ left, right = st.columns(2, gap="large")
152
+ with left:
153
+ st.markdown("#### Building classes")
154
+ fig = px.pie(
155
+ buildings.groupby("Building class").size().reset_index(),
156
+ values=0,
157
+ names="Building class",
158
+ )
159
+ st.plotly_chart(fig, use_container_width=True)
160
+
161
+ with right:
162
+ st.markdown("#### Timezones")
163
+ fig = px.pie(
164
+ buildings.groupby("Timezone").size().reset_index(),
165
+ values=0,
166
+ names="Timezone",
167
+ )
168
+ st.plotly_chart(fig, use_container_width=True)
169
+
170
+
171
+ def models_view(data):
172
+ models = (
173
+ data[
174
+ [
175
+ "model",
176
+ "cv_config.folds",
177
+ "cv_config.horizon",
178
+ "cv_config.step",
179
+ "cv_config.time",
180
+ "model_info.repository",
181
+ "model_info.tag",
182
+ "model_info.variate_type",
183
+ ]
184
+ ]
185
+ .groupby("model")
186
+ .first()
187
+ .rename(
188
+ columns={
189
+ "cv_config.folds": "CV Folds",
190
+ "cv_config.horizon": "CV Horizon",
191
+ "cv_config.step": "CV Step",
192
+ "cv_config.time": "CV Time",
193
+ "model_info.repository": "Image Repository",
194
+ "model_info.tag": "Image Tag",
195
+ "model_info.variate_type": "Variate type",
196
+ }
197
+ )
198
+ )
199
+
200
+ st.metric("Number of models", len(models))
201
+ st.divider()
202
+
203
+ st.markdown("### Models")
204
+ st.dataframe(models, use_container_width=True)
205
+
206
+ left, right = st.columns(2, gap="large")
207
+ with left:
208
+ st.markdown("#### Variate types")
209
+ fig = px.pie(
210
+ models.groupby("Variate type").size().reset_index(),
211
+ values=0,
212
+ names="Variate type",
213
+ )
214
+ st.plotly_chart(fig, use_container_width=True)
215
+
216
+ with right:
217
+ st.markdown("#### Frameworks")
218
+ _df = models.copy()
219
+ _df["Framework"] = _df.index.str.split(".").str[0]
220
+ fig = px.pie(
221
+ _df.groupby("Framework").size().reset_index(),
222
+ values=0,
223
+ names="Framework",
224
+ )
225
+ st.plotly_chart(fig, use_container_width=True)
226
+
227
+
228
+ def performance_view(data: pd.DataFrame, models_to_plot: set[str]):
229
+ data_to_plot = data[data["model"].isin(models_to_plot)].sort_values(
230
+ by="model", ascending=True
231
+ )
232
+
233
+ left, right = st.columns(2, gap="small")
234
+ with left:
235
+ metric = st.selectbox("Metric", ["MAE", "RMSE", "MBE", "rMAE"], index=0)
236
+ with right:
237
+ aggregation = st.selectbox(
238
+ "Aggregation", ["min", "mean", "median", "max", "std"], index=1
239
+ )
240
+ st.markdown(f"#### {aggregation.capitalize()} {metric} per building")
241
+
242
+ rank_df = (
243
+ data_to_plot.groupby(["model"])
244
+ .agg("median", numeric_only=True)
245
+ .sort_values(by=f"{metric}.{aggregation}")
246
+ .reset_index()
247
+ .rename_axis("rank")
248
+ .reset_index()[["rank", "model"]]
249
+ )
250
+
251
+ fig = px.box(
252
+ data_to_plot.merge(rank_df, on="model").sort_values(by="rank"),
253
+ x=f"{metric}.{aggregation}",
254
+ y="model",
255
+ color="model",
256
+ points="all",
257
+ )
258
+ fig.update_layout(showlegend=False, height=40 * len(models_to_plot))
259
+ st.plotly_chart(fig, use_container_width=True)
260
+
261
+ st.divider()
262
+
263
+ left, right = st.columns(2, gap="large")
264
+ with left:
265
+ x_metric = st.selectbox(
266
+ "Metric", ["MAE", "RMSE", "MBE", "rMAE"], index=0, key="x_metric"
267
+ )
268
+ x_aggregation = st.selectbox(
269
+ "Aggregation",
270
+ ["min", "mean", "median", "max", "std"],
271
+ index=1,
272
+ key="x_aggregation",
273
+ )
274
+ with right:
275
+ y_metric = st.selectbox(
276
+ "Aggregation", ["MAE", "RMSE", "MBE", "rMAE"], index=1, key="y_metric"
277
+ )
278
+ y_aggregation = st.selectbox(
279
+ "Aggregation",
280
+ ["min", "mean", "median", "max", "std"],
281
+ index=1,
282
+ key="y_aggregation",
283
+ )
284
+
285
+ st.markdown(
286
+ f"#### {x_aggregation.capitalize()} {x_metric} vs {y_aggregation.capitalize()} {y_metric}"
287
+ )
288
+ fig = px.scatter(
289
+ data_to_plot,
290
+ x=f"{x_metric}.{x_aggregation}",
291
+ y=f"{y_metric}.{y_aggregation}",
292
+ color="model",
293
+ )
294
+ fig.update_layout(height=600)
295
+ st.plotly_chart(fig, use_container_width=True)
296
+
297
+ st.divider()
298
+
299
+ left, right = st.columns(2, gap="small")
300
+ with left:
301
+ metric = st.selectbox(
302
+ "Metric", ["MAE", "RMSE", "MBE", "rMAE"], index=0, key="table_metric"
303
+ )
304
+ with right:
305
+ aggregation = st.selectbox(
306
+ "Aggregation across folds",
307
+ ["min", "mean", "median", "max", "std"],
308
+ index=1,
309
+ key="table_aggregation",
310
+ )
311
+
312
+ metrics_table = data_to_plot.groupby(["model"]).agg(
313
+ aggregation, numeric_only=True
314
+ )[
315
+ [
316
+ f"{metric}.min",
317
+ f"{metric}.mean",
318
+ f"{metric}.median",
319
+ f"{metric}.max",
320
+ f"{metric}.std",
321
+ ]
322
+ ]
323
+
324
+ def custom_table(styler):
325
+ styler.background_gradient(cmap="seismic", axis=0)
326
+ styler.format(precision=2)
327
+
328
+ # center text and increase font size
329
+ styler.map(lambda x: "text-align: center; font-size: 14px;")
330
+ return styler
331
+
332
+ st.markdown(f"#### {aggregation.capitalize()} {metric} stats per model")
333
+ styled_table = metrics_table.style.pipe(custom_table)
334
+ st.dataframe(styled_table, use_container_width=True)
335
+
336
+ metrics_per_building_table = (
337
+ data_to_plot.groupby(["model", "unique_id"])
338
+ .apply(aggregation, numeric_only=True)
339
+ .reset_index()
340
+ .pivot(index="model", columns="unique_id", values=f"{metric}.{aggregation}")
341
+ )
342
+ metrics_per_building_table.insert(
343
+ 0, "median", metrics_per_building_table.median(axis=1)
344
+ )
345
+ metrics_per_building_table.insert(
346
+ 0, "mean", metrics_per_building_table.mean(axis=1)
347
+ )
348
+ metrics_per_building_table = metrics_per_building_table.sort_values(by="mean")
349
+
350
+ def custom_table(styler):
351
+ styler.background_gradient(cmap="seismic", axis=None)
352
+ styler.format(precision=2)
353
+
354
+ # center text and increase font size
355
+ styler.map(lambda x: "text-align: center; font-size: 14px;")
356
+ return styler
357
+
358
+ st.markdown(f"#### {aggregation.capitalize()} {metric} stats per building")
359
+ styled_table = metrics_per_building_table.style.pipe(custom_table)
360
+ st.dataframe(styled_table, use_container_width=True)
361
+
362
+
363
+ def computation_view(data, models_to_plot: set[str]):
364
+ data_to_plot = data[data["model"].isin(models_to_plot)].sort_values(
365
+ by="model", ascending=True
366
+ )
367
+
368
+ st.markdown("#### Computational Resources")
369
+ fig = px.parallel_coordinates(
370
+ data_to_plot.groupby("model").mean(numeric_only=True).reset_index(),
371
+ dimensions=[
372
+ "model",
373
+ "resource_usage.CPU",
374
+ "resource_usage.memory",
375
+ "MAE.mean",
376
+ "RMSE.mean",
377
+ "MBE.mean",
378
+ "rMAE.mean",
379
+ ],
380
+ color="rMAE.mean",
381
+ color_continuous_scale=px.colors.diverging.Portland,
382
+ )
383
+ st.plotly_chart(fig, use_container_width=True)
384
+
385
+ st.divider()
386
+
387
+ left, center, right = st.columns(3, gap="small")
388
+ with left:
389
+ metric = st.selectbox("Metric", ["MAE", "RMSE", "MBE", "rMAE"], index=0)
390
+ with center:
391
+ aggregation_per_building = st.selectbox(
392
+ "Aggregation per building", ["min", "mean", "median", "max", "std"], index=1
393
+ )
394
+ with right:
395
+ aggregation_per_model = st.selectbox(
396
+ "Aggregation per model", ["min", "mean", "median", "max", "std"], index=1
397
+ )
398
+
399
+ st.markdown(
400
+ f"#### {aggregation_per_model.capitalize()} {aggregation_per_building.capitalize()} {metric} vs CPU usage"
401
+ )
402
+ aggregated_data = (
403
+ data_to_plot.groupby("model")
404
+ .agg(aggregation_per_building, numeric_only=True)
405
+ .reset_index()
406
+ )
407
+ fig = px.scatter(
408
+ aggregated_data,
409
+ x="resource_usage.CPU",
410
+ y=f"{metric}.{aggregation_per_model}",
411
+ color="model",
412
+ log_x=True,
413
+ )
414
+ fig.update_layout(height=600)
415
+ st.plotly_chart(fig, use_container_width=True)
images/energyville_logo.png ADDED
images/ku_leuven_logo.png ADDED
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ wandb==0.17.0
2
+ plotly==5.20.0
utils.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import wandb
3
+
4
+
5
+ def get_wandb_data(entity: str, project: str, api_key: str, job_type: str) -> pd.DataFrame:
6
+ api = wandb.Api(api_key=api_key)
7
+
8
+ # Project is specified by <entity/project-name>
9
+ filter_dict = {"jobType": job_type}
10
+ runs = api.runs(f"{entity}/{project}", filters=filter_dict)
11
+
12
+ summary_list, config_list, name_list = [], [], []
13
+ for run in runs:
14
+ # .summary contains the output keys/values for metrics like accuracy.
15
+ # We call ._json_dict to omit large files
16
+ summary_list.append(run.summary._json_dict)
17
+
18
+ # .config contains the hyperparameters.
19
+ # We remove special values that start with _.
20
+ config_list.append({k: v for k, v in run.config.items()})
21
+
22
+ # .name is the human-readable name of the run.
23
+ name_list.append(run.name)
24
+
25
+ summary_df = pd.json_normalize(summary_list, max_level=1)
26
+ config_df = pd.json_normalize(config_list, max_level=2)
27
+ runs_df = pd.concat([summary_df, config_df], axis=1)
28
+ runs_df.index = name_list
29
+ return runs_df
30
+
31
+
32
+ def get_leaderboard(runs_df: pd.DataFrame, metrics: list[str]) -> pd.DataFrame:
33
+ leaderboard = pd.DataFrame(
34
+ index=runs_df['model'].unique(),
35
+ columns=metrics
36
+ ).fillna(0)
37
+
38
+ for _, building_df in runs_df.groupby("unique_id"):
39
+ for column in leaderboard.columns:
40
+ best_model = building_df.loc[building_df[column].idxmin()].model
41
+ leaderboard.loc[best_model, column] += 1
42
+
43
+ leaderboard = leaderboard.sort_values(by=list(leaderboard.columns), ascending=False)
44
+ return leaderboard