From bd1339c88f6594c446a39183aca9ca6696d88509 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Tue, 21 Nov 2023 10:19:44 +0100 Subject: [PATCH 1/9] refactor map plotting functions --- app/plot_functions.py | 224 ++++++++++++++++++++++++++---------------- 1 file changed, 139 insertions(+), 85 deletions(-) diff --git a/app/plot_functions.py b/app/plot_functions.py index 20d69304..b80ad847 100644 --- a/app/plot_functions.py +++ b/app/plot_functions.py @@ -13,6 +13,24 @@ from ptxboa.api import PtxboaAPI +def agora_continuous_color_scale() -> list[tuple]: + """ + Get a continuous scale with agora colors. + + We cannot wrap this in a constant, since st.session_state["colors"] is not + availabe during import. + + Returns + ------- + list[tuple] + """ + return [ + (0, st.session_state["colors"][0]), # Starting color at the minimum data value + (0.5, st.session_state["colors"][6]), + (1, st.session_state["colors"][9]), # Ending color at the maximum data value + ] + + def plot_costs_on_map( api: PtxboaAPI, res_costs: pd.DataFrame, @@ -43,64 +61,118 @@ def plot_costs_on_map( f"{st.session_state['chain']} to " f"{st.session_state['country']}" ) - # define color scale: - color_scale = [ - (0, st.session_state["colors"][0]), # Starting color at the minimum data value - (0.5, st.session_state["colors"][6]), - (1, st.session_state["colors"][9]), # Ending color at the maximum data value - ] - - if scope == "world": - # remove subregions from deep dive countries (otherwise colorscale is not - # correct) - res_costs = remove_subregions(api, res_costs, st.session_state["country"]) - else: - res_costs = res_costs.copy().loc[ - res_costs.index.str.startswith(f"{scope} ("), : - ] - - # Create custom hover text: - custom_hover_data = res_costs.apply( - lambda x: f"{x.name}

" - + "
".join( - [ - f"{col}: {x[col]:.1f}" f"{st.session_state['output_unit']}" - for col in res_costs.columns[:-1] - ] - + [ - f"──────────
{res_costs.columns[-1]}: " - f"{x[res_costs.columns[-1]]:.1f}" - f"{st.session_state['output_unit']}" - ] - ), - axis=1, - ) if scope == "world": # Create a choropleth world map: - fig = px.choropleth( - locations=res_costs.index, # List of country codes or names - locationmode="country names", # Use country names as locations - color=res_costs[cost_component], # Color values for the countries - custom_data=[custom_hover_data], # Pass custom data for hover information - color_continuous_scale=color_scale, # Choose a color scale - title=title_string, + fig = _choropleth_map_world( + api=api, + df=res_costs, + color_col=cost_component, + custom_data_func=_make_costs_hoverdata, ) else: fig = _choropleth_map_deep_dive_country( - api, - res_costs, - scope, - color=cost_component, - custom_data=[custom_hover_data], - color_continuous_scale=color_scale, - title=title_string, - ) - fig.update_geos( - fitbounds="locations", - visible=True, + api=api, + df=res_costs, + deep_dive_country=scope, + color_col=cost_component, + custom_data_func=_make_costs_hoverdata, ) + return _set_map_layout(fig, title=title_string) + + +def _choropleth_map_world( + api: PtxboaAPI, + df: pd.DataFrame, + color_col: str, + custom_data_func: callable, +): + """ + Plot a chorpleth map for the whole world and one color for each country. + + Parameters + ---------- + df : pd.DataFrame + wide formatted dataframe, index needs to be country or region. + color_col : str + column that should be displayed + custom_data : list[pd.Series] + custom data used for hovers + + Returns + ------- + _type_ + _description_ + """ + df = remove_subregions(api=api, df=df, country_name=st.session_state["country"]) + fig = px.choropleth( + locations=df.index, + locationmode="country names", + color=df[color_col], + custom_data=custom_data_func(df), + color_continuous_scale=agora_continuous_color_scale(), + ) + return fig + + +def _choropleth_map_deep_dive_country( + api: PtxboaAPI, + df: pd.DataFrame, + deep_dive_country: Literal["Argentina", "Morocco", "South Africa"], + color_col: str, + custom_data_func: callable, +): + # subsetting 'df' for the selected deep dive country + df = df.copy().loc[df.index.str.startswith(f"{deep_dive_country} ("), :] + # need to calculate custom data befor is03166 column is appended. + hover_data = custom_data_func(df) + # get dataframe with info about iso 3166-2 codes and map them to res_costs + ddc_info = api.get_dimension("region") + df["iso3166_code"] = df.index.map( + pd.Series(ddc_info["iso3166_code"], index=ddc_info["region_name"]) + ) + + geojson_file = ( + Path(__file__).parent.parent.resolve() + / "data" + / f"{deep_dive_country.lower().replace(' ', '_')}_subregions.geojson" + ) + with geojson_file.open("r", encoding="utf-8") as f: + subregion_shapes = json.load(f) + + fig = px.choropleth( + locations=df["iso3166_code"], + featureidkey="properties.iso_3166_2", + color=df[color_col], + geojson=subregion_shapes, + custom_data=hover_data, + color_continuous_scale=agora_continuous_color_scale(), + ) + + fig.update_geos( + fitbounds="locations", + visible=True, + ) + return fig + + +def _set_map_layout(fig: go.Figure, title: str) -> go.Figure: + """ + Apply a unified layout for all maps used in the app. + + The px.choropleth plotting function that creates `fig` has to be called with the + 'custom_data' argument. + + Parameters + ---------- + fig : go.Figure + + Returns + ------- + go.Figure + same figure with updated geos, layout and hovertemplate. + """ # update layout: fig.update_geos( showcountries=True, # Show country borders @@ -122,49 +194,31 @@ def plot_costs_on_map( "len": 0.5, }, # colorbar margin={"t": 20, "b": 20, "l": 20, "r": 20}, # reduce margin around figure + title=title, ) # Set the hover template to use the custom data fig.update_traces(hovertemplate="%{customdata}") # Custom data - return fig -def _choropleth_map_deep_dive_country( - api, - res_costs_subset, - scope_country, - color, - custom_data, - color_continuous_scale, - title, -): - # get dataframe with info about iso 3166-2 codes and map them to res_costs - scope_info = api.get_dimension("region").loc[ - api.get_dimension("region")["region_name"].str.startswith(f"{scope_country} (") - ] - res_costs_subset["iso3166_code"] = res_costs_subset.index.map( - pd.Series(scope_info["iso3166_code"], index=scope_info["region_name"]) - ) - - geojson_file = ( - Path(__file__).parent.parent - / "data" - / f"{scope_country.lower().replace(' ', '_')}_subregions.geojson" - ) - with geojson_file.open("r", encoding="utf-8") as f: - subregion_shapes = json.load(f) - - fig = px.choropleth( - locations=res_costs_subset["iso3166_code"], - featureidkey="properties.iso_3166_2", - color=res_costs_subset[color], - geojson=subregion_shapes, - custom_data=custom_data, - color_continuous_scale=color_continuous_scale, - title=title, +def _make_costs_hoverdata(res_costs: pd.DataFrame) -> list[pd.Series]: + custom_hover_data = res_costs.apply( + lambda x: f"{x.name}

" + + "
".join( + [ + f"{col}: {x[col]:.1f}" f"{st.session_state['output_unit']}" + for col in res_costs.columns[:-1] + ] + + [ + f"──────────
{res_costs.columns[-1]}: " + f"{x[res_costs.columns[-1]]:.1f}" + f"{st.session_state['output_unit']}" + ] + ), + axis=1, ) - return fig + return [custom_hover_data] def create_bar_chart_costs(res_costs: pd.DataFrame, current_selection: str = None): From a308addb96d89bd3629c6ea47cffb4df13e433e7 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Tue, 21 Nov 2023 13:34:32 +0100 Subject: [PATCH 2/9] add map plotting function for input data --- app/plot_functions.py | 129 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 7 deletions(-) diff --git a/app/plot_functions.py b/app/plot_functions.py index b80ad847..11c68360 100644 --- a/app/plot_functions.py +++ b/app/plot_functions.py @@ -9,7 +9,7 @@ import plotly.graph_objects as go import streamlit as st -from app.ptxboa_functions import remove_subregions +from app.ptxboa_functions import remove_subregions, subset_and_pivot_input_data from ptxboa.api import PtxboaAPI @@ -49,7 +49,8 @@ def plot_costs_on_map( scope : Literal["world", "Argentina", "Morocco", "South Africa"], optional either world or a deep dive country, by default "world" cost_component : str, optional - one of the columns in 'res_costs', by default "Total" + The cost component which should be displayed as color in the map. One of + the columns in 'res_costs', by default "Total" Returns ------- @@ -79,7 +80,100 @@ def plot_costs_on_map( custom_data_func=_make_costs_hoverdata, ) - return _set_map_layout(fig, title=title_string) + return _set_map_layout( + fig, title=title_string, colorbar_title=st.session_state["output_unit"] + ) + + +def plot_input_data_on_map( + api: PtxboaAPI, + data_type: Literal["CAPEX", "full load hours", "interest rate"], + color_col: Literal[ + "PV tilted", "Wind Offshore", "Wind Onshore", "Wind-PV-Hybrid", "interest rate" + ], + scope: Literal["world", "Argentina", "Morocco", "South Africa"] = "world", + title: str = "", +) -> go.Figure: + """ + Plot input data on a map. + + Parameters + ---------- + api : PtxboaAPI + data_type : Literal["CAPEX", "full load hours", "interest rate"] + The data type from which a parameter is plotted + color_col : Literal[ "PV tilted", "Wind Offshore", "Wind Onshore", "Wind + the parameter to plot on the map + scope : Literal["world", "Argentina", "Morocco", "South Africa"], optional + either the whole world or a deep dive country, by default "world" + title : str, optional + title of the figure, by default "" + + Returns + ------- + go.Figure + """ + input_data = api.get_input_data( + scenario=st.session_state["scenario"], + user_data=st.session_state["user_changes_df"], + ) + + units = {"CAPEX": "USD/kW", "full load hours": "h/a", "interest rate": ""} + + if data_type == "interest rate": + assert color_col == "interest rate" + columns = "parameter_code" + process_code = [""] + custom_data_func_kwargs = {"float_precision": 3} + else: + assert color_col in [ + "PV tilted", + "Wind Offshore", + "Wind Onshore", + "Wind-PV-Hybrid", + ] + custom_data_func_kwargs = {"float_precision": 0} + columns = "process_code" + process_code = [ + "Wind Onshore", + "Wind Offshore", + "PV tilted", + "Wind-PV-Hybrid", + ] + custom_data_func_kwargs["unit"] = units[data_type] + + input_data = subset_and_pivot_input_data( + input_data=input_data, + source_region_code=None, + parameter_code=[data_type], + process_code=process_code, + index="source_region_code", + columns=columns, + values="value", + ) + + if scope == "world": + # Create a choropleth world map: + fig = _choropleth_map_world( + api=api, + df=input_data, + color_col=color_col, + custom_data_func=_make_inputs_hoverdata, + custom_data_func_kwargs=custom_data_func_kwargs, + ) + else: + fig = _choropleth_map_deep_dive_country( + api=api, + df=input_data, + deep_dive_country=scope, + color_col=color_col, + custom_data_func=_make_inputs_hoverdata, + custom_data_func_kwargs=custom_data_func_kwargs, + ) + + return _set_map_layout( + fig, title=title, colorbar_title=custom_data_func_kwargs["unit"] + ) def _choropleth_map_world( @@ -87,6 +181,7 @@ def _choropleth_map_world( df: pd.DataFrame, color_col: str, custom_data_func: callable, + custom_data_func_kwargs: dict | None = None, ): """ Plot a chorpleth map for the whole world and one color for each country. @@ -105,12 +200,14 @@ def _choropleth_map_world( _type_ _description_ """ + if custom_data_func_kwargs is None: + custom_data_func_kwargs = {} df = remove_subregions(api=api, df=df, country_name=st.session_state["country"]) fig = px.choropleth( locations=df.index, locationmode="country names", color=df[color_col], - custom_data=custom_data_func(df), + custom_data=custom_data_func(df, **custom_data_func_kwargs), color_continuous_scale=agora_continuous_color_scale(), ) return fig @@ -122,11 +219,14 @@ def _choropleth_map_deep_dive_country( deep_dive_country: Literal["Argentina", "Morocco", "South Africa"], color_col: str, custom_data_func: callable, + custom_data_func_kwargs: dict | None = None, ): + if custom_data_func_kwargs is None: + custom_data_func_kwargs = {} # subsetting 'df' for the selected deep dive country df = df.copy().loc[df.index.str.startswith(f"{deep_dive_country} ("), :] # need to calculate custom data befor is03166 column is appended. - hover_data = custom_data_func(df) + hover_data = custom_data_func(df, **custom_data_func_kwargs) # get dataframe with info about iso 3166-2 codes and map them to res_costs ddc_info = api.get_dimension("region") df["iso3166_code"] = df.index.map( @@ -157,7 +257,7 @@ def _choropleth_map_deep_dive_country( return fig -def _set_map_layout(fig: go.Figure, title: str) -> go.Figure: +def _set_map_layout(fig: go.Figure, title: str, colorbar_title: str) -> go.Figure: """ Apply a unified layout for all maps used in the app. @@ -167,6 +267,10 @@ def _set_map_layout(fig: go.Figure, title: str) -> go.Figure: Parameters ---------- fig : go.Figure + title : str + the figure title + colorbar_title : str + the title of the colorbar Returns ------- @@ -190,7 +294,7 @@ def _set_map_layout(fig: go.Figure, title: str) -> go.Figure: fig.update_layout( coloraxis_colorbar={ - "title": st.session_state["output_unit"], + "title": colorbar_title, "len": 0.5, }, # colorbar margin={"t": 20, "b": 20, "l": 20, "r": 20}, # reduce margin around figure @@ -202,6 +306,17 @@ def _set_map_layout(fig: go.Figure, title: str) -> go.Figure: return fig +def _make_inputs_hoverdata(df, unit, float_precision): + custom_hover_data = df.apply( + lambda x: f"{x.name}

" + + "
".join( + [f"{col}: {x[col]:.{float_precision}f}{unit}" for col in df.columns] + ), + axis=1, + ) + return [custom_hover_data] + + def _make_costs_hoverdata(res_costs: pd.DataFrame) -> list[pd.Series]: custom_hover_data = res_costs.apply( lambda x: f"{x.name}

" From f54f5a7408da21040edec25d0ef38e9a86b98f18 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Tue, 21 Nov 2023 13:36:10 +0100 Subject: [PATCH 3/9] add maps for region specific input data parameters --- app/tab_input_data.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/app/tab_input_data.py b/app/tab_input_data.py index 190e21e6..061d82ae 100644 --- a/app/tab_input_data.py +++ b/app/tab_input_data.py @@ -3,6 +3,7 @@ import plotly.express as px import streamlit as st +from app.plot_functions import plot_input_data_on_map from app.ptxboa_functions import display_and_edit_data_table, display_user_changes from ptxboa.api import PtxboaAPI @@ -86,10 +87,24 @@ def content_input_data(api: PtxboaAPI) -> None: missing_index_name = "parameter_code" missing_index_value = "interest rate" - c1, c2 = st.columns(2, gap="medium") - with c2: - # show data: - st.markdown("**Data:**") + c1, c2 = st.columns([2, 1], gap="large") + with c1: + st.markdown("**Map of input data values**") + if data_selection in ["full load hours", "CAPEX"]: + map_parameter = st.selectbox( + "Show Parameter on Map", process_code, key="input_data_map_parameter" + ) + else: + map_parameter = "interest rate" + fig = plot_input_data_on_map( + api=api, + data_type=data_selection, + color_col=map_parameter, + scope="world", + ) + st.plotly_chart(fig, use_container_width=True) + + with st.expander(f"**Region specific {data_selection} data**"): df = display_and_edit_data_table( input_data=input_data_without_subregions, missing_index_name=missing_index_name, @@ -100,10 +115,9 @@ def content_input_data(api: PtxboaAPI) -> None: process_code=process_code, column_config=column_config, ) - - with c1: + with c2: # create plot: - st.markdown("**Figure:**") + st.markdown("**Value Distribution over Source regions:**") fig = px.box(df) st.plotly_chart(fig, use_container_width=True) From 7117da65c7a94d69c97178fbb3fb49d449252813 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Tue, 21 Nov 2023 13:37:14 +0100 Subject: [PATCH 4/9] add map of full load hours for deep dive countries --- app/tab_deep_dive_countries.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/app/tab_deep_dive_countries.py b/app/tab_deep_dive_countries.py index 85e69cd3..77c31606 100644 --- a/app/tab_deep_dive_countries.py +++ b/app/tab_deep_dive_countries.py @@ -4,7 +4,7 @@ import plotly.express as px import streamlit as st -from app.plot_functions import plot_costs_on_map +from app.plot_functions import plot_costs_on_map, plot_input_data_on_map from app.ptxboa_functions import display_and_edit_data_table from ptxboa.api import PtxboaAPI @@ -84,8 +84,24 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None x = None st.markdown("TODO: fix surplus countries in data table") - c1, c2 = st.columns(2, gap="medium") - with c2: + st.subheader("Full load hours of renewable generation") + c1, c2 = st.columns([2, 1], gap="large") + with c1: + st.markdown("**Map of input data values**") + if data_selection in ["full load hours", "CAPEX"]: + map_parameter = st.selectbox( + "Show Parameter on Map", process_code, key="ddc_flh_map_parameter" + ) + else: + map_parameter = "interest rate" + fig = plot_input_data_on_map( + api=api, + data_type=data_selection, + color_col=map_parameter, + scope=ddc, + ) + st.plotly_chart(fig, use_container_width=True) + with st.expander(f"**Region specific {data_selection} data**"): # show data: st.markdown("**Data:**") df = display_and_edit_data_table( @@ -99,7 +115,7 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None column_config=column_config, key_suffix="_ddc", ) - with c1: + with c2: # create plot: st.markdown("**Figure:**") fig = px.box(df) From d0b6ac20b36dfc3371d8b0a3a67eb2a76bf20f2d Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Wed, 22 Nov 2023 10:26:22 +0100 Subject: [PATCH 5/9] format subheaders and titles --- app/tab_deep_dive_countries.py | 8 +++----- app/tab_input_data.py | 10 +++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/tab_deep_dive_countries.py b/app/tab_deep_dive_countries.py index 77c31606..e9f218b2 100644 --- a/app/tab_deep_dive_countries.py +++ b/app/tab_deep_dive_countries.py @@ -87,7 +87,7 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None st.subheader("Full load hours of renewable generation") c1, c2 = st.columns([2, 1], gap="large") with c1: - st.markdown("**Map of input data values**") + st.markdown("**Map**") if data_selection in ["full load hours", "CAPEX"]: map_parameter = st.selectbox( "Show Parameter on Map", process_code, key="ddc_flh_map_parameter" @@ -101,9 +101,7 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None scope=ddc, ) st.plotly_chart(fig, use_container_width=True) - with st.expander(f"**Region specific {data_selection} data**"): - # show data: - st.markdown("**Data:**") + with st.expander("**Data**"): df = display_and_edit_data_table( input_data=input_data, missing_index_name=missing_index_name, @@ -117,6 +115,6 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None ) with c2: # create plot: - st.markdown("**Figure:**") + st.markdown("**Regional Distribution**") fig = px.box(df) st.plotly_chart(fig, use_container_width=True) diff --git a/app/tab_input_data.py b/app/tab_input_data.py index 061d82ae..c8f7e93c 100644 --- a/app/tab_input_data.py +++ b/app/tab_input_data.py @@ -34,7 +34,7 @@ def content_input_data(api: PtxboaAPI) -> None: """ ) - st.subheader("Region specific data:") + st.subheader("Region specific data") # get input data: input_data = api.get_input_data( st.session_state["scenario"], @@ -89,7 +89,7 @@ def content_input_data(api: PtxboaAPI) -> None: c1, c2 = st.columns([2, 1], gap="large") with c1: - st.markdown("**Map of input data values**") + st.markdown("**Map**") if data_selection in ["full load hours", "CAPEX"]: map_parameter = st.selectbox( "Show Parameter on Map", process_code, key="input_data_map_parameter" @@ -104,7 +104,7 @@ def content_input_data(api: PtxboaAPI) -> None: ) st.plotly_chart(fig, use_container_width=True) - with st.expander(f"**Region specific {data_selection} data**"): + with st.expander("**Data**"): df = display_and_edit_data_table( input_data=input_data_without_subregions, missing_index_name=missing_index_name, @@ -117,12 +117,12 @@ def content_input_data(api: PtxboaAPI) -> None: ) with c2: # create plot: - st.markdown("**Value Distribution over Source regions:**") + st.markdown("**Regional Distribution**") fig = px.box(df) st.plotly_chart(fig, use_container_width=True) st.divider() - st.subheader("Data that is identical for all regions:") + st.subheader("Data that is identical for all regions") input_data_global = input_data.loc[input_data["source_region_code"] == ""] From dda8c210d0d474584f7f4655a699cdb43b847791 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Wed, 22 Nov 2023 11:11:17 +0100 Subject: [PATCH 6/9] horizontally align figures in separate st.columns --- app/tab_deep_dive_countries.py | 12 ++++++++---- app/tab_input_data.py | 13 +++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/tab_deep_dive_countries.py b/app/tab_deep_dive_countries.py index e9f218b2..9d89373e 100644 --- a/app/tab_deep_dive_countries.py +++ b/app/tab_deep_dive_countries.py @@ -85,8 +85,11 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None st.markdown("TODO: fix surplus countries in data table") st.subheader("Full load hours of renewable generation") - c1, c2 = st.columns([2, 1], gap="large") - with c1: + # in order to keep the figures horizontally aligned, we create two st.columns pairs + # the columns are identified by c_{row}_{column}, zero indexed + c_0_0, c_0_1 = st.columns([2, 1], gap="large") + c_1_0, c_1_1 = st.columns([2, 1], gap="large") + with c_0_0: st.markdown("**Map**") if data_selection in ["full load hours", "CAPEX"]: map_parameter = st.selectbox( @@ -94,6 +97,7 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None ) else: map_parameter = "interest rate" + with c_1_0: fig = plot_input_data_on_map( api=api, data_type=data_selection, @@ -113,8 +117,8 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None column_config=column_config, key_suffix="_ddc", ) - with c2: - # create plot: + with c_0_1: st.markdown("**Regional Distribution**") + with c_1_1: fig = px.box(df) st.plotly_chart(fig, use_container_width=True) diff --git a/app/tab_input_data.py b/app/tab_input_data.py index c8f7e93c..d5cf3d2f 100644 --- a/app/tab_input_data.py +++ b/app/tab_input_data.py @@ -87,8 +87,11 @@ def content_input_data(api: PtxboaAPI) -> None: missing_index_name = "parameter_code" missing_index_value = "interest rate" - c1, c2 = st.columns([2, 1], gap="large") - with c1: + # in order to keep the figures horizontally aligned, we create two st.columns pairs + # the columns are identified by c_{row}_{column}, zero indexed + c_0_0, c_0_1 = st.columns([2, 1], gap="large") + c_1_0, c_1_1 = st.columns([2, 1], gap="large") + with c_0_0: st.markdown("**Map**") if data_selection in ["full load hours", "CAPEX"]: map_parameter = st.selectbox( @@ -96,6 +99,7 @@ def content_input_data(api: PtxboaAPI) -> None: ) else: map_parameter = "interest rate" + with c_1_0: fig = plot_input_data_on_map( api=api, data_type=data_selection, @@ -115,9 +119,10 @@ def content_input_data(api: PtxboaAPI) -> None: process_code=process_code, column_config=column_config, ) - with c2: - # create plot: + with c_0_1: st.markdown("**Regional Distribution**") + with c_1_1: + # create plot: fig = px.box(df) st.plotly_chart(fig, use_container_width=True) From cd88e80069a4a6eec9062df7a68b5e15638681c8 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Wed, 22 Nov 2023 11:17:38 +0100 Subject: [PATCH 7/9] remove data selection st.radio for deep dive countries --- app/tab_deep_dive_countries.py | 54 ++++++++++------------------------ 1 file changed, 16 insertions(+), 38 deletions(-) diff --git a/app/tab_deep_dive_countries.py b/app/tab_deep_dive_countries.py index 9d89373e..eaf284fb 100644 --- a/app/tab_deep_dive_countries.py +++ b/app/tab_deep_dive_countries.py @@ -44,10 +44,8 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None fig_map = plot_costs_on_map(api, res_costs, scope=ddc, cost_component="Total") st.plotly_chart(fig_map, use_container_width=True) - # get input data: - + st.subheader("Full load hours of renewable generation") input_data = api.get_input_data(st.session_state["scenario"]) - # filter data: # get list of subregions: region_list = ( @@ -56,51 +54,31 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None .index.to_list() ) - # TODO: implement display of total costs - list_data_types = ["full load hours"] - data_selection = st.radio( - "Select data type", - list_data_types, - horizontal=True, - key="sel_data_ddc", - ) - if data_selection == "full load hours": - parameter_code = ["full load hours"] - process_code = [ - "Wind Onshore", - "Wind Offshore", - "PV tilted", - "Wind-PV-Hybrid", - ] - x = "process_code" - missing_index_name = "parameter_code" - missing_index_value = "full load hours" - column_config = {"format": "%.0f h/a", "min_value": 0, "max_value": 8760} + parameter_code = ["full load hours"] + process_code = [ + "Wind Onshore", + "Wind Offshore", + "PV tilted", + "Wind-PV-Hybrid", + ] + x = "process_code" + missing_index_name = "parameter_code" + missing_index_value = "full load hours" + column_config = {"format": "%.0f h/a", "min_value": 0, "max_value": 8760} - if data_selection == "total costs": - df = res_costs.copy() - df = res_costs.loc[region_list].rename({"Total": data_selection}, axis=1) - df = df.rename_axis("source_region_code", axis=0) - x = None - st.markdown("TODO: fix surplus countries in data table") - - st.subheader("Full load hours of renewable generation") # in order to keep the figures horizontally aligned, we create two st.columns pairs # the columns are identified by c_{row}_{column}, zero indexed c_0_0, c_0_1 = st.columns([2, 1], gap="large") c_1_0, c_1_1 = st.columns([2, 1], gap="large") with c_0_0: st.markdown("**Map**") - if data_selection in ["full load hours", "CAPEX"]: - map_parameter = st.selectbox( - "Show Parameter on Map", process_code, key="ddc_flh_map_parameter" - ) - else: - map_parameter = "interest rate" + map_parameter = st.selectbox( + "Show Parameter on Map", process_code, key="ddc_flh_map_parameter" + ) with c_1_0: fig = plot_input_data_on_map( api=api, - data_type=data_selection, + data_type="full load hours", color_col=map_parameter, scope=ddc, ) From c999326280af7a9903f9ad26197c2e0e8805ed5c Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Wed, 22 Nov 2023 12:15:50 +0100 Subject: [PATCH 8/9] adjust hovers for input data maps --- app/plot_functions.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/app/plot_functions.py b/app/plot_functions.py index 11c68360..274bede2 100644 --- a/app/plot_functions.py +++ b/app/plot_functions.py @@ -141,6 +141,8 @@ def plot_input_data_on_map( "Wind-PV-Hybrid", ] custom_data_func_kwargs["unit"] = units[data_type] + custom_data_func_kwargs["data_type"] = data_type + custom_data_func_kwargs["map_variable"] = color_col input_data = subset_and_pivot_input_data( input_data=input_data, @@ -306,14 +308,20 @@ def _set_map_layout(fig: go.Figure, title: str, colorbar_title: str) -> go.Figur return fig -def _make_inputs_hoverdata(df, unit, float_precision): - custom_hover_data = df.apply( - lambda x: f"{x.name}

" - + "
".join( - [f"{col}: {x[col]:.{float_precision}f}{unit}" for col in df.columns] - ), - axis=1, - ) +def _make_inputs_hoverdata(df, data_type, map_variable, unit, float_precision): + custom_hover_data = [] + if data_type == "interest rate": + for idx, row in df.iterrows(): + hover = f"{idx} | {data_type}

{row['interest rate']}" + custom_hover_data.append(hover) + else: + for idx, row in df.iterrows(): + hover = f"{idx} | {data_type}
" + for i, v in zip(row.index, row): + hover += f"
{i}: {v:.{float_precision}f}{unit}" + if i == map_variable: + hover += " (displayed on map)" + custom_hover_data.append(hover) return [custom_hover_data] From e70d6626626685a0b0253fecea5dba7124481ae7 Mon Sep 17 00:00:00 2001 From: "j.aschauer" Date: Wed, 22 Nov 2023 14:37:48 +0100 Subject: [PATCH 9/9] return edited data from `display_and_edit_data()` --- app/ptxboa_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ptxboa_functions.py b/app/ptxboa_functions.py index 14ddb74d..e88621a6 100644 --- a/app/ptxboa_functions.py +++ b/app/ptxboa_functions.py @@ -244,7 +244,7 @@ def display_and_edit_data_table( column_config_all = config_number_columns(df_tab, **column_config) # display data: - st.data_editor( + df_tab = st.data_editor( df_tab, use_container_width=True, key=key,