Simulator-UOPX / pages /2_Scenario_Planner.py
Pragya Jatav
version changes
d50c87c
raw
history blame
57.1 kB
import streamlit as st
from numerize.numerize import numerize
import numpy as np
from functools import partial
from collections import OrderedDict
from plotly.subplots import make_subplots
import plotly.graph_objects as go
from utilities import (
format_numbers,format_numbers_f,
load_local_css,
set_header,
initialize_data,
load_authenticator,
send_email,
channel_name_formating,
)
from classes import class_from_dict, class_to_dict
import pickle
import streamlit_authenticator as stauth
import yaml
from yaml import SafeLoader
import re
import pandas as pd
import plotly.express as px
import response_curves_model_quality as rc
st.set_page_config(layout="wide")
load_local_css("styles.css")
set_header()
for k, v in st.session_state.items():
if k not in ["logout", "login", "config"] and not k.startswith("FormSubmitter"):
st.session_state[k] = v
# ======================================================== #
# ======================= Functions ====================== #
# ======================================================== #
def optimize(key, status_placeholder):
"""
Optimize the spends for the sales
"""
channel_list = [
key for key, value in st.session_state["optimization_channels"].items() if value
]
if len(channel_list) > 0:
scenario = st.session_state["scenario"]
if key.lower() == "media spends":
with status_placeholder:
with st.spinner("Optimizing"):
result = st.session_state["scenario"].optimize(
st.session_state["total_spends_change"], channel_list
)
# elif key.lower() == "revenue":
else:
with status_placeholder:
with st.spinner("Optimizing"):
result = st.session_state["scenario"].optimize_spends(
st.session_state["total_sales_change"], channel_list
)
for channel_name, modified_spends in result:
st.session_state[channel_name] = numerize(
modified_spends * scenario.channels[channel_name].conversion_rate,
1,
)
prev_spends = (
st.session_state["scenario"].channels[channel_name].actual_total_spends
)
st.session_state[f"{channel_name}_change"] = round(
100 * (modified_spends - prev_spends) / prev_spends, 2
)
def save_scenario(scenario_name):
"""
Save the current scenario with the mentioned name in the session state
Parameters
----------
scenario_name
Name of the scenario to be saved
"""
if "saved_scenarios" not in st.session_state:
st.session_state = OrderedDict()
# st.session_state['saved_scenarios'][scenario_name] = st.session_state['scenario'].save()
st.session_state["saved_scenarios"][scenario_name] = class_to_dict(
st.session_state["scenario"]
)
st.session_state["scenario_input"] = ""
# print(type(st.session_state['saved_scenarios']))
with open("../saved_scenarios.pkl", "wb") as f:
pickle.dump(st.session_state["saved_scenarios"], f)
if "allow_spends_update" not in st.session_state:
st.session_state["allow_spends_update"] = True
if "allow_sales_update" not in st.session_state:
st.session_state["allow_sales_update"] = True
def update_sales_abs_slider():
actual_sales = _scenario.actual_total_sales
if validate_input(st.session_state["total_sales_change_abs_slider"]):
modified_sales = extract_number_for_string(
st.session_state["total_sales_change_abs_slider"]
)
st.session_state["total_sales_change"] = round(
((modified_sales / actual_sales) - 1) * 100
)
st.session_state["total_sales_change_abs"] = numerize(modified_sales, 1)
def update_sales_abs():
if (
st.session_state["total_sales_change_abs"]
in st.session_state["total_sales_change_abs_slider_options"]
):
st.session_state["allow_sales_update"] = True
else:
st.session_state["allow_sales_update"] = False
actual_sales = _scenario.actual_total_sales
if (
validate_input(st.session_state["total_sales_change_abs"])
and st.session_state["allow_sales_update"]
):
modified_sales = extract_number_for_string(
st.session_state["total_sales_change_abs"]
)
st.session_state["total_sales_change"] = round(
((modified_sales / actual_sales) - 1) * 100
)
st.session_state["total_sales_change_abs_slider"] = numerize(modified_sales, 1)
def update_sales():
st.session_state["total_sales_change_abs"] = numerize(
(1 + st.session_state["total_sales_change"] / 100)
* _scenario.actual_total_sales,
1,
)
st.session_state["total_sales_change_abs_slider"] = numerize(
(1 + st.session_state["total_sales_change"] / 100)
* _scenario.actual_total_sales,
1,
)
def update_all_spends_abs_slider():
actual_spends = _scenario.actual_total_spends
if validate_input(st.session_state["total_spends_change_abs_slider"]):
modified_spends = extract_number_for_string(
st.session_state["total_spends_change_abs_slider"]
)
st.session_state["total_spends_change"] = round(
((modified_spends / actual_spends) - 1) * 100
)
st.session_state["total_spends_change_abs"] = numerize(modified_spends, 1)
update_all_spends()
# def update_all_spends_abs_slider():
# actual_spends = _scenario.actual_total_spends
# if validate_input(st.session_state["total_spends_change_abs_slider"]):
# print("#" * 100)
# print(st.session_state["total_spends_change_abs_slider"])C:\Users\PragyaJatav\Downloads\Untitled Folder 2\simulatorAldi\pages\8_Scenario_Planner.py
# print("#" * 100)
# modified_spends = extract_number_for_string(
# st.session_state["total_spends_change_abs_slider"]
# )
# st.session_state["total_spends_change"] = (
# (modified_spends / actual_spends) - 1
# ) * 100
# st.session_state["total_spends_change_abs"] = st.session_state[
# "total_spends_change_abs_slider"
# ]
# update_all_spends()
def update_all_spends_abs():
if (
st.session_state["total_spends_change_abs"]
in st.session_state["total_spends_change_abs_slider_options"]
):
st.session_state["allow_spends_update"] = True
else:
st.session_state["allow_spends_update"] = False
actual_spends = _scenario.actual_total_spends
if (
validate_input(st.session_state["total_spends_change_abs"])
and st.session_state["allow_spends_update"]
):
modified_spends = extract_number_for_string(
st.session_state["total_spends_change_abs"]
)
st.session_state["total_spends_change"] = (
(modified_spends / actual_spends) - 1
) * 100
st.session_state["total_spends_change_abs_slider"] = st.session_state[
"total_spends_change_abs"
]
update_all_spends()
def update_spends():
st.session_state["total_spends_change_abs"] = numerize(
(1 + st.session_state["total_spends_change"] / 100)
* _scenario.actual_total_spends,
1,
)
st.session_state["total_spends_change_abs_slider"] = numerize(
(1 + st.session_state["total_spends_change"] / 100)
* _scenario.actual_total_spends,
1,
)
update_all_spends()
def update_all_spends():
"""
Updates spends for all the channels with the given overall spends change
"""
percent_change = st.session_state["total_spends_change"]
for channel_name in st.session_state["channels_list"]:
channel = st.session_state["scenario"].channels[channel_name]
current_spends = channel.actual_total_spends
modified_spends = (1 + percent_change / 100) * current_spends
st.session_state["scenario"].update(channel_name, modified_spends)
st.session_state[channel_name] = numerize(
modified_spends * channel.conversion_rate, 1
)
st.session_state[f"{channel_name}_change"] = percent_change
def extract_number_for_string(string_input):
string_input = string_input.upper()
if string_input.endswith("K"):
return float(string_input[:-1]) * 10**3
elif string_input.endswith("M"):
return float(string_input[:-1]) * 10**6
elif string_input.endswith("B"):
return float(string_input[:-1]) * 10**9
def validate_input(string_input):
pattern = r"\d+\.?\d*[K|M|B]$"
match = re.match(pattern, string_input)
if match is None:
return False
return True
def update_data_by_percent(channel_name):
prev_spends = (
st.session_state["scenario"].channels[channel_name].actual_total_spends
* st.session_state["scenario"].channels[channel_name].conversion_rate
)
modified_spends = prev_spends * (
1 + st.session_state[f"{channel_name}_change"] / 100
)
st.session_state[channel_name] = numerize(modified_spends, 1)
st.session_state["scenario"].update(
channel_name,
modified_spends
/ st.session_state["scenario"].channels[channel_name].conversion_rate,
)
def update_data(channel_name):
"""
Updates the spends for the given channel
"""
if validate_input(st.session_state[channel_name]):
modified_spends = extract_number_for_string(st.session_state[channel_name])
prev_spends = (
st.session_state["scenario"].channels[channel_name].actual_total_spends
* st.session_state["scenario"].channels[channel_name].conversion_rate
)
st.session_state[f"{channel_name}_change"] = round(
100 * (modified_spends - prev_spends) / prev_spends, 2
)
st.session_state["scenario"].update(
channel_name,
modified_spends
/ st.session_state["scenario"].channels[channel_name].conversion_rate,
)
# st.session_state['scenario'].update(channel_name, modified_spends)
# else:
# try:
# modified_spends = float(st.session_state[channel_name])
# prev_spends = st.session_state['scenario'].channels[channel_name].actual_total_spends * st.session_state['scenario'].channels[channel_name].conversion_rate
# st.session_state[f'{channel_name}_change'] = round(100*(modified_spends - prev_spends) / prev_spends,2)
# st.session_state['scenario'].update(channel_name, modified_spends/st.session_state['scenario'].channels[channel_name].conversion_rate)
# st.session_state[f'{channel_name}'] = numerize(modified_spends,1)
# except ValueError:
# st.write('Invalid input')
def select_channel_for_optimization(channel_name):
"""
Marks the given channel for optimization
"""
st.session_state["optimization_channels"][channel_name] = st.session_state[
f"{channel_name}_selected"
]
def select_all_channels_for_optimization():
"""
Marks all the channel for optimization
"""
for channel_name in st.session_state["optimization_channels"].keys():
st.session_state[f"{channel_name}_selected"] = st.session_state[
"optimze_all_channels"
]
st.session_state["optimization_channels"][channel_name] = st.session_state[
"optimze_all_channels"
]
def update_penalty():
"""
Updates the penalty flag for sales calculation
"""
st.session_state["scenario"].update_penalty(st.session_state["apply_penalty"])
def reset_scenario(panel_selected, file_selected, updated_rcs):
# #print(st.session_state['default_scenario_dict'])
# st.session_state['scenario'] = class_from_dict(st.session_state['default_scenario_dict'])
# for channel in st.session_state['scenario'].channels.values():
# st.session_state[channel.name] = float(channel.actual_total_spends * channel.conversion_rate)
# initialize_data()
if panel_selected == "Total Market":
initialize_data(
panel=panel_selected,
target_file=file_selected,
updated_rcs=updated_rcs,
metrics=metrics_selected,
)
panel = None
else:
initialize_data(
panel=panel_selected,
target_file=file_selected,
updated_rcs=updated_rcs,
metrics=metrics_selected,
)
for channel_name in st.session_state["channels_list"]:
st.session_state[f"{channel_name}_selected"] = False
st.session_state[f"{channel_name}_change"] = 0
st.session_state["optimze_all_channels"] = False
st.session_state["total_sales_change"] = 0
update_spends()
update_sales()
reset_inputs()
# st.rerun()
def format_number(num):
if num >= 1_000_000:
return f"{num / 1_000_000:.2f}M"
elif num >= 1_000:
return f"{num / 1_000:.0f}K"
else:
return f"{num:.2f}"
def summary_plot(data, x, y, title, text_column):
fig = px.bar(
data,
x=x,
y=y,
orientation="h",
title=title,
text=text_column,
color="Channel_name",
)
# Convert text_column to numeric values
data[text_column] = pd.to_numeric(data[text_column], errors="coerce")
# Update the format of the displayed text based on magnitude
fig.update_traces(
texttemplate="%{text:.2s}",
textposition="outside",
hovertemplate="%{x:.2s}",
)
fig.update_layout(xaxis_title=x, yaxis_title="Channel Name", showlegend=False)
return fig
def s_curve(x, K, b, a, x0):
return K / (1 + b * np.exp(-a * (x - x0)))
def find_segment_value(x, roi, mroi):
start_value = x[0]
end_value = x[len(x) - 1]
# Condition for green region: Both MROI and ROI > 1
green_condition = (roi > 1) & (mroi > 1)
left_indices = np.where(green_condition)[0]
left_value = x[left_indices[0]] if left_indices.size > 0 else x[0]
right_indices = np.where(green_condition)[0]
right_value = x[right_indices[-1]] if right_indices.size > 0 else x[0]
return start_value, end_value, left_value, right_value
def calculate_rgba(
start_value, end_value, left_value, right_value, current_channel_spends
):
# Initialize alpha to None for clarity
alpha = None
# Determine the color and calculate relative_position and alpha based on the point's position
if start_value <= current_channel_spends <= left_value:
color = "yellow"
relative_position = (current_channel_spends - start_value) / (
left_value - start_value
)
alpha = 0.8 - (0.6 * relative_position) # Alpha decreases from start to end
elif left_value < current_channel_spends <= right_value:
color = "green"
relative_position = (current_channel_spends - left_value) / (
right_value - left_value
)
alpha = 0.8 - (0.6 * relative_position) # Alpha decreases from start to end
elif right_value < current_channel_spends <= end_value:
color = "red"
relative_position = (current_channel_spends - right_value) / (
end_value - right_value
)
alpha = 0.2 + (0.6 * relative_position) # Alpha increases from start to end
else:
# Default case, if the spends are outside the defined ranges
return "rgba(136, 136, 136, 0.5)" # Grey for values outside the range
# Ensure alpha is within the intended range in case of any calculation overshoot
alpha = max(0.2, min(alpha, 0.8))
# Define color codes for RGBA
color_codes = {
"yellow": "255, 255, 0", # RGB for yellow
"green": "0, 128, 0", # RGB for green
"red": "255, 0, 0", # RGB for red
}
rgba = f"rgba({color_codes[color]}, {alpha})"
return rgba
def debug_temp(x_test, power, K, b, a, x0):
print("*" * 100)
# Calculate the count of bins
count_lower_bin = sum(1 for x in x_test if x <= 2524)
count_center_bin = sum(1 for x in x_test if x > 2524 and x <= 3377)
count_ = sum(1 for x in x_test if x > 3377)
print(
f"""
lower : {count_lower_bin}
center : {count_center_bin}
upper : {count_}
"""
)
# @st.cache
def plot_response_curves(summary_df_sorted):
# rows = (
# len(channels_list) // cols
# if len(channels_list) % cols == 0
# else len(channels_list) // cols + 1
# )
# rcs = st.session_state["rcs"]
# shapes = []
# fig = make_subplots(rows=rows, cols=cols, subplot_titles=channels_list)
channel_cols = [
'BroadcastTV',
'CableTV',
'Connected&OTTTV',
'DisplayProspecting',
'DisplayRetargeting',
'Video',
'SocialProspecting',
'SocialRetargeting',
'SearchBrand',
'SearchNon-brand',
'DigitalPartners',
'Audio',
'Email']
summary_df_sorted.index = summary_df_sorted["Channel_name"]
figures = [rc.response_curves(channels_list[i], summary_df_sorted["Optimized_spend"][channels_list[i]]/104, summary_df_sorted["New_sales"][channels_list[i]]/104) for i in range(13)]
# Display figures in a grid layout
cols = st.columns(3) # 4 columns for the grid
for idx, fig in enumerate(figures):
col = cols[idx % 3]
with col:
st.plotly_chart(fig, use_container_width=True)
# cols = st.columns(3)
# for i in range(0, len(channels_list)):
# col = channels_list[i]
# if col == "Panel":
# continue
# st.write(col)
# x_modified = summary_df_sorted["Optimized_spend"][col]/104
# y_modified = summary_df_sorted["New_sales"][col]/104
# st.plotly_chart(rc.response_curves(col,x_modified,y_modified))
# @st.cache
# def plot_response_curves():
# cols = 4
# rcs = st.session_state["rcs"]
# shapes = []
# fig = make_subplots(rows=6, cols=cols, subplot_titles=channels_list)
# for i in range(0, len(channels_list)):
# col = channels_list[i]
# x = st.session_state["actual_df"][col].values
# spends = x.sum()
# power = np.ceil(np.log(x.max()) / np.log(10)) - 3
# x = np.linspace(0, 3 * x.max(), 200)
# K = rcs[col]["K"]
# b = rcs[col]["b"]
# a = rcs[col]["a"]
# x0 = rcs[col]["x0"]
# y = s_curve(x / 10**power, K, b, a, x0)
# roi = y / x
# marginal_roi = a * (y) * (1 - y / K)
# fig.add_trace(
# go.Scatter(
# x=52
# * x
# * st.session_state["scenario"].channels[col].conversion_rate,
# y=52 * y,
# name=col,
# customdata=np.stack((roi, marginal_roi), axis=-1),
# hovertemplate="Spend:%{x:$.2s}<br>Sale:%{y:$.2s}<br>ROI:%{customdata[0]:.3f}<br>MROI:%{customdata[1]:.3f}",
# ),
# row=1 + (i) // cols,
# col=i % cols + 1,
# )
# fig.add_trace(
# go.Scatter(
# x=[
# spends
# * st.session_state["scenario"]
# .channels[col]
# .conversion_rate
# ],
# y=[52 * s_curve(spends / (10**power * 52), K, b, a, x0)],
# name=col,
# legendgroup=col,
# showlegend=False,
# marker=dict(color=["black"]),
# ),
# row=1 + (i) // cols,
# col=i % cols + 1,
# )
# shapes.append(
# go.layout.Shape(
# type="line",
# x0=0,
# y0=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
# x1=spends
# * st.session_state["scenario"].channels[col].conversion_rate,
# y1=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
# line_width=1,
# line_dash="dash",
# line_color="black",
# xref=f"x{i+1}",
# yref=f"y{i+1}",
# )
# )
# shapes.append(
# go.layout.Shape(
# type="line",
# x0=spends
# * st.session_state["scenario"].channels[col].conversion_rate,
# y0=0,
# x1=spends
# * st.session_state["scenario"].channels[col].conversion_rate,
# y1=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
# line_width=1,
# line_dash="dash",
# line_color="black",
# xref=f"x{i+1}",
# yref=f"y{i+1}",
# )
# )
# fig.update_layout(
# height=1500,
# width=1000,
# title_text="Response Curves",
# showlegend=False,
# shapes=shapes,
# )
# fig.update_annotations(font_size=10)
# fig.update_xaxes(title="Spends")
# fig.update_yaxes(title=target)
# return fig
# ======================================================== #
# ==================== HTML Components =================== #
# ======================================================== #
def generate_spending_header(heading):
return st.markdown(
f"""<h2 class="spends-header">{heading}</h2>""", unsafe_allow_html=True
)
# ======================================================== #
# =================== Session variables ================== #
# ======================================================== #
with open("config.yaml") as file:
config = yaml.load(file, Loader=SafeLoader)
st.session_state["config"] = config
authenticator = stauth.Authenticate(
config["credentials"],
config["cookie"]["name"],
config["cookie"]["key"],
config["cookie"]["expiry_days"],
config["preauthorized"],
)
st.session_state["authenticator"] = authenticator
name, authentication_status, username = authenticator.login("Login", "main")
auth_status = st.session_state.get("authentication_status")
import os
import glob
def get_excel_names(directory):
# Create a list to hold the final parts of the filenames
last_portions = []
# Patterns to match Excel files (.xlsx and .xls) that contain @#
patterns = [
os.path.join(directory, "*@#*.xlsx"),
os.path.join(directory, "*@#*.xls"),
]
# Process each pattern
for pattern in patterns:
files = glob.glob(pattern)
# Extracting the last portion after @# for each file
for file in files:
base_name = os.path.basename(file)
last_portion = base_name.split("@#")[-1]
last_portion = last_portion.replace(".xlsx", "").replace(
".xls", ""
) # Removing extensions
last_portions.append(last_portion)
return last_portions
def name_formating(channel_name):
# Replace underscores with spaces
name_mod = channel_name.replace("_", " ")
# Capitalize the first letter of each word
name_mod = name_mod.title()
return name_mod
@st.cache_data(show_spinner=False)
def panel_fetch(file_selected):
raw_data_mmm_df = pd.read_excel(file_selected, sheet_name="RAW DATA MMM")
# if "Panel" in raw_data_mmm_df.columns:
# panel = list(set(raw_data_mmm_df["Panel"]))
# else:
# raw_data_mmm_df = None
# panel = None
# raw_data_mmm_df = None
panel = None
return panel
def reset_inputs():
if "total_spends_change_abs" in st.session_state:
del st.session_state.total_spends_change_abs
if "total_spends_change" in st.session_state:
del st.session_state.total_spends_change
if "total_spends_change_abs_slider" in st.session_state:
del st.session_state.total_spends_change_abs_slider
if "total_sales_change_abs" in st.session_state:
del st.session_state.total_sales_change_abs
if "total_sales_change" in st.session_state:
del st.session_state.total_sales_change
if "total_sales_change_abs_slider" in st.session_state:
del st.session_state.total_sales_change_abs_slider
st.session_state["initialized"] = False
if auth_status == True:
authenticator.logout("Logout", "main")
st.header("Scenario Planner")
def scenario_planner_plots():
with st.expander('Optimized Spends Overview'):
# if st.button('Refresh'):
# st.experimental_rerun()
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# Define light colors for bars
import plotly.graph_objects as go
from plotly.subplots import make_subplots
st.empty()
#st.header('Model Result Analysis')
spends_data=pd.read_excel('Overview_data_test.xlsx')
with open('summary_df.pkl', 'rb') as file:
summary_df_sorted = pickle.load(file)
#st.write(summary_df_sorted)
# selected_scenario= st.selectbox('Select Saved Scenarios',['S1','S2'])
summary_df_sorted=summary_df_sorted.sort_values(by=['Optimized_spend'],ascending=False)
summary_df_sorted['old_efficiency']=(summary_df_sorted['Old_sales']/summary_df_sorted['Old_sales'].sum())/(summary_df_sorted['Actual_spend']/summary_df_sorted['Actual_spend'].sum())
summary_df_sorted['new_efficiency']=(summary_df_sorted['New_sales']/summary_df_sorted['New_sales'].sum())/(summary_df_sorted['Optimized_spend']/summary_df_sorted['Optimized_spend'].sum())
summary_df_sorted['old_roi']=summary_df_sorted['Old_sales']/summary_df_sorted['Actual_spend']
summary_df_sorted['new_roi']=summary_df_sorted['New_sales']/summary_df_sorted['Optimized_spend']
total_actual_spend = summary_df_sorted['Actual_spend'].sum()
total_optimized_spend = summary_df_sorted['Optimized_spend'].sum()
actual_spend_percentage = (summary_df_sorted['Actual_spend'] / total_actual_spend) * 100
optimized_spend_percentage = (summary_df_sorted['Optimized_spend'] / total_optimized_spend) * 100
light_blue = 'rgba(0, 31, 120, 0.7)'
light_orange = 'rgba(0, 181, 219, 0.7)'
light_green = 'rgba(240, 61, 20, 0.7)'
light_red = 'rgba(250, 110, 10, 0.7)'
light_purple = 'rgba(255, 191, 69, 0.7)'
# # Create subplots with one row and two columns
# fig = make_subplots(rows=3, cols=1, subplot_titles=("Actual vs. Optimized Spend", "Actual vs. Optimized Contribution", "Actual vs. Optimized ROI"))
# # Add actual vs optimized spend bars
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Actual_spend'], name='Actual',
# text=summary_df_sorted['Actual_spend'].apply(format_number) + ' '+' (' + actual_spend_percentage.round(2).astype(str) + '%)',
# marker_color=light_blue, orientation='h'),
# row=1,
# col=1)
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Optimized_spend'], name='Optimized',
# text=summary_df_sorted['Optimized_spend'].apply(format_number) + ' (' + optimized_spend_percentage.round(2).astype(str) + '%)',
# marker_color=light_orange,
# orientation='h'),
# row=1,
# col=1)
# fig.update_xaxes(title_text="Amount", row=1, col=1)
# # Add actual vs optimized Contribution
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['New_sales'],
# name='Optimized Contribution',text=summary_df_sorted['New_sales'].apply(format_number),
# marker_color=light_orange, orientation='h',showlegend=False), row=2, col=1)
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Old_sales'],
# name='Actual Contribution',text=summary_df_sorted['Old_sales'].apply(format_number),
# marker_color=light_blue, orientation='h',showlegend=False), row=2, col=1)
# fig.update_xaxes(title_text="Contribution", row=2, col=1)
# # Add actual vs optimized ROI bars
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['new_roi'],
# name='Optimized ROI',text=summary_df_sorted['new_roi'].apply(format_number) ,
# marker_color=light_orange, orientation='h',showlegend=False), row=3, col=1)
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['old_roi'],
# name='Actual ROI', text=summary_df_sorted['old_roi'].apply(format_number) ,
# marker_color=light_blue, orientation='h',showlegend=False), row=3, col=1)
# fig.update_xaxes(title_text="ROI", row=3, col=1)
# # Update layout
# fig.update_layout(title_text="Actual vs. Optimized Metrics for Media Channels",
# showlegend=True, yaxis=dict(title='Media Channels', autorange="reversed"))
# st.plotly_chart(fig,use_container_width=True)
# Create subplots with one row and two columns
fig = go.Figure()
# Add actual vs optimized spend bars
fig.add_trace(go.Bar(x=summary_df_sorted['Channel_name'], y=summary_df_sorted['Actual_spend'], name='Actual',
text=summary_df_sorted['Actual_spend'].apply(format_number) + ' '
# +
# ' '+
# '</br> (' + actual_spend_percentage.astype(int).astype(str) + '%)'
,textposition='outside',#textfont=dict(size=30),
marker_color=light_blue))
fig.add_trace(go.Bar(x=summary_df_sorted['Channel_name'], y=summary_df_sorted['Optimized_spend'], name='Optimized',
text=summary_df_sorted['Optimized_spend'].apply(format_number) + ' '
# +
# '</br> (' + optimized_spend_percentage.astype(int).astype(str) + '%)'
,textposition='outside',#textfont=dict(size=30),
marker_color=light_orange))
fig.update_xaxes(title_text="Channels")
fig.update_yaxes(title_text="Spends ($)")
fig.update_layout(
title = "Actual vs. Optimized Spends",
margin=dict(t=40, b=40, l=40, r=40)
)
st.plotly_chart(fig,use_container_width=True)
# Add actual vs optimized Contribution
fig = go.Figure()
fig.add_trace(go.Bar(x=summary_df_sorted['Channel_name'], y=summary_df_sorted['Old_sales'],
name='Actual Contribution',text=summary_df_sorted['Old_sales'].apply(format_number),textposition='outside',
marker_color=light_blue,showlegend=True))
fig.add_trace(go.Bar(x=summary_df_sorted['Channel_name'], y=summary_df_sorted['New_sales'],
name='Optimized Contribution',text=summary_df_sorted['New_sales'].apply(format_number),textposition='outside',
marker_color=light_orange, showlegend=True))
fig.update_yaxes(title_text="Contribution")
fig.update_xaxes(title_text="Channels")
fig.update_layout(
title = "Actual vs. Optimized Contributions",
margin=dict(t=40, b=40, l=40, r=40)
# yaxis=dict(range=[0, 0.002]),
)
st.plotly_chart(fig,use_container_width=True)
# Add actual vs optimized Efficiency bars
fig = go.Figure()
summary_df_sorted_p = summary_df_sorted[summary_df_sorted['Channel_name']!="Panel"]
fig.add_trace(go.Bar(x=summary_df_sorted_p['Channel_name'], y=summary_df_sorted_p['old_efficiency'],
name='Actual Efficiency', text=summary_df_sorted_p['old_efficiency'].apply(format_number) ,textposition='outside',
marker_color=light_blue,showlegend=True))
fig.add_trace(go.Bar(x=summary_df_sorted_p['Channel_name'], y=summary_df_sorted_p['new_efficiency'],
name='Optimized Efficiency',text=summary_df_sorted_p['new_efficiency'].apply(format_number),textposition='outside' ,
marker_color=light_orange,showlegend=True))
fig.update_xaxes(title_text="Channels")
fig.update_yaxes(title_text="ROI")
fig.update_layout(
title = "Actual vs. Optimized ROI",
margin=dict(t=40, b=40, l=40, r=40),
# yaxis=dict(range=[0, 0.002]),
)
st.plotly_chart(fig,use_container_width=True)
# Response Metrics
directory = "metrics_level_data"
metrics_list = get_excel_names(directory)
# metrics_selected = col1.selectbox(
# "Response Metrics",
# metrics_list,
# format_func=name_formating,
# index=0,
# on_change=reset_inputs,
# )
metrics_selected='prospects'
# Target
target = name_formating(metrics_selected)
file_selected = (
f"Overview_data_test_panel@#{metrics_selected}.xlsx"
)
# Panel List
panel_list = panel_fetch(file_selected)
# # Panel Selected
# panel_selected = st.selectbox(
# "Markets",
# ["Total Market"] + panel_list,
# index=0,
# on_change=reset_inputs,
# )
# st.write(panel_selected)
panel_selected = "Total Market"
st.session_state['selected_markets']=panel_selected
if "update_rcs" in st.session_state:
updated_rcs = st.session_state["update_rcs"]
else:
updated_rcs = None
if "first_time" not in st.session_state:
st.session_state["first_time"] = True
# Check if state is initiaized
is_state_initiaized = st.session_state.get("initialized", False)
if not is_state_initiaized or st.session_state["first_time"]:
# initialize_data()
if panel_selected == "Total Market":
initialize_data(
panel=panel_selected,
target_file=file_selected,
updated_rcs=updated_rcs,
metrics=metrics_selected,
)
panel = None
else:
initialize_data(
panel=panel_selected,
target_file=file_selected,
updated_rcs=updated_rcs,
metrics=metrics_selected,
)
st.session_state["initialized"] = True
st.session_state["first_time"] = False
# initialize_data(
# panel=panel_selected,
# target_file=file_selected,
# updated_rcs=updated_rcs,
# metrics=metrics_selected,
# )
# st.session_state["initialized"] = True
# st.session_state["first_time"] = False
# Channels List
channels_list = st.session_state["channels_list"]
# ======================================================== #
# ========================== UI ========================== #
# ======================================================== #
# print(list(st.session_state.keys()))
main_header = st.columns((2, 2))
sub_header = st.columns((1, 1, 1, 1))
_scenario = st.session_state["scenario"]
if "total_spends_change" not in st.session_state:
st.session_state.total_spends_change = 0
if "total_sales_change" not in st.session_state:
st.session_state.total_sales_change = 0
if "total_spends_change_abs" not in st.session_state:
st.session_state["total_spends_change_abs"] = numerize(
_scenario.actual_total_spends, 1
)
if "total_sales_change_abs" not in st.session_state:
st.session_state["total_sales_change_abs"] = numerize(
_scenario.actual_total_sales, 1
)
if "total_spends_change_abs_slider" not in st.session_state:
st.session_state.total_spends_change_abs_slider = numerize(
_scenario.actual_total_spends, 1
)
if "total_sales_change_abs_slider" not in st.session_state:
st.session_state.total_sales_change_abs_slider = numerize(
_scenario.actual_total_sales, 1
)
with main_header[0]:
st.subheader("Actual")
with main_header[-1]:
st.subheader("Simulated")
with sub_header[0]:
st.metric(label="Spends", value=format_numbers(_scenario.actual_total_spends))
with sub_header[1]:
st.metric(
label=target,
value=format_numbers_f(
float(_scenario.actual_total_sales)
),
)
with sub_header[2]:
st.metric(
label="Spends",
value=format_numbers(_scenario.modified_total_spends),
delta=numerize(_scenario.delta_spends, 1),
)
with sub_header[3]:
st.metric(
label=target,
value=format_numbers_f(
float(_scenario.modified_total_sales)
),
delta=numerize(_scenario.delta_sales, 1),
)
with st.expander("Channel Spends Simulator", expanded=True):
_columns1 = st.columns((2, 2, 1, 1))
with _columns1[0]:
optimization_selection = st.selectbox(
"Optimize", options=["Media Spends", target], key="optimization_key"
)
with _columns1[1]:
st.markdown("#")
# if st.checkbox(
# label="Optimize all Channels",
# key="optimze_all_channels",
# value=False,
# # on_change=select_all_channels_for_optimization,
# ):
# select_all_channels_for_optimization()
st.checkbox(
label="Optimize all Channels",
key="optimze_all_channels",
value=False,
on_change=select_all_channels_for_optimization,
)
with _columns1[2]:
st.markdown("#")
# st.button(
# "Optimize",
# on_click=optimize,
# args=(st.session_state["optimization_key"]),
# use_container_width=True,
# )
optimize_placeholder = st.empty()
with _columns1[3]:
st.markdown("#")
st.button(
"Reset",
on_click=reset_scenario,
args=(panel_selected, file_selected, updated_rcs),
# use_container_width=True,
)
# st.write(target)
_columns2 = st.columns((2, 2, 2))
if st.session_state["optimization_key"] == "Media Spends":
with _columns2[0]:
spend_input = st.text_input(
"Absolute",
key="total_spends_change_abs",
# label_visibility="collapsed",
on_change=update_all_spends_abs,
)
with _columns2[1]:
st.number_input(
"Percent Change",
key="total_spends_change",
min_value=-50,
max_value=50,
step=1,
on_change=update_spends,
)
with _columns2[2]:
min_value = round(_scenario.actual_total_spends * 0.5)
max_value = round(_scenario.actual_total_spends * 1.5)
st.session_state["total_spends_change_abs_slider_options"] = [
numerize(value, 1)
for value in range(min_value, max_value + 1, int(1e4))
]
# st.select_slider(
# "Absolute Slider",
# options=st.session_state["total_spends_change_abs_slider_options"],
# key="total_spends_change_abs_slider",
# on_change=update_all_spends_abs_slider,
# )
elif st.session_state["optimization_key"] == target:
# st.write(target)
with _columns2[0]:
sales_input = st.text_input(
"Absolute",
key="total_sales_change_abs",
on_change=update_sales_abs,
)
with _columns2[1]:
st.number_input(
"Percent Change",
key="total_sales_change",
min_value=-50,
max_value=50,
step=1,
on_change=update_sales,
)
with _columns2[2]:
min_value = round(_scenario.actual_total_sales * 0.5)
max_value = round(_scenario.actual_total_sales * 1.5)
st.write(min_value)
st.write(max_value)
# for value in range(min_value, max_value + 1, int(100)):
# st.write(numerize(value, 1))
st.session_state["total_sales_change_abs_slider_options"] = [
numerize(value, 1)
for value in range(min_value, max_value + 1, int(100))
]
st.select_slider(
"Absolute Slider",
options=st.session_state["total_sales_change_abs_slider_options"],
key="total_sales_change_abs_slider",
on_change=update_sales_abs_slider,
# value=numerize(min_value, 1)
)
if (
not st.session_state["allow_sales_update"]
and optimization_selection == target
):
st.warning("Invalid Input")
if (
not st.session_state["allow_spends_update"]
and optimization_selection == "Media Spends"
):
st.warning("Invalid Input")
status_placeholder = st.empty()
# if optimize_placeholder.button("Optimize", use_container_width=True):
# optimize(st.session_state["optimization_key"], status_placeholder)
# st.rerun()
optimize_placeholder.button(
"Optimize",
on_click=optimize,
args=(st.session_state["optimization_key"], status_placeholder),
# use_container_width=True,
)
st.markdown("""<hr class="spends-heading-seperator">""", unsafe_allow_html=True)
_columns = st.columns((2.5, 2, 1.5, 1.5, 1))
with _columns[0]:
generate_spending_header("Channel")
with _columns[1]:
generate_spending_header("Spends Input")
with _columns[2]:
generate_spending_header("Spends")
with _columns[3]:
generate_spending_header(target)
with _columns[4]:
generate_spending_header("Optimize")
st.markdown("""<hr class="spends-heading-seperator">""", unsafe_allow_html=True)
if "acutual_predicted" not in st.session_state:
st.session_state["acutual_predicted"] = {
"Channel_name": [],
"Actual_spend": [],
"Optimized_spend": [],
"Delta": [],
"New_sales":[],
"Old_sales":[]
}
for i, channel_name in enumerate(channels_list):
# st.write(channel_name)
_channel_class = st.session_state["scenario"].channels[channel_name]
_columns = st.columns((2.5, 1.5, 1.5, 1.5, 1))
with _columns[0]:
st.write(channel_name_formating(channel_name))
bin_placeholder = st.container()
with _columns[1]:
channel_bounds = _channel_class.bounds
channel_spends = float(_channel_class.actual_total_spends)
min_value = float((1 + channel_bounds[0] / 100) * channel_spends)
max_value = float((1 + channel_bounds[1] / 100) * channel_spends)
##print(st.session_state[channel_name])
spend_input = st.text_input(
channel_name,
key=channel_name,
label_visibility="collapsed",
on_change=partial(update_data, channel_name),
)
if not validate_input(spend_input):
st.error("Invalid input")
channel_name_current = f"{channel_name}_change"
st.number_input(
"Percent Change",
key=channel_name_current,
step=1,
on_change=partial(update_data_by_percent, channel_name),
)
with _columns[2]:
# spends
current_channel_spends = float(
_channel_class.modified_total_spends
* _channel_class.conversion_rate
)
actual_channel_spends = float(
_channel_class.actual_total_spends * _channel_class.conversion_rate
)
spends_delta = float(
_channel_class.delta_spends * _channel_class.conversion_rate
)
st.session_state["acutual_predicted"]["Channel_name"].append(
channel_name
)
st.session_state["acutual_predicted"]["Actual_spend"].append(
actual_channel_spends
)
st.session_state["acutual_predicted"]["Optimized_spend"].append(
current_channel_spends
)
st.session_state["acutual_predicted"]["Delta"].append(spends_delta)
## REMOVE
st.metric(
"Spends",
format_numbers(current_channel_spends),
delta=numerize(spends_delta, 1),
label_visibility="collapsed",
)
with _columns[3]:
# sales
current_channel_sales = float(_channel_class.modified_total_sales)
actual_channel_sales = float(_channel_class.actual_total_sales)
sales_delta = float(_channel_class.delta_sales)
st.session_state["acutual_predicted"]["Old_sales"].append(actual_channel_sales)
st.session_state["acutual_predicted"]["New_sales"].append(current_channel_sales)
#st.write(actual_channel_sales)
st.metric(
target,
format_numbers_f(current_channel_sales),
delta=numerize(sales_delta, 1),
label_visibility="collapsed",
)
with _columns[4]:
# if st.checkbox(
# label="select for optimization",
# key=f"{channel_name}_selected",
# value=False,
# # on_change=partial(select_channel_for_optimization, channel_name),
# label_visibility="collapsed",
# ):
# select_channel_for_optimization(channel_name)
st.checkbox(
label="select for optimization",
key=f"{channel_name}_selected",
value=False,
on_change=partial(select_channel_for_optimization, channel_name),
label_visibility="collapsed",
)
st.markdown(
"""<hr class="spends-child-seperator">""",
unsafe_allow_html=True,
)
# Bins
col = channels_list[i]
x_actual = st.session_state["scenario"].channels[col].actual_spends
x_modified = st.session_state["scenario"].channels[col].modified_spends
# x_modified_total = 0
# for c in channels_list:
# # st.write(c)
# # st.write(st.session_state["scenario"].channels[c].modified_spends)
# x_modified_total = x_modified_total + st.session_state["scenario"].channels[c].modified_spends.sum()
# st.write(x_modified_total)
x_total = x_modified.sum()
power = np.ceil(np.log(x_actual.max()) / np.log(10)) - 3
updated_rcs_key = f"{metrics_selected}#@{panel_selected}#@{channel_name}"
if updated_rcs and updated_rcs_key in list(updated_rcs.keys()):
K = updated_rcs[updated_rcs_key]["K"]
b = updated_rcs[updated_rcs_key]["b"]
a = updated_rcs[updated_rcs_key]["a"]
x0 = updated_rcs[updated_rcs_key]["x0"]
else:
K = st.session_state["rcs"][col]["K"]
b = st.session_state["rcs"][col]["b"]
a = st.session_state["rcs"][col]["a"]
x0 = st.session_state["rcs"][col]["x0"]
x_plot = np.linspace(0, 5 * x_actual.sum(), 200)
# Append current_channel_spends to the end of x_plot
x_plot = np.append(x_plot, current_channel_spends)
x, y, marginal_roi = [], [], []
for x_p in x_plot:
x.append(x_p * x_actual / x_actual.sum())
for index in range(len(x_plot)):
y.append(s_curve(x[index] / 10**power, K, b, a, x0))
for index in range(len(x_plot)):
marginal_roi.append(
a * y[index] * (1 - y[index] / np.maximum(K, np.finfo(float).eps))
)
x = (
np.sum(x, axis=1)
* st.session_state["scenario"].channels[col].conversion_rate
)
y = np.sum(y, axis=1)
marginal_roi = (
np.average(marginal_roi, axis=1)
/ st.session_state["scenario"].channels[col].conversion_rate
)
roi = y / np.maximum(x, np.finfo(float).eps)
# roi = (y/np.sum(y))/(x/np.sum(x))
# st.write(x)
# st.write(y)
# st.write(roi)
# st.write(roi[-1])
roi_current, marginal_roi_current = roi[-1], marginal_roi[-1]
x, y, roi, marginal_roi = (
x[:-1],
y[:-1],
roi[:-1],
marginal_roi[:-1],
) # Drop data for current spends
# roi_current =
start_value, end_value, left_value, right_value = find_segment_value(
x,
roi,
marginal_roi,
)
#st.write(roi_current)
rgba = calculate_rgba(
start_value,
end_value,
left_value,
right_value,
current_channel_spends,
)
summary_df = pd.DataFrame(st.session_state["acutual_predicted"])
# st.dataframe(summary_df)
summary_df.drop_duplicates(subset="Channel_name", keep="last", inplace=True)
# st.dataframe(summary_df)
summary_df_sorted = summary_df.sort_values(by="Delta", ascending=False)
summary_df_sorted["Delta_percent"] = np.round(
((summary_df_sorted["Optimized_spend"] / summary_df_sorted["Actual_spend"]) - 1)
* 100,
2,
)
summary_df_sorted=summary_df_sorted.sort_values(by=['Optimized_spend'],ascending=False)
summary_df_sorted['old_efficiency']=(summary_df_sorted['Old_sales']/summary_df_sorted['Old_sales'].sum())/(summary_df_sorted['Actual_spend']/summary_df_sorted['Actual_spend'].sum())
summary_df_sorted['new_efficiency']=(summary_df_sorted['New_sales']/summary_df_sorted['New_sales'].sum())/(summary_df_sorted['Optimized_spend']/summary_df_sorted['Optimized_spend'].sum())
a = (summary_df_sorted[summary_df_sorted['Channel_name']== col]).reset_index()['new_efficiency'][0]
# st.write(a)
with bin_placeholder:
if a> 1:
fill_color_box = "#98fb98"
elif a <1:
fill_color_box = "#ff6868"
else:
fill_color_box = "#ff6868"
st.markdown(
f"""
<div style="
border-radius: 12px;
background-color: {fill_color_box};
padding: 10px;
text-align: center;
color: {'black'};
">
<p style="margin: 0; font-size: 20px;">Efficiency: {round(a,2)}</p>
<!--<p style="margin: 0; font-size: 20px;">Marginal ROI: {round(marginal_roi_current,1)}</p>-->
</div>
""",
unsafe_allow_html=True,
)
with st.expander("See Response Curves", expanded=True):
fig = plot_response_curves(summary_df_sorted)
# st.plotly_chart(rc.response_curves(col))
# st.plotly_chart(fig, use_container_width=True)
summary_df = pd.DataFrame(st.session_state["acutual_predicted"])
# st.dataframe(summary_df)
summary_df.drop_duplicates(subset="Channel_name", keep="last", inplace=True)
# st.dataframe(summary_df)
summary_df_sorted = summary_df.sort_values(by="Delta", ascending=False)
summary_df_sorted["Delta_percent"] = np.round(
((summary_df_sorted["Optimized_spend"] / summary_df_sorted["Actual_spend"]) - 1)
* 100,
2,
)
with open("summary_df.pkl", "wb") as f:
pickle.dump(summary_df_sorted, f)
# st.dataframe(summary_df_sorted)
# ___columns=st.columns(3)
# with ___columns[2]:
# fig=summary_plot(summary_df_sorted, x='Delta_percent', y='Channel_name', title='Delta', text_column='Delta_percent')
# st.plotly_chart(fig,use_container_width=True)
# with ___columns[0]:
# fig=summary_plot(summary_df_sorted, x='Actual_spend', y='Channel_name', title='Actual Spend', text_column='Actual_spend')
# st.plotly_chart(fig,use_container_width=True)
# with ___columns[1]:
# fig=summary_plot(summary_df_sorted, x='Optimized_spend', y='Channel_name', title='Planned Spend', text_column='Optimized_spend')
# st.plotly_chart(fig,use_container_width=True)
scenario_planner_plots()
_columns = st.columns(2)
# with _columns[0]:
st.subheader("Save Scenario")
scenario_name = st.text_input(
"Scenario name",
key="scenario_input",
placeholder="Scenario name",
label_visibility="collapsed",
)
st.button(
"Save",
on_click=lambda: save_scenario(scenario_name),
disabled=len(st.session_state["scenario_input"]) == 0,#use_container_width=True
)
elif auth_status == False:
st.error("Username/Password is incorrect")
if auth_status != True:
try:
username_forgot_pw, email_forgot_password, random_password = (
authenticator.forgot_password("Forgot password")
)
if username_forgot_pw:
st.session_state["config"]["credentials"]["usernames"][username_forgot_pw][
"password"
] = stauth.Hasher([random_password]).generate()[0]
send_email(email_forgot_password, random_password)
st.success("New password sent securely")
# Random password to be transferred to user securely
elif username_forgot_pw == False:
st.error("Username not found")
except Exception as e:
st.error(e)