import streamlit as st from scenario import numerize import pandas as pd from utilities import ( format_numbers, load_local_css, set_header, name_formating, project_selection, ) import pickle import yaml from yaml import SafeLoader from scenario import class_from_dict import plotly.express as px import numpy as np import plotly.graph_objects as go import pandas as pd from plotly.subplots import make_subplots import sqlite3 from utilities import update_db from collections import OrderedDict import os st.set_page_config(layout="wide") load_local_css("styles.css") set_header() st.empty() st.header("AI Model Media Recommendation") # def get_saved_scenarios_dict(): # # Path to the saved scenarios file # saved_scenarios_dict_path = os.path.join( # st.session_state["project_path"], "saved_scenarios.pkl" # ) # # Load existing scenarios if the file exists # if os.path.exists(saved_scenarios_dict_path): # with open(saved_scenarios_dict_path, "rb") as f: # saved_scenarios_dict = pickle.load(f) # else: # saved_scenarios_dict = OrderedDict() # return saved_scenarios_dict # # Function to format values based on their size # def format_value(value): # return round(value, 4) if value < 1 else round(value, 1) # # Function to recursively convert non-serializable types to serializable ones # def convert_to_serializable(obj): # if isinstance(obj, np.ndarray): # return obj.tolist() # elif isinstance(obj, dict): # return {key: convert_to_serializable(value) for key, value in obj.items()} # elif isinstance(obj, list): # return [convert_to_serializable(element) for element in obj] # elif isinstance(obj, (int, float, str, bool, type(None))): # return obj # else: # # Fallback: convert the object to a string # return str(obj) if "username" not in st.session_state: st.session_state["username"] = None if "project_name" not in st.session_state: st.session_state["project_name"] = None if "project_dct" not in st.session_state: project_selection() st.stop() # if "project_path" not in st.session_state: # st.stop() # if 'username' in st.session_state and st.session_state['username'] is not None: # data_path = os.path.join(st.session_state["project_path"], "data_import.pkl") # try: # with open(data_path, "rb") as f: # data = pickle.load(f) # except Exception as e: # st.error(f"Please import data from the Data Import Page") # st.stop() # # Get saved scenarios dictionary and scenario name list # saved_scenarios_dict = get_saved_scenarios_dict() # scenarios_list = list(saved_scenarios_dict.keys()) # #st.write(saved_scenarios_dict) # # Check if the list of saved scenarios is empty # if len(scenarios_list) == 0: # # Display a warning message if no scenarios are saved # st.warning("No scenarios saved. Please save a scenario to load.", icon="⚠️") # st.stop() # # Display a dropdown saved scenario list # selected_scenario = st.selectbox( # "Pick a Scenario", sorted(scenarios_list), key="selected_scenario" # ) # selected_scenario_data = saved_scenarios_dict[selected_scenario] # # Scenarios Name # metrics_name = selected_scenario_data["metrics_selected"] # panel_name = selected_scenario_data["panel_selected"] # optimization_name = selected_scenario_data["optimization"] # # Display the scenario details with bold "Metric," "Panel," and "Optimization" # # Create columns for download and delete buttons # download_col, delete_col = st.columns(2) # channels_list = list(selected_scenario_data["channels"].keys()) # # List to hold data for all channels # channels_data = [] # # Iterate through each channel and gather required data # for channel in channels_list: # channel_conversion_rate = selected_scenario_data["channels"][channel][ # "conversion_rate" # ] # channel_actual_spends = ( # selected_scenario_data["channels"][channel]["actual_total_spends"] # * channel_conversion_rate # ) # channel_optimized_spends = ( # selected_scenario_data["channels"][channel]["modified_total_spends"] # * channel_conversion_rate # ) # channel_actual_metrics = selected_scenario_data["channels"][channel][ # "actual_total_sales" # ] # channel_optimized_metrics = selected_scenario_data["channels"][channel][ # "modified_total_sales" # ] # channel_roi_mroi_data = selected_scenario_data["channel_roi_mroi"][channel] # # Extract the ROI and MROI data # actual_roi = channel_roi_mroi_data["actual_roi"] # optimized_roi = channel_roi_mroi_data["optimized_roi"] # actual_mroi = channel_roi_mroi_data["actual_mroi"] # optimized_mroi = channel_roi_mroi_data["optimized_mroi"] # # Calculate spends per metric # spends_per_metrics_actual = channel_actual_spends / channel_actual_metrics # spends_per_metrics_optimized = channel_optimized_spends / channel_optimized_metrics # # Append the collected data as a dictionary to the list # channels_data.append( # { # "Channel Name": channel, # "Spends Actual": channel_actual_spends, # "Spends Optimized": channel_optimized_spends, # f"{metrics_name} Actual": channel_actual_metrics, # f"{name_formating(metrics_name)} Optimized": numerize( # channel_optimized_metrics # ), # "ROI Actual": format_value(actual_roi), # "ROI Optimized": format_value(optimized_roi), # "MROI Actual": format_value(actual_mroi), # "MROI Optimized": format_value(optimized_mroi), # f"Spends per {name_formating(metrics_name)} Actual": numerize( # spends_per_metrics_actual # ), # f"Spends per {name_formating(metrics_name)} Optimized": numerize( # spends_per_metrics_optimized # ), # } # ) # # Create a DataFrame from the collected data ##NEW CODE########## scenarios_name_placeholder = st.empty() # Function to get saved scenarios dictionary def get_saved_scenarios_dict(): return st.session_state["project_dct"]["saved_scenarios"]["saved_scenarios_dict"] # Function to format values based on their size def format_value(value): return round(value, 4) if value < 1 else round(value, 1) # Function to recursively convert non-serializable types to serializable ones def convert_to_serializable(obj): if isinstance(obj, np.ndarray): return obj.tolist() elif isinstance(obj, dict): return {key: convert_to_serializable(value) for key, value in obj.items()} elif isinstance(obj, list): return [convert_to_serializable(element) for element in obj] elif isinstance(obj, (int, float, str, bool, type(None))): return obj else: # Fallback: convert the object to a string return str(obj) # Get saved scenarios dictionary and scenario name list saved_scenarios_dict = get_saved_scenarios_dict() scenarios_list = list(saved_scenarios_dict.keys()) # Check if the list of saved scenarios is empty if len(scenarios_list) == 0: # Display a warning message if no scenarios are saved st.warning("No scenarios saved. Please save a scenario to load.", icon="⚠️") st.stop() # Display a dropdown saved scenario list selected_scenario = st.selectbox( "Pick a Scenario", sorted(scenarios_list), key="selected_scenario" ) selected_scenario_data = saved_scenarios_dict[selected_scenario] # Scenarios Name metrics_name = selected_scenario_data["metrics_selected"] panel_name = selected_scenario_data["panel_selected"] optimization_name = selected_scenario_data["optimization"] multiplier = selected_scenario_data["multiplier"] timeframe = selected_scenario_data["timeframe"] # Display the scenario details with bold "Metric," "Panel," and "Optimization" scenarios_name_placeholder.markdown( f"**Metric**: {name_formating(metrics_name)}; **Panel**: {name_formating(panel_name)}; **Fix**: {name_formating(optimization_name)}; **Timeframe**: {name_formating(timeframe)}" ) # Create columns for download and delete buttons download_col, delete_col = st.columns(2) # Channel List channels_list = list(selected_scenario_data["channels"].keys()) # List to hold data for all channels channels_data = [] # Iterate through each channel and gather required data for channel in channels_list: channel_conversion_rate = selected_scenario_data["channels"][channel][ "conversion_rate" ] channel_actual_spends = ( selected_scenario_data["channels"][channel]["actual_total_spends"] * channel_conversion_rate ) channel_optimized_spends = ( selected_scenario_data["channels"][channel]["modified_total_spends"] * channel_conversion_rate ) channel_actual_metrics = selected_scenario_data["channels"][channel][ "actual_total_sales" ] channel_optimized_metrics = selected_scenario_data["channels"][channel][ "modified_total_sales" ] channel_roi_mroi_data = selected_scenario_data["channel_roi_mroi"][channel] # Extract the ROI and MROI data actual_roi = channel_roi_mroi_data["actual_roi"] optimized_roi = channel_roi_mroi_data["optimized_roi"] actual_mroi = channel_roi_mroi_data["actual_mroi"] optimized_mroi = channel_roi_mroi_data["optimized_mroi"] # Calculate spends per metric spends_per_metrics_actual = channel_actual_spends / channel_actual_metrics spends_per_metrics_optimized = channel_optimized_spends / channel_optimized_metrics # Append the collected data as a dictionary to the list channels_data.append( { "Channel Name": channel, "Spends Actual": (channel_actual_spends / multiplier), "Spends Optimized": (channel_optimized_spends / multiplier), f"{name_formating(metrics_name)} Actual": ( channel_actual_metrics / multiplier ), f"{name_formating(metrics_name)} Optimized": ( channel_optimized_metrics / multiplier ), "ROI Actual": format_value(actual_roi), "ROI Optimized": format_value(optimized_roi), "MROI Actual": format_value(actual_mroi), "MROI Optimized": format_value(optimized_mroi), f"Spends per {name_formating(metrics_name)} Actual": round( spends_per_metrics_actual, 2 ), f"Spends per {name_formating(metrics_name)} Optimized": round( spends_per_metrics_optimized, 2 ), } ) # Create a DataFrame from the collected data # df = pd.DataFrame(channels_data) # # Display the DataFrame # st.dataframe(df, hide_index=True) summary_df_sorted = pd.DataFrame(channels_data).sort_values(by=["Spends Optimized"]) summary_df_sorted["Delta"] = ( summary_df_sorted["Spends Optimized"] - summary_df_sorted["Spends Actual"] ) summary_df_sorted["Delta_percent"] = np.round( (summary_df_sorted["Delta"]) / summary_df_sorted["Spends Actual"] * 100, 2 ) # spends_data = pd.read_excel("Overview_data_test.xlsx") st.header("Optimized Media Spend Overview") channel_colors = px.colors.qualitative.Plotly fig = make_subplots( rows=1, cols=3, subplot_titles=("Actual Spend", "Spends Optimized", "Delta"), horizontal_spacing=0.05, ) for i, channel in enumerate(summary_df_sorted["Channel Name"].unique()): channel_df = summary_df_sorted[summary_df_sorted["Channel Name"] == channel] channel_color = channel_colors[i % len(channel_colors)] fig.add_trace( go.Bar( x=channel_df["Spends Actual"], y=channel_df["Channel Name"], text=channel_df["Spends Actual"].apply(format_numbers), marker_color=channel_color, orientation="h", ), row=1, col=1, ) fig.add_trace( go.Bar( x=channel_df["Spends Optimized"], y=channel_df["Channel Name"], text=channel_df["Spends Optimized"].apply(format_numbers), marker_color=channel_color, orientation="h", showlegend=False, ), row=1, col=2, ) fig.add_trace( go.Bar( x=channel_df["Delta_percent"], y=channel_df["Channel Name"], text=channel_df["Delta_percent"].apply(lambda x: f"{x:.0f}%"), marker_color=channel_color, orientation="h", showlegend=False, ), row=1, col=3, ) fig.update_layout(height=600, width=900, title="", showlegend=False) fig.update_yaxes(showticklabels=False, row=1, col=2) fig.update_yaxes(showticklabels=False, row=1, col=3) fig.update_xaxes(showticklabels=False, row=1, col=1) fig.update_xaxes(showticklabels=False, row=1, col=2) fig.update_xaxes(showticklabels=False, row=1, col=3) st.plotly_chart(fig, use_container_width=True) summary_df_sorted["Perc_alloted"] = np.round( summary_df_sorted["Spends Optimized"] / summary_df_sorted["Spends Optimized"].sum(), 2, ) st.header("Optimized Media Spend Allocation") fig = make_subplots( rows=1, cols=2, subplot_titles=("Spends Optimized", "% Split"), horizontal_spacing=0.05, ) for i, channel in enumerate(summary_df_sorted["Channel Name"].unique()): channel_df = summary_df_sorted[summary_df_sorted["Channel Name"] == channel] channel_color = channel_colors[i % len(channel_colors)] fig.add_trace( go.Bar( x=channel_df["Spends Optimized"], y=channel_df["Channel Name"], text=channel_df["Spends Optimized"].apply(format_numbers), marker_color=channel_color, orientation="h", ), row=1, col=1, ) fig.add_trace( go.Bar( x=channel_df["Perc_alloted"], y=channel_df["Channel Name"], text=channel_df["Perc_alloted"].apply(lambda x: f"{100*x:.0f}%"), marker_color=channel_color, orientation="h", showlegend=False, ), row=1, col=2, ) fig.update_layout(height=600, width=900, title="", showlegend=False) fig.update_yaxes(showticklabels=False, row=1, col=2) fig.update_yaxes(showticklabels=False, row=1, col=3) fig.update_xaxes(showticklabels=False, row=1, col=1) fig.update_xaxes(showticklabels=False, row=1, col=2) fig.update_xaxes(showticklabels=False, row=1, col=3) st.plotly_chart(fig, use_container_width=True) st.session_state["cleaned_data"] = st.session_state["project_dct"]["data_import"][ "imputed_tool_df" ] st.session_state["category_dict"] = st.session_state["project_dct"]["data_import"][ "category_dict" ] effectiveness_overall = pd.DataFrame() response_metrics = list( *[ st.session_state["category_dict"][key] for key in st.session_state["category_dict"].keys() if key == "Response Metrics" ] ) effectiveness_overall = ( st.session_state["cleaned_data"][response_metrics].sum().reset_index() ) effectiveness_overall.columns = ["ResponseMetricName", "ResponseMetricValue"] effectiveness_overall["Efficiency"] = effectiveness_overall["ResponseMetricValue"].map( lambda x: x / summary_df_sorted["Spends Optimized"].sum() ) columns6 = st.columns(3) effectiveness_overall.sort_values( by=["ResponseMetricValue"], ascending=False, inplace=True ) effectiveness_overall = np.round(effectiveness_overall, 2) columns4 = st.columns([0.55, 0.45]) # effectiveness_overall = effectiveness_overall.sort_values(by=["ResponseMetricValue"]) # with columns4[0]: # fig = px.funnel( # effectiveness_overall, # x="ResponseMetricValue", # y="ResponseMetricName", # color="ResponseMetricName", # title="Effectiveness", # ) # fig.update_layout( # showlegend=False, # yaxis=dict(tickmode="array"), # ) # fig.update_traces( # textinfo="value", # textposition="inside", # texttemplate="%{x:.2s} ", # hoverinfo="y+x+percent initial", # ) # fig.update_traces( # marker=dict(line=dict(color="black", width=2)), # selector=dict(marker=dict(color="blue")), # ) # st.plotly_chart(fig, use_container_width=True) # with columns4[1]: # fig1 = px.bar( # effectiveness_overall.sort_values(by=["ResponseMetricValue"], ascending=False), # x="Efficiency", # y="ResponseMetricName", # color="ResponseMetricName", # text_auto=True, # title="Efficiency", # ) # # Update layout and traces # fig1.update_traces( # customdata=effectiveness_overall["Efficiency"], textposition="auto" # ) # fig1.update_layout(showlegend=False) # fig1.update_yaxes(title="", showticklabels=False) # fig1.update_xaxes(title="", showticklabels=False) # fig1.update_xaxes(tickfont=dict(size=20)) # fig1.update_yaxes(tickfont=dict(size=20)) # st.plotly_chart(fig1, use_container_width=True) # Function to format metric names def format_metric_name(metric_name): return str(metric_name).lower().replace("response_metric_", "").replace("_", " ").strip().title() # Apply the formatting function to the 'ResponseMetricName' column effectiveness_overall["FormattedMetricName"] = effectiveness_overall["ResponseMetricName"].apply(format_metric_name) # Multiselect widget with all options as default, but using the formatted names for display all_metrics = effectiveness_overall["FormattedMetricName"].unique() selected_metrics = st.multiselect( "Select Metrics to Display", options=all_metrics, default=all_metrics ) # Filter the data based on the selected metrics (using formatted names) if selected_metrics: filtered_data = effectiveness_overall[ effectiveness_overall["FormattedMetricName"].isin(selected_metrics) ] # Sort values for funnel plot filtered_data = filtered_data.sort_values(by=["ResponseMetricValue"]) # Generate a consistent color mapping for all selected metrics color_map = {metric: px.colors.qualitative.Plotly[i % len(px.colors.qualitative.Plotly)] for i, metric in enumerate(filtered_data["FormattedMetricName"].unique())} # First plot: Funnel with columns4[0]: fig = px.funnel( filtered_data, x="ResponseMetricValue", y="FormattedMetricName", # Use formatted names for y-axis color="FormattedMetricName", # Use formatted names for color color_discrete_map=color_map, # Ensure consistent colors title="Effectiveness", ) fig.update_layout( showlegend=False, yaxis=dict(title="Response Metric", tickmode="array"), # Set y-axis label to 'Response Metric' ) fig.update_traces( textinfo="value", textposition="inside", texttemplate="%{x:.2s} ", hoverinfo="y+x+percent initial", ) fig.update_traces( marker=dict(line=dict(color="black", width=2)), selector=dict(marker=dict(color="blue")), ) st.plotly_chart(fig, use_container_width=True) # Second plot: Bar chart with columns4[1]: fig1 = px.bar( filtered_data.sort_values(by=["ResponseMetricValue"], ascending=False), x="Efficiency", y="FormattedMetricName", # Use formatted names for y-axis color="FormattedMetricName", # Use formatted names for color color_discrete_map=color_map, # Ensure consistent colors text_auto=True, title="Efficiency", ) # Update layout and traces fig1.update_traces( customdata=filtered_data["Efficiency"], textposition="auto" ) fig1.update_layout(showlegend=False) fig1.update_yaxes(title="", showticklabels=False) fig1.update_xaxes(title="", showticklabels=False) fig1.update_xaxes(tickfont=dict(size=20)) fig1.update_yaxes(tickfont=dict(size=20)) st.plotly_chart(fig1, use_container_width=True) else: st.info("Please select at least one response metric to display the charts.") st.header("Return Forecast by Media Channel") with st.expander("Return Forecast by Media Channel"): metric = metrics_name metric = metric.lower().replace("_", " ") + " " + "actual" summary_df_sorted.columns = [ col.lower().replace("_", " ") for col in summary_df_sorted.columns ] effectiveness = summary_df_sorted[metric] summary_df_sorted["Efficiency"] = ( summary_df_sorted[metric] / summary_df_sorted["spends optimized"] ) channel_colors = px.colors.qualitative.Plotly fig = make_subplots( rows=1, cols=3, subplot_titles=("Optimized Spends", "Effectiveness", "Efficiency"), horizontal_spacing=0.05, ) for i, channel in enumerate(summary_df_sorted["channel name"].unique()): channel_df = summary_df_sorted[summary_df_sorted["channel name"] == channel] channel_color = channel_colors[i % len(channel_colors)] fig.add_trace( go.Bar( x=channel_df["spends optimized"], y=channel_df["channel name"], text=channel_df["spends optimized"].apply(format_numbers), marker_color=channel_color, orientation="h", ), row=1, col=1, ) fig.add_trace( go.Bar( x=channel_df[metric], y=channel_df["channel name"], text=channel_df[metric].apply(format_numbers), marker_color=channel_color, orientation="h", showlegend=False, ), row=1, col=2, ) fig.add_trace( go.Bar( x=channel_df["Efficiency"], y=channel_df["channel name"], text=channel_df["Efficiency"].apply(lambda x: f"{x:.2f}"), marker_color=channel_color, orientation="h", showlegend=False, ), row=1, col=3, ) fig.update_layout( height=600, width=900, title="Media Channel Performance", showlegend=False, ) fig.update_yaxes(showticklabels=False, row=1, col=2) fig.update_yaxes(showticklabels=False, row=1, col=3) fig.update_xaxes(showticklabels=False, row=1, col=1) fig.update_xaxes(showticklabels=False, row=1, col=2) fig.update_xaxes(showticklabels=False, row=1, col=3) st.plotly_chart(fig, use_container_width=True)