Spaces:
Sleeping
Sleeping
| 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) | |