diff --git a/app/layout_elements.py b/app/layout_elements.py new file mode 100644 index 00000000..bbdc4ce0 --- /dev/null +++ b/app/layout_elements.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +"""Layout elements that get reused in several tabs.""" +import pandas as pd +import streamlit as st + +from app.plot_functions import create_bar_chart_costs +from app.ptxboa_functions import config_number_columns + + +def display_costs( + df_costs: pd.DataFrame, key: str, titlestring: str, key_suffix: str = "" +): + """Display costs as table and bar chart.""" + key_suffix = key_suffix.lower().replace(" ", "_") + st.subheader(titlestring) + c1, c2 = st.columns([1, 5]) + with c1: + # filter data: + df_res = df_costs.copy() + + # select filter: + show_which_data = st.radio( + "Select elements to display:", + ["All", "Manual select"], + index=0, + key=f"show_which_data_{key}_{key_suffix}", + ) + + # apply filter: + if show_which_data == "Manual select": + ind_select = st.multiselect( + "Select regions:", + df_res.index.values, + default=df_res.index.values, + key=f"select_data_{key}_{key_suffix}", + ) + df_res = df_res.loc[ind_select] + + # sort: + sort_ascending = st.toggle( + "Sort by total costs?", + value=True, + key=f"sort_data_{key}_{key_suffix}", + ) + if sort_ascending: + df_res = df_res.sort_values(["Total"], ascending=True) + with c2: + # create graph: + fig = create_bar_chart_costs( + df_res, + current_selection=st.session_state[key], + ) + st.plotly_chart(fig, use_container_width=True) + + with st.expander("**Data**"): + column_config = config_number_columns( + df_res, format=f"%.1f {st.session_state['output_unit']}" + ) + st.dataframe(df_res, use_container_width=True, column_config=column_config) + return None diff --git a/app/plot_functions.py b/app/plot_functions.py index 274bede2..207ed705 100644 --- a/app/plot_functions.py +++ b/app/plot_functions.py @@ -9,7 +9,11 @@ import plotly.graph_objects as go import streamlit as st -from app.ptxboa_functions import remove_subregions, subset_and_pivot_input_data +from app.ptxboa_functions import ( + remove_subregions, + select_subregions, + subset_and_pivot_input_data, +) from ptxboa.api import PtxboaAPI @@ -226,7 +230,7 @@ def _choropleth_map_deep_dive_country( 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} ("), :] + df = select_subregions(df, deep_dive_country) # need to calculate custom data befor is03166 column is appended. 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 diff --git a/app/ptxboa_functions.py b/app/ptxboa_functions.py index 48ad1e90..e2682a8c 100644 --- a/app/ptxboa_functions.py +++ b/app/ptxboa_functions.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Utility functions for streamlit app.""" +from typing import Literal import pandas as pd import streamlit as st @@ -83,19 +84,28 @@ def calculate_results_list( for parameter in parameter_list: settings2 = settings.copy() settings2[parameter_to_change] = parameter - res_single = calculate_results_single(api, settings2, user_data=user_data) + res_single = calculate_results_single( + api, + settings2, + user_data=st.session_state["user_changes_df"], + ) res_list.append(res_single) res_details = pd.concat(res_list) - return aggregate_costs(res_details) + return aggregate_costs(res_details, parameter_to_change) -def aggregate_costs(res_details: pd.DataFrame) -> pd.DataFrame: +def aggregate_costs( + res_details: pd.DataFrame, parameter_to_change: str +) -> pd.DataFrame: """Aggregate detailed costs.""" # Exclude levelized costs: res = res_details.loc[res_details["cost_type"] != "LC"] res = res.pivot_table( - index="region", columns="process_type", values="values", aggfunc="sum" + index=parameter_to_change, + columns="process_type", + values="values", + aggfunc="sum", ) # calculate total costs: res["Total"] = res.sum(axis=1) @@ -185,6 +195,27 @@ def remove_subregions(api: PtxboaAPI, df: pd.DataFrame, country_name: str): return df +def select_subregions( + df: pd.DataFrame, deep_dive_country: Literal["Argentina", "Morocco", "South Africa"] +) -> pd.DataFrame: + """ + Only select rows corresponding to subregions of a deep dive country. + + Parameters + ---------- + df : pd.DataFrame + pandas DataFrame with list of regions as index. + deep_dive_country : str in {"Argentina", "Morocco", "South Africa"} + + + Returns + ------- + pd.DataFrame + """ + df = df.copy().loc[df.index.str.startswith(f"{deep_dive_country} ("), :] + return df + + def reset_user_changes(): """Reset all user changes.""" if ( diff --git a/app/tab_compare_costs.py b/app/tab_compare_costs.py deleted file mode 100644 index 34689d54..00000000 --- a/app/tab_compare_costs.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- -"""Content of compare costs tab.""" -import pandas as pd -import streamlit as st - -from app.plot_functions import create_bar_chart_costs -from app.ptxboa_functions import ( - calculate_results_list, - config_number_columns, - remove_subregions, -) -from ptxboa.api import PtxboaAPI - - -def content_compare_costs(api: PtxboaAPI, res_costs: pd.DataFrame) -> None: - """Create content for the "compare costs" sheet. - - Parameters - ---------- - api : :class:`~ptxboa.api.PtxboaAPI` - an instance of the api class - res_costs : pd.DataFrame - Results. - """ - with st.expander("What is this?"): - st.markdown( - """ -**Compare costs** - -On this sheet, users can analyze total cost and cost components for -different supply countries, scenarios, renewable electricity sources and process chains. -Data is represented as a bar chart and in tabular form. - -Data can be filterend and sorted. - """ - ) - - def display_costs(df_costs: pd.DataFrame, key: str, titlestring: str): - """Display costs as table and bar chart.""" - st.subheader(titlestring) - c1, c2 = st.columns([1, 5]) - with c1: - # filter data: - df_res = df_costs.copy() - - # select filter: - show_which_data = st.radio( - "Select elements to display:", - ["All", "Manual select"], - index=0, - key=f"show_which_data_{key}", - ) - - # apply filter: - if show_which_data == "Manual select": - ind_select = st.multiselect( - "Select regions:", - df_res.index.values, - default=df_res.index.values, - key=f"select_data_{key}", - ) - df_res = df_res.loc[ind_select] - - # sort: - sort_ascending = st.toggle( - "Sort by total costs?", value=True, key=f"sort_data_{key}" - ) - if sort_ascending: - df_res = df_res.sort_values(["Total"], ascending=True) - with c2: - # create graph: - fig = create_bar_chart_costs( - df_res, - current_selection=st.session_state[key], - ) - st.plotly_chart(fig, use_container_width=True) - - with st.expander("**Data**"): - column_config = config_number_columns( - df_res, format=f"%.1f {st.session_state['output_unit']}" - ) - st.dataframe(df_res, use_container_width=True, column_config=column_config) - - res_costs_without_subregions = remove_subregions( - api, res_costs, st.session_state["country"] - ) - display_costs(res_costs_without_subregions, "region", "Costs by region:") - - # Display costs by scenario: - res_scenario = calculate_results_list( - api, "scenario", user_data=st.session_state["user_changes_df"] - ) - display_costs(res_scenario, "scenario", "Costs by data scenario:") - - # Display costs by RE generation: - # TODO: remove PV tracking manually, this needs to be fixed in data - list_res_gen = api.get_dimension("res_gen").index.to_list() - list_res_gen.remove("PV tracking") - res_res_gen = calculate_results_list( - api, - "res_gen", - parameter_list=list_res_gen, - user_data=st.session_state["user_changes_df"], - ) - display_costs(res_res_gen, "res_gen", "Costs by renewable electricity source:") - - # TODO: display costs by chain diff --git a/app/tab_dashboard.py b/app/tab_dashboard.py index 10670d3c..768813b6 100644 --- a/app/tab_dashboard.py +++ b/app/tab_dashboard.py @@ -1,13 +1,17 @@ # -*- coding: utf-8 -*- """Content of dashboard tab.""" +import pandas as pd import streamlit as st from plotly.subplots import make_subplots +from app.layout_elements import display_costs from app.plot_functions import ( create_bar_chart_costs, create_box_plot, plot_costs_on_map, ) +from app.ptxboa_functions import remove_subregions +from ptxboa.api import PtxboaAPI def _create_infobox(context_data: dict): @@ -30,7 +34,14 @@ def write_info(info): write_info(info4) -def content_dashboard(api, res_costs: dict, context_data: dict): +def content_dashboard( + api: PtxboaAPI, + costs_per_region: pd.DataFrame, + costs_per_scenario: pd.DataFrame, + costs_per_res_gen: pd.DataFrame, + costs_per_chain: pd.DataFrame, + context_data: dict, +): with st.expander("What is this?"): st.markdown( """ @@ -39,6 +50,8 @@ def content_dashboard(api, res_costs: dict, context_data: dict): regional distribution of total costs across supply regions - a split-up of costs by category for your chosen supply region - key information on your chosen demand country. +- total cost and cost components for different supply countries, scenarios, +renewable electricity sources and process chains. Switch to other tabs to explore data and results in more detail! """ @@ -48,14 +61,16 @@ def content_dashboard(api, res_costs: dict, context_data: dict): with c_1: fig_map = plot_costs_on_map( - api, res_costs, scope="world", cost_component="Total" + api, costs_per_region, scope="world", cost_component="Total" ) st.plotly_chart(fig_map, use_container_width=True) with c_2: # create box plot and bar plot: - fig1 = create_box_plot(res_costs) - filtered_data = res_costs[res_costs.index == st.session_state["region"]] + fig1 = create_box_plot(costs_per_region) + filtered_data = costs_per_region[ + costs_per_region.index == st.session_state["region"] + ] fig2 = create_bar_chart_costs(filtered_data) doublefig = make_subplots(rows=1, cols=2, shared_yaxes=True) @@ -70,3 +85,17 @@ def content_dashboard(api, res_costs: dict, context_data: dict): st.plotly_chart(doublefig, use_container_width=True) _create_infobox(context_data) + + display_costs( + remove_subregions(api, costs_per_region, st.session_state["country"]), + "region", + "Costs by region:", + ) + + display_costs(costs_per_scenario, "scenario", "Costs by data scenario:") + + display_costs( + costs_per_res_gen, "res_gen", "Costs by renewable electricity source:" + ) + + display_costs(costs_per_chain, "chain", "Costs by supply chain:") diff --git a/app/tab_deep_dive_countries.py b/app/tab_deep_dive_countries.py index eaf284fb..16c0625f 100644 --- a/app/tab_deep_dive_countries.py +++ b/app/tab_deep_dive_countries.py @@ -4,19 +4,20 @@ import plotly.express as px import streamlit as st +from app.layout_elements import display_costs 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 app.ptxboa_functions import display_and_edit_data_table, select_subregions from ptxboa.api import PtxboaAPI -def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None: +def content_deep_dive_countries(api: PtxboaAPI, costs_per_region: pd.DataFrame) -> None: """Create content for the "costs by region" sheet. Parameters ---------- api : :class:`~ptxboa.api.PtxboaAPI` an instance of the api class - res_costs : pd.DataFrame + costs_per_region : pd.DataFrame Results. Output @@ -41,9 +42,18 @@ def content_deep_dive_countries(api: PtxboaAPI, res_costs: pd.DataFrame) -> None "Select country:", ["Argentina", "Morocco", "South Africa"], horizontal=True ) - fig_map = plot_costs_on_map(api, res_costs, scope=ddc, cost_component="Total") + fig_map = plot_costs_on_map( + api, costs_per_region, scope=ddc, cost_component="Total" + ) st.plotly_chart(fig_map, use_container_width=True) + display_costs( + select_subregions(costs_per_region, ddc), + key="region", + titlestring="Costs per subregion", + key_suffix=ddc, + ) + st.subheader("Full load hours of renewable generation") input_data = api.get_input_data(st.session_state["scenario"]) # filter data: diff --git a/ptxboa_streamlit.py b/ptxboa_streamlit.py index 94e988fe..5d5e1e6f 100644 --- a/ptxboa_streamlit.py +++ b/ptxboa_streamlit.py @@ -10,7 +10,6 @@ from app.context_data import load_context_data from app.sidebar import make_sidebar from app.tab_certification_schemes import content_certification_schemes -from app.tab_compare_costs import content_compare_costs from app.tab_country_fact_sheets import content_country_fact_sheets from app.tab_dashboard import content_dashboard from app.tab_deep_dive_countries import content_deep_dive_countries @@ -62,7 +61,6 @@ ( t_dashboard, t_market_scanning, - t_compare_costs, t_input_data, t_deep_dive_countries, t_country_fact_sheets, @@ -74,7 +72,6 @@ [ "Dashboard", "Market scanning", - "Compare costs", "Input data", "Deep-dive countries", "Country fact sheets", @@ -96,9 +93,29 @@ colors = pd.read_csv("data/Agora_Industry_Colours.csv") st.session_state["colors"] = colors["Hex Code"].to_list() -# calculate results: -res_costs = pf.calculate_results_list( - api, "region", user_data=st.session_state["user_changes_df"] +# calculate results over different data dimensions: +costs_per_region = pf.calculate_results_list( + api, + parameter_to_change="region", + parameter_list=None, +) +costs_per_scenario = pf.calculate_results_list( + api, + parameter_to_change="scenario", + parameter_list=None, +) +costs_per_res_gen = pf.calculate_results_list( + api, + parameter_to_change="res_gen", + # TODO: here we remove PV tracking manually, this needs to be fixed in data + parameter_list=[ + x for x in api.get_dimension("res_gen").index.to_list() if x != "PV tracking" + ], +) +costs_per_chain = pf.calculate_results_list( + api, + parameter_to_change="chain", + parameter_list=None, ) # import context data: @@ -106,19 +123,23 @@ # dashboard: with t_dashboard: - content_dashboard(api, res_costs, cd) + content_dashboard( + api, + costs_per_region=costs_per_region, + costs_per_scenario=costs_per_scenario, + costs_per_res_gen=costs_per_res_gen, + costs_per_chain=costs_per_chain, + context_data=cd, + ) with t_market_scanning: - content_market_scanning(api, res_costs) - -with t_compare_costs: - content_compare_costs(api, res_costs) + content_market_scanning(api, costs_per_region) with t_input_data: content_input_data(api) with t_deep_dive_countries: - content_deep_dive_countries(api, res_costs) + content_deep_dive_countries(api, costs_per_region) with t_country_fact_sheets: content_country_fact_sheets(cd)