Spaces:
Sleeping
Sleeping
Pragya Jatav
commited on
Commit
·
85d2c7e
1
Parent(s):
c84cfaa
test 7
Browse files- Model_Result_Overview.py +174 -62
- Overview_data_test_panel@#prospects.xlsx +0 -0
- Streamlit_functions.py +764 -0
- Test/scenario_test_df.csv +45 -45
- __pycache__/Streamlit_functions.cpython-310.pyc +0 -0
- __pycache__/response_curves_model_quality.cpython-310.pyc +0 -0
- __pycache__/response_curves_model_quality_base.cpython-310.pyc +0 -0
- all_solutions_2024-05-09.json +148 -0
- pages/2_Scenario_Planner.py +1532 -0
- pages/3_Saved_Scenarios.py +420 -0
- pages/4_Model Quality.py +57 -0
- pages/5_Glossary.py +33 -0
- response_curves_model_quality.py +489 -0
- response_curves_model_quality_base.py +230 -0
- summary_df.pkl +2 -2
Model_Result_Overview.py
CHANGED
|
@@ -3,7 +3,7 @@ import streamlit as st
|
|
| 3 |
import pandas as pd
|
| 4 |
from sklearn.preprocessing import MinMaxScaler
|
| 5 |
import pickle
|
| 6 |
-
|
| 7 |
from utilities import load_authenticator
|
| 8 |
|
| 9 |
from utilities_with_panel import (set_header,
|
|
@@ -104,89 +104,148 @@ if auth_status:
|
|
| 104 |
is_state_initiaized = st.session_state.get('initialized',False)
|
| 105 |
if not is_state_initiaized:
|
| 106 |
a=1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
-
def panel_fetch(file_selected):
|
| 109 |
-
|
| 110 |
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
|
| 117 |
-
|
| 118 |
|
| 119 |
-
def rerun():
|
| 120 |
-
|
| 121 |
|
| 122 |
-
metrics_selected='prospects'
|
| 123 |
|
| 124 |
-
file_selected = (
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
panel_list = panel_fetch(file_selected)
|
| 128 |
|
| 129 |
-
if "selected_markets" not in st.session_state:
|
| 130 |
-
|
| 131 |
|
| 132 |
|
| 133 |
-
st.header('Overview of previous spends')
|
| 134 |
|
| 135 |
-
selected_market= st.selectbox(
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
|
| 140 |
|
| 141 |
|
| 142 |
-
initialize_data(target_col,selected_market)
|
| 143 |
-
scenario = st.session_state['scenario']
|
| 144 |
-
raw_df = st.session_state['raw_df']
|
| 145 |
# st.write(scenario.actual_total_spends)
|
| 146 |
# st.write(scenario.actual_total_sales)
|
| 147 |
-
columns = st.columns((1,1,3))
|
| 148 |
-
|
| 149 |
-
with columns[0]:
|
| 150 |
-
|
| 151 |
-
###print(f"##################### {scenario.actual_total_sales} ##################")
|
| 152 |
-
with columns[1]:
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
actual_summary_df = create_channel_summary(scenario)
|
| 157 |
-
actual_summary_df['Channel'] = actual_summary_df['Channel'].apply(channel_name_formating)
|
| 158 |
-
|
| 159 |
-
columns = st.columns((2,1))
|
| 160 |
-
#with columns[0]:
|
| 161 |
-
with st.expander('Channel wise overview'):
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
|
| 172 |
-
st.markdown("<hr>",unsafe_allow_html=True)
|
| 173 |
-
##############################
|
| 174 |
|
| 175 |
-
st.plotly_chart(create_contribution_pie(scenario),use_container_width=True)
|
| 176 |
-
st.markdown("<hr>",unsafe_allow_html=True)
|
| 177 |
|
| 178 |
|
| 179 |
-
################################3
|
| 180 |
-
st.plotly_chart(create_contribuion_stacked_plot(scenario),use_container_width=True)
|
| 181 |
-
st.markdown("<hr>",unsafe_allow_html=True)
|
| 182 |
-
#######################################
|
| 183 |
|
| 184 |
-
selected_channel_name = st.selectbox('Channel', st.session_state['channels_list'] + ['non media'], format_func=channel_name_formating)
|
| 185 |
-
selected_channel = scenario.channels.get(selected_channel_name,None)
|
| 186 |
|
| 187 |
-
st.plotly_chart(create_channel_spends_sales_plot(selected_channel), use_container_width=True)
|
| 188 |
|
| 189 |
-
st.markdown("<hr>",unsafe_allow_html=True)
|
| 190 |
|
| 191 |
# elif auth_status == False:
|
| 192 |
# st.error('Username/Password is incorrect')
|
|
@@ -201,3 +260,56 @@ if auth_status:
|
|
| 201 |
# st.error('Username not found')
|
| 202 |
# except Exception as e:
|
| 203 |
# st.error(e)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import pandas as pd
|
| 4 |
from sklearn.preprocessing import MinMaxScaler
|
| 5 |
import pickle
|
| 6 |
+
import Streamlit_functions as sf
|
| 7 |
from utilities import load_authenticator
|
| 8 |
|
| 9 |
from utilities_with_panel import (set_header,
|
|
|
|
| 104 |
is_state_initiaized = st.session_state.get('initialized',False)
|
| 105 |
if not is_state_initiaized:
|
| 106 |
a=1
|
| 107 |
+
|
| 108 |
+
# st.header("")
|
| 109 |
+
st.markdown("<h5 style='font-weight: normal;'>MMM Readout for Selected Period</h5>", unsafe_allow_html=True)
|
| 110 |
+
#### Input Select Start and End Date
|
| 111 |
+
|
| 112 |
+
# Create two columns for start date and end date input
|
| 113 |
+
col1, col2 = st.columns(2)
|
| 114 |
+
|
| 115 |
+
with col1:
|
| 116 |
+
start_date = st.date_input("Start Date: ")
|
| 117 |
+
|
| 118 |
+
with col2:
|
| 119 |
+
end_date = st.date_input("End Date: ")
|
| 120 |
+
|
| 121 |
+
# Dropdown menu options
|
| 122 |
+
options = [
|
| 123 |
+
"Month on Month",
|
| 124 |
+
"Year on Year"]
|
| 125 |
+
col1, col2 = st.columns(2)
|
| 126 |
+
# Create a dropdown menu
|
| 127 |
+
with col1:
|
| 128 |
+
selected_option = st.selectbox('Select a comparision', options)
|
| 129 |
+
with col2:
|
| 130 |
+
st.write("")
|
| 131 |
+
# Waterfall chart
|
| 132 |
+
fig = sf.waterfall(start_date,end_date,selected_option)
|
| 133 |
+
st.plotly_chart(fig,use_container_width=True)
|
| 134 |
+
|
| 135 |
+
# Waterfall table
|
| 136 |
+
shares_df = sf.shares_df_func(start_date,end_date)
|
| 137 |
+
st.table(sf.waterfall_table_func(shares_df).style.format("{:.0%}"))
|
| 138 |
+
|
| 139 |
+
## Channel Contribution Bar Chart
|
| 140 |
+
st.plotly_chart(sf.channel_contribution(start_date,end_date),use_container_width=True)
|
| 141 |
+
# Format first three rows in percentage format
|
| 142 |
+
# styled_df = sf.shares_table_func(shares_df)
|
| 143 |
+
# # styled_df = styled_df.round(0).astype(int)
|
| 144 |
+
# styled_df.iloc[:3] = (styled_df.iloc[:3]).astype(int)
|
| 145 |
+
|
| 146 |
+
# # Round next two rows to two decimal places
|
| 147 |
+
# styled_df.iloc[3:5] = styled_df.iloc[3:5].round(0).astype(str)
|
| 148 |
+
|
| 149 |
+
# st.table(styled_df)
|
| 150 |
+
st.dataframe(sf.shares_table_func(shares_df),use_container_width=True)
|
| 151 |
+
|
| 152 |
+
st.dataframe(sf.eff_table_func(shares_df),use_container_width=True)
|
| 153 |
+
|
| 154 |
+
### CPP CHART
|
| 155 |
+
st.plotly_chart(sf.cpp(start_date,end_date),use_container_width=True)
|
| 156 |
+
|
| 157 |
+
### Base decomp CHART
|
| 158 |
+
st.plotly_chart(sf.base_decomp(),use_container_width=True)
|
| 159 |
+
|
| 160 |
+
### Media decomp CHART
|
| 161 |
+
st.plotly_chart(sf.media_decomp(),use_container_width=True)
|
| 162 |
+
|
| 163 |
+
# fig = sf.pie1(start_date,end_date)
|
| 164 |
+
# st.plotly_chart(fig,use_container_width=True)
|
| 165 |
+
# # st.dataframe(fig)
|
| 166 |
|
| 167 |
+
# def panel_fetch(file_selected):
|
| 168 |
+
# raw_data_mmm_df = pd.read_excel(file_selected, sheet_name="RAW DATA MMM")
|
| 169 |
|
| 170 |
+
# if "Panel" in raw_data_mmm_df.columns:
|
| 171 |
+
# panel = list(set(raw_data_mmm_df["Panel"]))
|
| 172 |
+
# else:
|
| 173 |
+
# raw_data_mmm_df = None
|
| 174 |
+
# panel = None
|
| 175 |
|
| 176 |
+
# return panel
|
| 177 |
|
| 178 |
+
# def rerun():
|
| 179 |
+
# st.rerun()
|
| 180 |
|
| 181 |
+
# metrics_selected='prospects'
|
| 182 |
|
| 183 |
+
# file_selected = (
|
| 184 |
+
# f"Overview_data_test_panel@#{metrics_selected}.xlsx"
|
| 185 |
+
# )
|
| 186 |
+
# panel_list = panel_fetch(file_selected)
|
| 187 |
|
| 188 |
+
# if "selected_markets" not in st.session_state:
|
| 189 |
+
# st.session_state['selected_markets']='DMA1'
|
| 190 |
|
| 191 |
|
| 192 |
+
# st.header('Overview of previous spends')
|
| 193 |
|
| 194 |
+
# selected_market= st.selectbox(
|
| 195 |
+
# "Select Markets",
|
| 196 |
+
# ["Total Market"] + panel_list
|
| 197 |
+
# )
|
| 198 |
|
| 199 |
|
| 200 |
|
| 201 |
+
# initialize_data(target_col,selected_market)
|
| 202 |
+
# scenario = st.session_state['scenario']
|
| 203 |
+
# raw_df = st.session_state['raw_df']
|
| 204 |
# st.write(scenario.actual_total_spends)
|
| 205 |
# st.write(scenario.actual_total_sales)
|
| 206 |
+
# columns = st.columns((1,1,3))
|
| 207 |
+
|
| 208 |
+
# with columns[0]:
|
| 209 |
+
# st.metric(label='Spends', value=format_numbers(float(scenario.actual_total_spends)))
|
| 210 |
+
# ###print(f"##################### {scenario.actual_total_sales} ##################")
|
| 211 |
+
# with columns[1]:
|
| 212 |
+
# st.metric(label=target, value=format_numbers(float(scenario.actual_total_sales),include_indicator=False))
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
# actual_summary_df = create_channel_summary(scenario)
|
| 216 |
+
# actual_summary_df['Channel'] = actual_summary_df['Channel'].apply(channel_name_formating)
|
| 217 |
+
|
| 218 |
+
# columns = st.columns((2,1))
|
| 219 |
+
# #with columns[0]:
|
| 220 |
+
# with st.expander('Channel wise overview'):
|
| 221 |
+
# st.markdown(actual_summary_df.style.set_table_styles(
|
| 222 |
+
# [{
|
| 223 |
+
# 'selector': 'th',
|
| 224 |
+
# 'props': [('background-color', '#FFFFF')]
|
| 225 |
+
# },
|
| 226 |
+
# {
|
| 227 |
+
# 'selector' : 'tr:nth-child(even)',
|
| 228 |
+
# 'props' : [('background-color', '#FFFFF')]
|
| 229 |
+
# }]).to_html(), unsafe_allow_html=True)
|
| 230 |
|
| 231 |
+
# st.markdown("<hr>",unsafe_allow_html=True)
|
| 232 |
+
# ##############################
|
| 233 |
|
| 234 |
+
# st.plotly_chart(create_contribution_pie(scenario),use_container_width=True)
|
| 235 |
+
# st.markdown("<hr>",unsafe_allow_html=True)
|
| 236 |
|
| 237 |
|
| 238 |
+
# ################################3
|
| 239 |
+
# st.plotly_chart(create_contribuion_stacked_plot(scenario),use_container_width=True)
|
| 240 |
+
# st.markdown("<hr>",unsafe_allow_html=True)
|
| 241 |
+
# #######################################
|
| 242 |
|
| 243 |
+
# selected_channel_name = st.selectbox('Channel', st.session_state['channels_list'] + ['non media'], format_func=channel_name_formating)
|
| 244 |
+
# selected_channel = scenario.channels.get(selected_channel_name,None)
|
| 245 |
|
| 246 |
+
# st.plotly_chart(create_channel_spends_sales_plot(selected_channel), use_container_width=True)
|
| 247 |
|
| 248 |
+
# st.markdown("<hr>",unsafe_allow_html=True)
|
| 249 |
|
| 250 |
# elif auth_status == False:
|
| 251 |
# st.error('Username/Password is incorrect')
|
|
|
|
| 260 |
# st.error('Username not found')
|
| 261 |
# except Exception as e:
|
| 262 |
# st.error(e)
|
| 263 |
+
# st.header("")
|
| 264 |
+
# st.markdown("<h5 style='font-weight: normal;'>MMM Readout for Selected Period</h5>", unsafe_allow_html=True)
|
| 265 |
+
# #### Input Select Start and End Date
|
| 266 |
+
|
| 267 |
+
# # Create two columns for start date and end date input
|
| 268 |
+
# col1, col2 = st.columns(2)
|
| 269 |
+
|
| 270 |
+
# with col1:
|
| 271 |
+
# start_date = st.date_input("Start Date: ")
|
| 272 |
+
|
| 273 |
+
# with col2:
|
| 274 |
+
# end_date = st.date_input("End Date: ")
|
| 275 |
+
# # Dropdown menu options
|
| 276 |
+
# options = [
|
| 277 |
+
# "Month on Month",
|
| 278 |
+
# "Year on Year"]
|
| 279 |
+
# col1, col2 = st.columns(2)
|
| 280 |
+
# # Create a dropdown menu
|
| 281 |
+
# with col1:
|
| 282 |
+
# selected_option = st.selectbox('Select a comparision', options)
|
| 283 |
+
# with col2:
|
| 284 |
+
# st.write("")
|
| 285 |
+
# # Waterfall chart
|
| 286 |
+
# fig = sf.waterfall(start_date,end_date,selected_option)
|
| 287 |
+
# st.plotly_chart(fig)
|
| 288 |
+
|
| 289 |
+
# # Waterfall table
|
| 290 |
+
# shares_df = sf.shares_df_func(start_date,end_date)
|
| 291 |
+
# st.table(sf.waterfall_table_func(shares_df).style.format("{:.0%}"))
|
| 292 |
+
|
| 293 |
+
# ## Channel Contribution Bar Chart
|
| 294 |
+
# st.plotly_chart(sf.channel_contribution(start_date,end_date))
|
| 295 |
+
# # Format first three rows in percentage format
|
| 296 |
+
# # styled_df = sf.shares_table_func(shares_df)
|
| 297 |
+
# # # styled_df = styled_df.round(0).astype(int)
|
| 298 |
+
# # styled_df.iloc[:3] = (styled_df.iloc[:3]).astype(int)
|
| 299 |
+
|
| 300 |
+
# # # Round next two rows to two decimal places
|
| 301 |
+
# # styled_df.iloc[3:5] = styled_df.iloc[3:5].round(0).astype(str)
|
| 302 |
+
|
| 303 |
+
# # st.table(styled_df)
|
| 304 |
+
# st.dataframe(sf.shares_table_func(shares_df))
|
| 305 |
+
|
| 306 |
+
# st.dataframe(sf.eff_table_func(shares_df))
|
| 307 |
+
|
| 308 |
+
# ### CPP CHART
|
| 309 |
+
# st.plotly_chart(sf.cpp(start_date,end_date))
|
| 310 |
+
|
| 311 |
+
# ### Base decomp CHART
|
| 312 |
+
# st.plotly_chart(sf.base_decomp())
|
| 313 |
+
|
| 314 |
+
# ### Media decomp CHART
|
| 315 |
+
# st.plotly_chart(sf.media_decomp())
|
Overview_data_test_panel@#prospects.xlsx
CHANGED
|
Binary files a/Overview_data_test_panel@#prospects.xlsx and b/Overview_data_test_panel@#prospects.xlsx differ
|
|
|
Streamlit_functions.py
ADDED
|
@@ -0,0 +1,764 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
from scipy.optimize import curve_fit
|
| 5 |
+
from sklearn.preprocessing import MinMaxScaler
|
| 6 |
+
import warnings
|
| 7 |
+
import warnings
|
| 8 |
+
warnings.filterwarnings("ignore")
|
| 9 |
+
import os
|
| 10 |
+
import plotly.graph_objects as go
|
| 11 |
+
from datetime import datetime,timedelta
|
| 12 |
+
from plotly.subplots import make_subplots
|
| 13 |
+
import pandas as pd
|
| 14 |
+
import json
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# working_directory = r"C:\Users\PragyaJatav\Downloads\Deliverables\Deliverables\Response Curves 09_07_24\Response Curves Resources"
|
| 19 |
+
# os.chdir(working_directory)
|
| 20 |
+
|
| 21 |
+
## reading input data
|
| 22 |
+
df= pd.read_csv('response_curves_input_file.csv')
|
| 23 |
+
df.dropna(inplace=True)
|
| 24 |
+
df['Date'] = pd.to_datetime(df['Date'])
|
| 25 |
+
df.reset_index(inplace=True)
|
| 26 |
+
# df
|
| 27 |
+
|
| 28 |
+
spend_cols = ['tv_broadcast_spend',
|
| 29 |
+
'tv_cable_spend',
|
| 30 |
+
'stream_video_spend',
|
| 31 |
+
'olv_spend',
|
| 32 |
+
'disp_prospect_spend',
|
| 33 |
+
'disp_retarget_spend',
|
| 34 |
+
'social_prospect_spend',
|
| 35 |
+
'social_retarget_spend',
|
| 36 |
+
'search_brand_spend',
|
| 37 |
+
'search_nonbrand_spend',
|
| 38 |
+
'cm_spend',
|
| 39 |
+
'audio_spend',
|
| 40 |
+
'email_spend']
|
| 41 |
+
metric_cols = ['tv_broadcast_grp',
|
| 42 |
+
'tv_cable_grp',
|
| 43 |
+
'stream_video_imp',
|
| 44 |
+
'olv_imp',
|
| 45 |
+
'disp_prospect_imp',
|
| 46 |
+
'disp_retarget_imp',
|
| 47 |
+
'social_prospect_imp',
|
| 48 |
+
'social_retarget_imp',
|
| 49 |
+
'search_brand_imp',
|
| 50 |
+
'search_nonbrand_imp',
|
| 51 |
+
'cm_spend',
|
| 52 |
+
'audio_imp',
|
| 53 |
+
'email_imp']
|
| 54 |
+
channels = [
|
| 55 |
+
'BROADCAST TV',
|
| 56 |
+
'CABLE TV',
|
| 57 |
+
'CONNECTED & OTT TV',
|
| 58 |
+
'VIDEO',
|
| 59 |
+
'DISPLAY PROSPECTING',
|
| 60 |
+
'DISPLAY RETARGETING',
|
| 61 |
+
'SOCIAL PROSPECTING',
|
| 62 |
+
'SOCIAL RETARGETING',
|
| 63 |
+
'SEARCH BRAND',
|
| 64 |
+
'SEARCH NON-BRAND',
|
| 65 |
+
'DIGITAL PARTNERS',
|
| 66 |
+
'AUDIO',
|
| 67 |
+
'EMAIL']
|
| 68 |
+
contribution_cols = [
|
| 69 |
+
'Broadcast TV_Prospects',
|
| 70 |
+
'Cable TV_Prospects',
|
| 71 |
+
'Connected & OTT TV_Prospects',
|
| 72 |
+
'Video_Prospects',
|
| 73 |
+
'Display Prospecting_Prospects',
|
| 74 |
+
'Display Retargeting_Prospects',
|
| 75 |
+
'Social Prospecting_Prospects',
|
| 76 |
+
'Social Retargeting_Prospects',
|
| 77 |
+
'Search Brand_Prospects',
|
| 78 |
+
'Search Non-brand_Prospects',
|
| 79 |
+
'Digital Partners_Prospects',
|
| 80 |
+
'Audio_Prospects',
|
| 81 |
+
'Email_Prospects']
|
| 82 |
+
|
| 83 |
+
def pie1(start_date,end_date):
|
| 84 |
+
start_date = pd.to_datetime(start_date)
|
| 85 |
+
end_date = pd.to_datetime(end_date)
|
| 86 |
+
cur_data = df[(df['Date'] >= start_date) & (df['Date'] <= end_date)]
|
| 87 |
+
data = cur_data[spend_cols].sum().transpose()
|
| 88 |
+
data.index = channels
|
| 89 |
+
data.columns = ["p"]
|
| 90 |
+
# Create a pie chart with custom options
|
| 91 |
+
fig = go.Figure(data=[go.Pie(
|
| 92 |
+
labels=channels,
|
| 93 |
+
values=data["p"],
|
| 94 |
+
hoverinfo='label+percent',
|
| 95 |
+
# textinfo='value',
|
| 96 |
+
|
| 97 |
+
)])
|
| 98 |
+
|
| 99 |
+
# Customize the layout
|
| 100 |
+
fig.update_layout(
|
| 101 |
+
title="Distribution of Spends"
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
# Show the figure
|
| 105 |
+
return data
|
| 106 |
+
|
| 107 |
+
def waterfall(start_date,end_date,btn_chart):
|
| 108 |
+
# if pd.isnull(start_date) == True :
|
| 109 |
+
# start_date = datetime(2024, 1, 28)
|
| 110 |
+
# if pd.isnull(end_date) == True :
|
| 111 |
+
# end_date = datetime(2024, 2, 24)
|
| 112 |
+
# start_date = datetime.strptime(start_date, "%Y-%m-%d")
|
| 113 |
+
# end_date = datetime.strptime(end_date, "%Y-%m-%d")
|
| 114 |
+
# start_date = start_date.datetime.data
|
| 115 |
+
# end_date = end_date.datetime.data
|
| 116 |
+
start_date = pd.to_datetime(start_date)
|
| 117 |
+
end_date = pd.to_datetime(end_date)
|
| 118 |
+
|
| 119 |
+
if btn_chart == "Month on Month":
|
| 120 |
+
start_date_prev = start_date +timedelta(weeks=-4)
|
| 121 |
+
end_date_prev = start_date +timedelta(days=-1)
|
| 122 |
+
else:
|
| 123 |
+
start_date_prev = start_date +timedelta(weeks=-52)
|
| 124 |
+
end_date_prev = start_date_prev +timedelta(weeks=4) +timedelta(days=-1)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
prev_data = df[(df['Date'] >= start_date_prev) & (df['Date'] <= end_date_prev)]
|
| 129 |
+
cur_data = df[(df['Date'] >= start_date) & (df['Date'] <= end_date)]
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
# Example data for the waterfall chart
|
| 133 |
+
data = [
|
| 134 |
+
{'label': 'Previous Period', 'value': round(prev_data[contribution_cols].values.sum())},
|
| 135 |
+
{'label': 'Broadcast TV', 'value': round(cur_data['Broadcast TV_Prospects'].sum()-prev_data['Broadcast TV_Prospects'].sum())},
|
| 136 |
+
{'label': 'Cable TV', 'value': round(cur_data['Cable TV_Prospects'].sum()-prev_data['Cable TV_Prospects'].sum())},
|
| 137 |
+
{'label': 'Connected & OTT TV', 'value': round(cur_data['Connected & OTT TV_Prospects'].sum()-prev_data['Connected & OTT TV_Prospects'].sum())},
|
| 138 |
+
{'label': 'Video', 'value': round(cur_data['Video_Prospects'].sum()-prev_data['Video_Prospects'].sum())},
|
| 139 |
+
{'label': 'Display Prospecting', 'value': round(cur_data['Display Prospecting_Prospects'].sum()-prev_data['Display Prospecting_Prospects'].sum())},
|
| 140 |
+
{'label': 'Display Retargeting', 'value': round(cur_data['Display Retargeting_Prospects'].sum()-prev_data['Display Retargeting_Prospects'].sum())},
|
| 141 |
+
{'label': 'Social Prospecting', 'value': round(cur_data['Social Prospecting_Prospects'].sum()-prev_data['Social Prospecting_Prospects'].sum())},
|
| 142 |
+
{'label': 'Social Retargeting', 'value': round(cur_data['Social Retargeting_Prospects'].sum()-prev_data['Social Retargeting_Prospects'].sum())},
|
| 143 |
+
{'label': 'Search Brand', 'value': round(cur_data['Search Brand_Prospects'].sum()-prev_data['Search Brand_Prospects'].sum())},
|
| 144 |
+
{'label': 'Search Non-brand', 'value': round(cur_data['Search Non-brand_Prospects'].sum()-prev_data['Search Non-brand_Prospects'].sum())},
|
| 145 |
+
{'label': 'Digital Partners', 'value': round(cur_data['Digital Partners_Prospects'].sum()-prev_data['Digital Partners_Prospects'].sum())},
|
| 146 |
+
{'label': 'Audio', 'value': round(cur_data['Audio_Prospects'].sum()-prev_data['Audio_Prospects'].sum())},
|
| 147 |
+
{'label': 'Email', 'value': round(cur_data['Email_Prospects'].sum()-prev_data['Email_Prospects'].sum())},
|
| 148 |
+
{'label': 'Current Period', 'value': round(cur_data[contribution_cols].values.sum())}
|
| 149 |
+
]
|
| 150 |
+
|
| 151 |
+
# Calculate cumulative values for the waterfall chart
|
| 152 |
+
cumulative = [0]
|
| 153 |
+
for i in range(len(data)):
|
| 154 |
+
cumulative.append(cumulative[-1] + data[i]['value'])
|
| 155 |
+
|
| 156 |
+
# Adjusting values to start from zero for both first and last columns
|
| 157 |
+
cumulative[-1] = 0 # Set the last cumulative value to zero
|
| 158 |
+
|
| 159 |
+
# Extracting labels and values
|
| 160 |
+
labels = [item['label'] for item in data]
|
| 161 |
+
values = [item['value'] for item in data]
|
| 162 |
+
|
| 163 |
+
# Plotting the waterfall chart using go.Bar
|
| 164 |
+
bars = []
|
| 165 |
+
for i in range(len(data)):
|
| 166 |
+
color = '#4A88D9' if i == 0 or i == len(data) - 1 else '#DC5537' # Blue for first and last, gray for others
|
| 167 |
+
hover_text = f"<b>{labels[i]}</b><br>Value: {abs(values[i])}"
|
| 168 |
+
|
| 169 |
+
bars.append(go.Bar(
|
| 170 |
+
x=[labels[i]],
|
| 171 |
+
y=[cumulative[i+1] - cumulative[i]],
|
| 172 |
+
base=[cumulative[i]],
|
| 173 |
+
text=[f"{abs(values[i])}"],
|
| 174 |
+
textposition='outside',
|
| 175 |
+
hovertemplate=hover_text,
|
| 176 |
+
marker=dict(color=color),
|
| 177 |
+
showlegend=False
|
| 178 |
+
))
|
| 179 |
+
|
| 180 |
+
# Creating the figure
|
| 181 |
+
fig = go.Figure(data=bars)
|
| 182 |
+
|
| 183 |
+
# Updating layout for black background and gray gridlines
|
| 184 |
+
if btn_chart == "Month on Month":
|
| 185 |
+
fig.update_layout(
|
| 186 |
+
title=f"Change in MMM Estimated Prospect Contribution <br>{start_date_prev.strftime('%Y-%m-%d')} to {end_date_prev.strftime('%Y-%m-%d')} vs. {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}"
|
| 187 |
+
,showlegend=False,
|
| 188 |
+
# plot_bgcolor='black',
|
| 189 |
+
# paper_bgcolor='black',
|
| 190 |
+
# font=dict(color='white'), # Changing font color to white for better contrast
|
| 191 |
+
xaxis=dict(
|
| 192 |
+
showgrid=False,
|
| 193 |
+
zeroline=False, # Hiding the x-axis zero line
|
| 194 |
+
),
|
| 195 |
+
yaxis=dict(
|
| 196 |
+
title="Prospects",
|
| 197 |
+
showgrid=True,
|
| 198 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 199 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 200 |
+
range=[18000, max(cumulative)+1000] # Setting the y-axis range from 19k to slightly above the maximum value
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
)
|
| 204 |
+
else :
|
| 205 |
+
fig.update_layout(
|
| 206 |
+
title=f"Change in MMM Estimated Prospect Contribution <br>{start_date_prev.strftime('%Y-%m-%d')} to {end_date_prev.strftime('%Y-%m-%d')} vs. {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}"
|
| 207 |
+
,showlegend=False,
|
| 208 |
+
# plot_bgcolor='black',
|
| 209 |
+
# paper_bgcolor='black',
|
| 210 |
+
# font=dict(color='white'), # Changing font color to white for better contrast
|
| 211 |
+
xaxis=dict(
|
| 212 |
+
showgrid=False,
|
| 213 |
+
zeroline=False, # Hiding the x-axis zero line
|
| 214 |
+
),
|
| 215 |
+
yaxis=dict(
|
| 216 |
+
title="Prospects",
|
| 217 |
+
showgrid=True,
|
| 218 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 219 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 220 |
+
range=[10000, max(cumulative)+1000] # Setting the y-axis range from 19k to slightly above the maximum value
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
)
|
| 224 |
+
# print(cur_data)
|
| 225 |
+
# print(prev_data)
|
| 226 |
+
# fig.show()
|
| 227 |
+
return fig
|
| 228 |
+
|
| 229 |
+
def shares_df_func(start_date,end_date):
|
| 230 |
+
# if pd.isnull(start_date) == True :
|
| 231 |
+
# start_date = datetime(2024, 1, 28)
|
| 232 |
+
# if pd.isnull(end_date) == True :
|
| 233 |
+
# end_date = datetime(2024, 2, 24)
|
| 234 |
+
|
| 235 |
+
start_date = pd.to_datetime(start_date)
|
| 236 |
+
end_date = pd.to_datetime(end_date)
|
| 237 |
+
|
| 238 |
+
start_date_prev = start_date +timedelta(weeks=-4)
|
| 239 |
+
end_date_prev = start_date +timedelta(days=-1)
|
| 240 |
+
|
| 241 |
+
prev_data = df[(df['Date'] >= start_date_prev) & (df['Date'] <= end_date_prev)]
|
| 242 |
+
cur_data = df[(df['Date'] >= start_date) & (df['Date'] <= end_date)]
|
| 243 |
+
cur_df1 = pd.DataFrame(cur_data[spend_cols].sum()).reset_index()
|
| 244 |
+
cur_df2 = pd.DataFrame(cur_data[metric_cols].sum()).reset_index()
|
| 245 |
+
cur_df3 = pd.DataFrame(cur_data[contribution_cols].sum()).reset_index()
|
| 246 |
+
|
| 247 |
+
cur_df1.columns = ["channels","cur_total_spend"]
|
| 248 |
+
cur_df2.columns = ["channels","cur_total_support"]
|
| 249 |
+
cur_df3.columns = ["channels","cur_total_contributions"]
|
| 250 |
+
cur_df1["channels"] = channels
|
| 251 |
+
cur_df2["channels"] = channels
|
| 252 |
+
cur_df3["channels"] = channels
|
| 253 |
+
|
| 254 |
+
cur_df1["cur_spend_share"] = (cur_df1["cur_total_spend"]/cur_df1["cur_total_spend"].sum())*100
|
| 255 |
+
cur_df2["cur_support_share"] = (cur_df2["cur_total_support"]/cur_df2["cur_total_support"].sum())*100
|
| 256 |
+
cur_df3["cur_contributions_share"] = (cur_df3["cur_total_contributions"]/cur_df3["cur_total_contributions"].sum())*100
|
| 257 |
+
|
| 258 |
+
prev_df1 = pd.DataFrame(prev_data[spend_cols].sum()).reset_index()
|
| 259 |
+
prev_df2 = pd.DataFrame(prev_data[metric_cols].sum()).reset_index()
|
| 260 |
+
prev_df3 = pd.DataFrame(prev_data[contribution_cols].sum()).reset_index()
|
| 261 |
+
|
| 262 |
+
prev_df1.columns = ["channels","prev_total_spend"]
|
| 263 |
+
prev_df2.columns = ["channels","prev_total_support"]
|
| 264 |
+
prev_df3.columns = ["channels","prev_total_contributions"]
|
| 265 |
+
|
| 266 |
+
prev_df1["channels"] = channels
|
| 267 |
+
prev_df2["channels"] = channels
|
| 268 |
+
prev_df3["channels"] = channels
|
| 269 |
+
|
| 270 |
+
prev_df1["prev_spend_share"] = (prev_df1["prev_total_spend"]/prev_df1["prev_total_spend"].sum())*100
|
| 271 |
+
prev_df2["prev_support_share"] = (prev_df2["prev_total_support"]/prev_df2["prev_total_support"].sum())*100
|
| 272 |
+
prev_df3["prev_contributions_share"] = (prev_df3["prev_total_contributions"]/prev_df3["prev_total_contributions"].sum())*100
|
| 273 |
+
|
| 274 |
+
cur_df = cur_df1.merge(cur_df2,on="channels",how = "inner")
|
| 275 |
+
cur_df = cur_df.merge(cur_df3,on="channels",how = "inner")
|
| 276 |
+
|
| 277 |
+
prev_df = prev_df1.merge(prev_df2,on="channels",how = "inner")
|
| 278 |
+
prev_df = prev_df.merge(prev_df3,on="channels",how = "inner")
|
| 279 |
+
|
| 280 |
+
shares_df = cur_df.merge(prev_df,on = "channels",how = "inner")
|
| 281 |
+
shares_df["Contribution Change"] = (-shares_df["prev_contributions_share"]+shares_df["cur_contributions_share"])/shares_df["prev_contributions_share"]
|
| 282 |
+
shares_df["Support Change"] = (-shares_df["prev_support_share"]+shares_df["cur_support_share"])/shares_df["prev_support_share"]
|
| 283 |
+
shares_df["Spend Change"] = (-shares_df["prev_spend_share"]+shares_df["cur_spend_share"])/shares_df["prev_spend_share"]
|
| 284 |
+
shares_df["Efficiency Index"] = shares_df["cur_contributions_share"]/shares_df["cur_spend_share"]
|
| 285 |
+
shares_df["Effectiveness Index"] = shares_df["cur_support_share"]/shares_df["cur_spend_share"]
|
| 286 |
+
return shares_df
|
| 287 |
+
|
| 288 |
+
def waterfall_table_func(shares_df):
|
| 289 |
+
### waterfall delta table
|
| 290 |
+
# if pd.isnull(start_date) == True :
|
| 291 |
+
# start_date = datetime(2024, 1, 28)
|
| 292 |
+
# if pd.isnull(end_date) == True :
|
| 293 |
+
# end_date = datetime(2024, 2, 24)
|
| 294 |
+
|
| 295 |
+
waterfall_delta_df = shares_df[["channels","Contribution Change","Support Change","Spend Change"]]
|
| 296 |
+
waterfall_delta_df = waterfall_delta_df.rename(columns = {"channels":"METRIC"})
|
| 297 |
+
waterfall_delta_df.index = waterfall_delta_df["METRIC"]
|
| 298 |
+
waterfall_delta_df = waterfall_delta_df.round(2)
|
| 299 |
+
return (waterfall_delta_df[["Contribution Change","Support Change","Spend Change"]].transpose())
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def channel_contribution(start_date,end_date):
|
| 303 |
+
|
| 304 |
+
# if pd.isnull(start_date) == True :
|
| 305 |
+
# start_date = datetime(2024, 1, 28)
|
| 306 |
+
# if pd.isnull(end_date) == True :
|
| 307 |
+
# end_date = datetime(2024, 2, 24)
|
| 308 |
+
|
| 309 |
+
start_date = pd.to_datetime(start_date)
|
| 310 |
+
end_date = pd.to_datetime(end_date)
|
| 311 |
+
|
| 312 |
+
cur_data = df[(df['Date'] >= start_date) & (df['Date'] <= end_date)]
|
| 313 |
+
|
| 314 |
+
channel_df = pd.DataFrame(cur_data[contribution_cols].sum()).reset_index()
|
| 315 |
+
channel_df.columns = ["channels","contributions"]
|
| 316 |
+
channel_df["channels"] = channels
|
| 317 |
+
|
| 318 |
+
# Creating the bar chart
|
| 319 |
+
fig = go.Figure(data=[go.Bar(
|
| 320 |
+
x=channel_df['channels'],
|
| 321 |
+
y=round(channel_df['contributions']),
|
| 322 |
+
marker=dict(color='rgb(74, 136, 217)'), # Blue color for all bars
|
| 323 |
+
text=round(channel_df['contributions']),
|
| 324 |
+
textposition='outside'
|
| 325 |
+
)])
|
| 326 |
+
|
| 327 |
+
# Updating layout for better visualization
|
| 328 |
+
fig.update_layout(
|
| 329 |
+
title=f"Media Contribution <br> {cur_data['Date'].min().strftime('%Y-%m-%d')} to {cur_data['Date'].max().strftime('%Y-%m-%d')}",
|
| 330 |
+
# plot_bgcolor='black',
|
| 331 |
+
# paper_bgcolor='black',
|
| 332 |
+
# font=dict(color='white'), # Changing font color to white for better contrast
|
| 333 |
+
xaxis=dict(
|
| 334 |
+
showgrid=False,
|
| 335 |
+
gridcolor='gray', # Setting x-axis gridline color to gray
|
| 336 |
+
zeroline=False, # Hiding the x-axis zero line
|
| 337 |
+
),
|
| 338 |
+
yaxis=dict(
|
| 339 |
+
title="Prospect",
|
| 340 |
+
showgrid=True,
|
| 341 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 342 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 343 |
+
)
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
return fig
|
| 347 |
+
def shares_table_func(shares_df):
|
| 348 |
+
|
| 349 |
+
# if pd.isnull(start_date) == True :
|
| 350 |
+
# start_date = datetime(2024, 1, 28)
|
| 351 |
+
# if pd.isnull(end_date) == True :
|
| 352 |
+
# end_date = datetime(2024, 2, 24)
|
| 353 |
+
|
| 354 |
+
### Shares tables
|
| 355 |
+
shares_table_df = shares_df[["channels","cur_spend_share","cur_support_share","cur_contributions_share","Efficiency Index","Effectiveness Index"]]
|
| 356 |
+
shares_table_df = shares_table_df.rename(columns = {"channels":"METRIC",
|
| 357 |
+
"cur_spend_share":"Spend Share",
|
| 358 |
+
"cur_support_share":"Support Share",
|
| 359 |
+
"cur_contributions_share":"Contribution Share"})
|
| 360 |
+
shares_table_df.index = shares_table_df["METRIC"]
|
| 361 |
+
for c in ["Spend Share","Support Share","Contribution Share"]:
|
| 362 |
+
shares_table_df[c] = shares_table_df[c].astype(int)
|
| 363 |
+
shares_table_df[c] = shares_table_df[c].astype(str)+'%'
|
| 364 |
+
for c in ["Efficiency Index","Effectiveness Index"]:
|
| 365 |
+
shares_table_df[c] = shares_table_df[c].round(2).astype(str)
|
| 366 |
+
shares_table_df = shares_table_df[["Spend Share","Support Share","Contribution Share","Efficiency Index","Effectiveness Index"]].transpose()
|
| 367 |
+
return (shares_table_df)
|
| 368 |
+
|
| 369 |
+
def eff_table_func(shares_df):
|
| 370 |
+
|
| 371 |
+
# if pd.isnull(start_date) == True :
|
| 372 |
+
# start_date = datetime(2024, 1, 28)
|
| 373 |
+
# if pd.isnull(end_date) == True :
|
| 374 |
+
# end_date = datetime(2024, 2, 24)
|
| 375 |
+
|
| 376 |
+
media_df = shares_df[['channels', 'cur_total_spend',"cur_total_support", "cur_total_contributions" ,'cur_spend_share',
|
| 377 |
+
'cur_support_share', 'cur_contributions_share', 'Efficiency Index', 'Effectiveness Index']]
|
| 378 |
+
media_df = media_df.rename(columns = {"channels":"MEDIA",
|
| 379 |
+
"cur_total_spend":"TOTAL SPEND",
|
| 380 |
+
"cur_total_support":"TOTAL SUPPORT",
|
| 381 |
+
"cur_total_contributions":"TOTAL CONTRIBUTION",
|
| 382 |
+
|
| 383 |
+
"cur_spend_share":"SPEND SHARE",
|
| 384 |
+
"cur_support_share":"SUPPORT SHARE",
|
| 385 |
+
"cur_contributions_share":"CONTRIBUTION SHARE",
|
| 386 |
+
'Efficiency Index':'EFFICIENCY INDEX',
|
| 387 |
+
'Effectiveness Index' :'EFFECTIVENESS INDEX'
|
| 388 |
+
})
|
| 389 |
+
|
| 390 |
+
media_df.index = media_df["MEDIA"]
|
| 391 |
+
media_df.drop(columns = ["MEDIA"],inplace = True)
|
| 392 |
+
for c in ["TOTAL SPEND","TOTAL SUPPORT","TOTAL CONTRIBUTION"]:
|
| 393 |
+
media_df[c] = media_df[c].astype(int).astype(str)
|
| 394 |
+
for c in ["SPEND SHARE","SUPPORT SHARE","CONTRIBUTION SHARE"]:
|
| 395 |
+
media_df[c] = media_df[c].astype(int)
|
| 396 |
+
media_df[c] = media_df[c].astype(str)+'%'
|
| 397 |
+
for c in ['EFFICIENCY INDEX','EFFECTIVENESS INDEX']:
|
| 398 |
+
media_df[c] = media_df[c].round(2).astype(str)
|
| 399 |
+
return (media_df)
|
| 400 |
+
|
| 401 |
+
def cpp(start_date,end_date):
|
| 402 |
+
# if pd.isnull(start_date) == True :
|
| 403 |
+
# start_date = datetime(2024, 1, 28)
|
| 404 |
+
# if pd.isnull(end_date) == True :
|
| 405 |
+
# end_date = datetime(2024, 2, 24)
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
start_date = pd.to_datetime(start_date)
|
| 409 |
+
end_date = pd.to_datetime(end_date)
|
| 410 |
+
|
| 411 |
+
cur_data = df[(df['Date'] >= start_date) & (df['Date'] <= end_date)]
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
fig = go.Figure()
|
| 415 |
+
colors = [
|
| 416 |
+
'rgba(74, 136, 217, 0.8)', # Blue
|
| 417 |
+
'rgba(220, 85, 55, 0.8)', # Red
|
| 418 |
+
'rgba(67, 150, 80, 0.8)', # Green
|
| 419 |
+
'rgba(237, 151, 35, 0.8)', # Orange
|
| 420 |
+
'rgba(145, 68, 255, 0.8)', # Purple
|
| 421 |
+
'rgba(128, 128, 128, 0.8)', # Gray
|
| 422 |
+
'rgba(255, 165, 0, 0.8)', # Amber
|
| 423 |
+
'rgba(255, 192, 203, 0.8)', # Pink
|
| 424 |
+
'rgba(0, 191, 255, 0.8)', # Deep Sky Blue
|
| 425 |
+
'rgba(127, 255, 0, 0.8)', # Chartreuse
|
| 426 |
+
'rgba(255, 69, 0, 0.8)', # Red-Orange
|
| 427 |
+
'rgba(75, 0, 130, 0.8)', # Indigo
|
| 428 |
+
'rgba(240, 230, 140, 0.8)', # Khaki
|
| 429 |
+
'rgba(218, 112, 214, 0.8)'
|
| 430 |
+
]
|
| 431 |
+
|
| 432 |
+
for i in range(0,13):
|
| 433 |
+
cpp_df = cur_data[['Date',spend_cols[i],contribution_cols[i]]]
|
| 434 |
+
cpp_df[channels[i]+"_cpp"] = cpp_df[spend_cols[i]]/cpp_df[contribution_cols[i]]
|
| 435 |
+
# Add each line trace
|
| 436 |
+
fig.add_trace(go.Scatter(x=cpp_df['Date'], y=cpp_df[channels[i]+"_cpp"], mode='lines', name=channels[i]))
|
| 437 |
+
|
| 438 |
+
# Update layout for better visualization
|
| 439 |
+
fig.update_layout(
|
| 440 |
+
title=f"CPP distribution <br>{cur_data['Date'].min().strftime('%Y-%m-%d')} to {cur_data['Date'].max().strftime('%Y-%m-%d')}"
|
| 441 |
+
,
|
| 442 |
+
# plot_bgcolor='black',
|
| 443 |
+
# paper_bgcolor='black',
|
| 444 |
+
# font=dict(color='white'), # Changing font color to white for better contrast
|
| 445 |
+
xaxis=dict(
|
| 446 |
+
showgrid=True,
|
| 447 |
+
gridcolor='gray', # Setting x-axis gridline color to gray
|
| 448 |
+
zeroline=False, # Hiding the x-axis zero line
|
| 449 |
+
),
|
| 450 |
+
yaxis=dict(
|
| 451 |
+
title="CPP",
|
| 452 |
+
showgrid=True,
|
| 453 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 454 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 455 |
+
),
|
| 456 |
+
hovermode='x' # Show hover info for all lines at a single point
|
| 457 |
+
)
|
| 458 |
+
return fig
|
| 459 |
+
|
| 460 |
+
def base_decomp():
|
| 461 |
+
|
| 462 |
+
# if pd.isnull(start_date) == True :
|
| 463 |
+
# start_date = datetime(2024, 1, 28)
|
| 464 |
+
# if pd.isnull(end_date) == True :
|
| 465 |
+
# end_date = datetime(2024, 2, 24)
|
| 466 |
+
|
| 467 |
+
base_decomp_df = df[['Date','Unemployment', 'Competition','Trend','Seasonality','Base_0']]
|
| 468 |
+
fig = go.Figure()
|
| 469 |
+
|
| 470 |
+
# Add each line trace
|
| 471 |
+
fig.add_trace(go.Scatter(x=base_decomp_df['Date'], y=base_decomp_df['Base_0'], mode='lines', name='Trend and Seasonality'))
|
| 472 |
+
fig.add_trace(go.Scatter(x=base_decomp_df['Date'], y=base_decomp_df['Unemployment'], mode='lines', name='Unemployment'))
|
| 473 |
+
fig.add_trace(go.Scatter(x=base_decomp_df['Date'], y=base_decomp_df['Competition'], mode='lines', name='Competition'))
|
| 474 |
+
|
| 475 |
+
# Update layout for better visualization
|
| 476 |
+
fig.update_layout(
|
| 477 |
+
title=f"Base decomposition"
|
| 478 |
+
# <br>{cur_data['Date'].min().strftime('%Y-%m-%d')} to {cur_data['Date'].max().strftime('%Y-%m-%d')}"
|
| 479 |
+
,
|
| 480 |
+
# plot_bgcolor='black',
|
| 481 |
+
# paper_bgcolor='black',
|
| 482 |
+
# font=dict(color='white'), # Changing font color to white for better contrast
|
| 483 |
+
xaxis=dict(
|
| 484 |
+
showgrid=False,
|
| 485 |
+
gridcolor='gray', # Setting x-axis gridline color to gray
|
| 486 |
+
zeroline=True, # Hiding the x-axis zero line
|
| 487 |
+
),
|
| 488 |
+
yaxis=dict(
|
| 489 |
+
title="Prospect",
|
| 490 |
+
showgrid=True,
|
| 491 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 492 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 493 |
+
),
|
| 494 |
+
hovermode='x' # Show hover info for all lines at a single point
|
| 495 |
+
)
|
| 496 |
+
|
| 497 |
+
return fig
|
| 498 |
+
|
| 499 |
+
def media_decomp():
|
| 500 |
+
# if pd.isnull(start_date) == True :
|
| 501 |
+
# start_date = datetime(2024, 1, 28)
|
| 502 |
+
# if pd.isnull(end_date) == True :
|
| 503 |
+
# end_date = datetime(2024, 2, 24)
|
| 504 |
+
|
| 505 |
+
df['base'] = df[ 'Base_0']+df['Unemployment']+df['Competition']
|
| 506 |
+
cols = ['Date',
|
| 507 |
+
'base',
|
| 508 |
+
'Broadcast TV_Prospects',
|
| 509 |
+
'Cable TV_Prospects',
|
| 510 |
+
'Connected & OTT TV_Prospects',
|
| 511 |
+
'Video_Prospects',
|
| 512 |
+
'Display Prospecting_Prospects',
|
| 513 |
+
'Display Retargeting_Prospects',
|
| 514 |
+
'Social Prospecting_Prospects',
|
| 515 |
+
'Social Retargeting_Prospects',
|
| 516 |
+
'Search Brand_Prospects',
|
| 517 |
+
'Search Non-brand_Prospects',
|
| 518 |
+
'Digital Partners_Prospects',
|
| 519 |
+
'Audio_Prospects',
|
| 520 |
+
'Email_Prospects',
|
| 521 |
+
]
|
| 522 |
+
media_decomp_df = df[cols]
|
| 523 |
+
|
| 524 |
+
# Calculating the cumulative sum for stacking
|
| 525 |
+
cumulative_df = media_decomp_df.copy()
|
| 526 |
+
# for channel in media_decomp_df.columns[1:]:
|
| 527 |
+
# cumulative_df[channel] = cumulative_df[channel] + cumulative_df[channel].shift(1, fill_value=0)
|
| 528 |
+
|
| 529 |
+
media_cols = media_decomp_df.columns
|
| 530 |
+
for i in range(2,len(media_cols)):
|
| 531 |
+
# print(media_cols[i])
|
| 532 |
+
cumulative_df[media_cols[i]] = cumulative_df[media_cols[i]] + cumulative_df[media_cols[i-1]]
|
| 533 |
+
# cumulative_df
|
| 534 |
+
|
| 535 |
+
# Creating the stacked area chart
|
| 536 |
+
fig = go.Figure()
|
| 537 |
+
|
| 538 |
+
colors =colors = [
|
| 539 |
+
'rgba(74, 136, 217, 0.8)', # Blue
|
| 540 |
+
'rgba(220, 85, 55, 0.8)', # Red
|
| 541 |
+
'rgba(67, 150, 80, 0.8)', # Green
|
| 542 |
+
'rgba(237, 151, 35, 0.8)', # Orange
|
| 543 |
+
'rgba(145, 68, 255, 0.8)', # Purple
|
| 544 |
+
'rgba(128, 128, 128, 0.8)', # Gray
|
| 545 |
+
'rgba(255, 165, 0, 0.8)', # Amber
|
| 546 |
+
'rgba(255, 192, 203, 0.8)', # Pink
|
| 547 |
+
'rgba(0, 191, 255, 0.8)', # Deep Sky Blue
|
| 548 |
+
'rgba(127, 255, 0, 0.8)', # Chartreuse
|
| 549 |
+
'rgba(255, 69, 0, 0.8)', # Red-Orange
|
| 550 |
+
'rgba(75, 0, 130, 0.8)', # Indigo
|
| 551 |
+
'rgba(240, 230, 140, 0.8)', # Khaki
|
| 552 |
+
'rgba(218, 112, 214, 0.8)'
|
| 553 |
+
]
|
| 554 |
+
|
| 555 |
+
for idx, channel in enumerate(media_decomp_df.columns[1:]):
|
| 556 |
+
fig.add_trace(go.Scatter(
|
| 557 |
+
x=media_decomp_df['Date'],
|
| 558 |
+
y=cumulative_df[channel],
|
| 559 |
+
fill='tonexty' if idx > 0 else 'tozeroy', # Fill to the previous curve
|
| 560 |
+
mode='none',
|
| 561 |
+
name=str.split(channel,'_')[0],
|
| 562 |
+
text=media_decomp_df[channel], # Adding text for each point
|
| 563 |
+
hoverinfo='x+y+text',
|
| 564 |
+
fillcolor=colors[idx] # Different color for each channel
|
| 565 |
+
))
|
| 566 |
+
|
| 567 |
+
# Updating layout for better visualization
|
| 568 |
+
fig.update_layout(
|
| 569 |
+
title=f"Media decomposition",# <br>{cur_data['Date'].min().strftime('%Y-%m-%d')} to {cur_data['Date'].max().strftime('%Y-%m-%d')}",
|
| 570 |
+
# plot_bgcolor='black',
|
| 571 |
+
# paper_bgcolor='black',
|
| 572 |
+
# font=dict(color='white'), # Changing font color to white for better contrast
|
| 573 |
+
xaxis=dict(
|
| 574 |
+
showgrid=False,
|
| 575 |
+
gridcolor='gray', # Setting x-axis gridline color to gray
|
| 576 |
+
zeroline=False, # Hiding the x-axis zero line
|
| 577 |
+
),
|
| 578 |
+
yaxis=dict(
|
| 579 |
+
title="Prospect",
|
| 580 |
+
showgrid=True,
|
| 581 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 582 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 583 |
+
)
|
| 584 |
+
)
|
| 585 |
+
|
| 586 |
+
return fig
|
| 587 |
+
|
| 588 |
+
def mmm_model_quality():
|
| 589 |
+
base_df = df[['Date',"Y_hat","Y"]]
|
| 590 |
+
fig = go.Figure()
|
| 591 |
+
|
| 592 |
+
# Add each line trace
|
| 593 |
+
fig.add_trace(go.Scatter(x=base_df['Date'], y=base_df['Y_hat'], mode='lines', name='Predicted'))
|
| 594 |
+
fig.add_trace(go.Scatter(x=base_df['Date'], y=base_df['Y'], mode='lines', name='Actual (Prospect)'))
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
# Update layout for better visualization
|
| 598 |
+
fig.update_layout(
|
| 599 |
+
title=f"MMM Model Quality"
|
| 600 |
+
,
|
| 601 |
+
# plot_bgcolor='black',
|
| 602 |
+
# paper_bgcolor='black',
|
| 603 |
+
# font=dict(color='white'), # Changing font color to white for better contrast
|
| 604 |
+
xaxis=dict(
|
| 605 |
+
showgrid=False,
|
| 606 |
+
gridcolor='gray', # Setting x-axis gridline color to gray
|
| 607 |
+
zeroline=False, # Hiding the x-axis zero line
|
| 608 |
+
),
|
| 609 |
+
yaxis=dict(
|
| 610 |
+
title="Prospects",
|
| 611 |
+
showgrid=True,
|
| 612 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 613 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 614 |
+
),
|
| 615 |
+
hovermode='x' # Show hover info for all lines at a single point
|
| 616 |
+
)
|
| 617 |
+
|
| 618 |
+
return(fig)
|
| 619 |
+
|
| 620 |
+
def media_data():
|
| 621 |
+
# Path to your JSON file
|
| 622 |
+
json_file_path = "all_solutions_2024-05-09.json"
|
| 623 |
+
# Read the JSON file
|
| 624 |
+
with open(json_file_path, 'r') as file:
|
| 625 |
+
json_data = json.load(file)
|
| 626 |
+
|
| 627 |
+
# Initialize a list to store the extracted data
|
| 628 |
+
extracted_data = []
|
| 629 |
+
|
| 630 |
+
# Extract half_life and coeff from media_params
|
| 631 |
+
for params_type in ["control_params","other_params","media_params"]:
|
| 632 |
+
for media, params in json_data['solution_0']['solution'][params_type].items():
|
| 633 |
+
try:
|
| 634 |
+
extracted_data.append({
|
| 635 |
+
'category': media,# str.split(params_type,'_')[0],
|
| 636 |
+
'half_life': params['half_life'],
|
| 637 |
+
'coeff': params['coeff']
|
| 638 |
+
})
|
| 639 |
+
except:
|
| 640 |
+
extracted_data.append({
|
| 641 |
+
'category':media,# str.split(params_type,'_')[0],
|
| 642 |
+
'half_life': None,
|
| 643 |
+
'coeff': params['coeff']
|
| 644 |
+
})
|
| 645 |
+
|
| 646 |
+
media_df = pd.DataFrame(extracted_data)
|
| 647 |
+
return media_df
|
| 648 |
+
|
| 649 |
+
|
| 650 |
+
def elasticity(media_df):
|
| 651 |
+
fig = go.Figure()
|
| 652 |
+
# media_df = media_df[["category","coeff"]]
|
| 653 |
+
fig.add_trace(go.Bar(
|
| 654 |
+
|
| 655 |
+
x=media_df['coeff'],
|
| 656 |
+
y=media_df['category'],
|
| 657 |
+
orientation='h', # Setting the orientation to horizontal
|
| 658 |
+
marker_color='rgba(75, 136, 257, 1)' # Color for the bars
|
| 659 |
+
))
|
| 660 |
+
|
| 661 |
+
# Updating layout for better visualization
|
| 662 |
+
fig.update_layout(
|
| 663 |
+
title="Media and Baseline Elasticity",
|
| 664 |
+
xaxis=dict(
|
| 665 |
+
title="Elasticity (coefficient)",
|
| 666 |
+
showgrid=True,
|
| 667 |
+
gridcolor='gray', # Setting x-axis gridline color to gray
|
| 668 |
+
zeroline=False, # Hiding the x-axis zero line
|
| 669 |
+
),
|
| 670 |
+
yaxis=dict(
|
| 671 |
+
|
| 672 |
+
showgrid=False,
|
| 673 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 674 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 675 |
+
),
|
| 676 |
+
# plot_bgcolor='black',
|
| 677 |
+
# paper_bgcolor='black',
|
| 678 |
+
# font=dict(color='lightgray') # Changing font color to white for better contrast
|
| 679 |
+
)
|
| 680 |
+
return fig
|
| 681 |
+
|
| 682 |
+
|
| 683 |
+
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def half_life(media_df):
|
| 688 |
+
fig = go.Figure()
|
| 689 |
+
# media_df = media_df[["category","coeff"]]
|
| 690 |
+
fig.add_trace(go.Bar(
|
| 691 |
+
|
| 692 |
+
x=media_df[media_df['half_life'].isnull()==False]['half_life'],
|
| 693 |
+
y=media_df[media_df['half_life'].isnull()==False]['category'],
|
| 694 |
+
orientation='h', # Setting the orientation to horizontal
|
| 695 |
+
marker_color='rgba(75, 136, 257, 1)' # Color for the bars
|
| 696 |
+
))
|
| 697 |
+
|
| 698 |
+
# Updating layout for better visualization
|
| 699 |
+
fig.update_layout(
|
| 700 |
+
title="Media Half-life",
|
| 701 |
+
xaxis=dict(
|
| 702 |
+
title="Weeks",
|
| 703 |
+
showgrid=True,
|
| 704 |
+
gridcolor='gray', # Setting x-axis gridline color to gray
|
| 705 |
+
zeroline=False, # Hiding the x-axis zero line
|
| 706 |
+
),
|
| 707 |
+
yaxis=dict(
|
| 708 |
+
|
| 709 |
+
showgrid=False,
|
| 710 |
+
gridcolor='gray', # Setting y-axis gridline color to gray
|
| 711 |
+
zeroline=False, # Hiding the y-axis zero line
|
| 712 |
+
),
|
| 713 |
+
# plot_bgcolor='black',
|
| 714 |
+
# paper_bgcolor='black',
|
| 715 |
+
# font=dict(color='lightgray') # Changing font color to white for better contrast
|
| 716 |
+
)
|
| 717 |
+
return fig
|
| 718 |
+
|
| 719 |
+
|
| 720 |
+
# media metrics table
|
| 721 |
+
n = 104
|
| 722 |
+
k = 18
|
| 723 |
+
|
| 724 |
+
def calculate_aic(y, y_hat):
|
| 725 |
+
n = len(y)
|
| 726 |
+
sse = np.sum((y - y_hat) ** 2)
|
| 727 |
+
aic = n * np.log(sse / n) + 2 * k
|
| 728 |
+
return aic
|
| 729 |
+
|
| 730 |
+
def calculate_bic(y, y_hat):
|
| 731 |
+
n = len(y)
|
| 732 |
+
sse = np.sum((y - y_hat) ** 2)
|
| 733 |
+
bic = n * np.log(sse / n) + k * np.log(n)
|
| 734 |
+
return bic
|
| 735 |
+
def calculate_r_squared(y, y_hat):
|
| 736 |
+
ss_total = np.sum((y - np.mean(y)) ** 2)
|
| 737 |
+
ss_residual = np.sum((y - y_hat) ** 2)
|
| 738 |
+
r_squared = 1 - (ss_residual / ss_total)
|
| 739 |
+
return r_squared
|
| 740 |
+
|
| 741 |
+
# Function to calculate Adjusted R-squared
|
| 742 |
+
def calculate_adjusted_r_squared(y, y_hat):
|
| 743 |
+
n = len(y)
|
| 744 |
+
r_squared = calculate_r_squared(y, y_hat)
|
| 745 |
+
adjusted_r_squared = 1 - ((1 - r_squared) * (n - 1) / (n - k - 1))
|
| 746 |
+
return adjusted_r_squared
|
| 747 |
+
|
| 748 |
+
# Function to calculate MAPE
|
| 749 |
+
def calculate_mape(y, y_hat):
|
| 750 |
+
mape = np.mean(np.abs((y - y_hat) / y)) * 100
|
| 751 |
+
return mape
|
| 752 |
+
|
| 753 |
+
def model_metrics_table_func():
|
| 754 |
+
model_metrics_df = pd.DataFrame([calculate_r_squared(df["Y"], df["Y_hat"]),
|
| 755 |
+
calculate_adjusted_r_squared(df["Y"], df["Y_hat"]),
|
| 756 |
+
calculate_mape(df["Y"], df["Y_hat"]),
|
| 757 |
+
calculate_aic(df["Y"], df["Y_hat"]),
|
| 758 |
+
calculate_bic(df["Y"], df["Y_hat"])])
|
| 759 |
+
model_metrics_df.index = ["R-squared","Adjusted R-squared","MAPE","AIC","BIC"]
|
| 760 |
+
model_metrics_df = model_metrics_df.transpose()
|
| 761 |
+
model_metrics_df.index = ['']
|
| 762 |
+
return model_metrics_df.round(2)
|
| 763 |
+
|
| 764 |
+
|
Test/scenario_test_df.csv
CHANGED
|
@@ -2,104 +2,104 @@ other_contributions,correction,sales
|
|
| 2 |
1,-890.9083269913208,5690.218095071322
|
| 3 |
1,-475.04172715926325,5552.575607149263
|
| 4 |
1,-3.0084997762223793,5560.943568626222
|
| 5 |
-
1,-55.
|
| 6 |
1,-556.3423571615149,5516.629386071515
|
| 7 |
1,-798.7293276068531,5445.739089056853
|
| 8 |
-
1,-831.
|
| 9 |
-
1,-747.
|
| 10 |
-
1,-420.
|
| 11 |
1,-271.1869770058056,5319.342549515806
|
| 12 |
-
1,36.
|
| 13 |
1,301.40268262302834,5389.612139710971
|
| 14 |
-
1,-149.
|
| 15 |
1,-178.18371062845563,5131.547398718455
|
| 16 |
1,-344.31242848795137,5289.838616957952
|
| 17 |
1,-230.8534688342088,5451.796660734209
|
| 18 |
1,123.81965248641245,5218.377356663587
|
| 19 |
1,-346.37018641133545,5376.028569331336
|
| 20 |
-
1,-271.
|
| 21 |
1,-354.554715570026,5403.077810960025
|
| 22 |
-
1,-19.
|
| 23 |
1,280.9211846086464,5590.702815091353
|
| 24 |
1,219.92735776987683,5516.867885530122
|
| 25 |
-
1,781.
|
| 26 |
-
1,1294.
|
| 27 |
1,738.501471567386,5867.577001832614
|
| 28 |
1,796.9528952899409,5766.399026290058
|
| 29 |
-
1,415.
|
| 30 |
1,786.9046031624202,5653.93211614758
|
| 31 |
-
1,699.
|
| 32 |
1,539.745101025057,5709.584150782943
|
| 33 |
1,377.1008301603306,5701.305955438669
|
| 34 |
1,-171.62603119793766,5654.003287164937
|
| 35 |
1,2.582553312521668,5483.3585810364775
|
| 36 |
-
1,-34.
|
| 37 |
1,-232.94753657288948,5380.036090195889
|
| 38 |
-
1,-468.
|
| 39 |
-
1,-322.
|
| 40 |
1,-286.06881459022316,4870.059378248223
|
| 41 |
1,-567.8495337345976,5126.330691409597
|
| 42 |
1,-178.17958404447836,4755.834189569478
|
| 43 |
-
1,-138.
|
| 44 |
-
1,-224.
|
| 45 |
-
1,792.
|
| 46 |
1,1355.6289643675454,6164.484521602455
|
| 47 |
-
1,986.
|
| 48 |
1,1059.455769237742,6192.769529952258
|
| 49 |
1,383.32346060172495,6147.518456028276
|
| 50 |
1,-187.89672830752534,5715.406060937526
|
| 51 |
-
1,-212.
|
| 52 |
1,72.72524427662756,5103.391602309372
|
| 53 |
1,-95.74246649852375,5238.581337104524
|
| 54 |
1,-120.67574389038145,5559.6276727923805
|
| 55 |
-
1,-129.
|
| 56 |
-
1,-225.
|
| 57 |
-
1,-218.
|
| 58 |
1,-527.1306773658152,5229.707354409815
|
| 59 |
-
1,-787.
|
| 60 |
-
1,-1039.
|
| 61 |
1,-753.3501980635592,5429.758980752559
|
| 62 |
1,-357.5844211273052,5439.306041177304
|
| 63 |
-
1,-324.
|
| 64 |
1,-133.5001332835127,5695.581704533513
|
| 65 |
-
1,-45.
|
| 66 |
-
1,-198.
|
| 67 |
1,-140.84226664971084,5403.844047839711
|
| 68 |
1,-328.0694341550152,5409.443929865015
|
| 69 |
-
1,-471.
|
| 70 |
1,-340.9581299499314,4979.624243809932
|
| 71 |
1,-451.5102744182759,4939.252369518276
|
| 72 |
-
1,-470.
|
| 73 |
-
1,-241.
|
| 74 |
-
1,208.
|
| 75 |
1,515.8201019324006,5531.571609717599
|
| 76 |
-
1,645.
|
| 77 |
-
1,600.
|
| 78 |
1,991.718208446463,5546.432488283537
|
| 79 |
1,1013.1534153918865,5402.554699058114
|
| 80 |
1,917.9498416432871,5331.587882096714
|
| 81 |
1,1015.0218196550877,5173.547445494913
|
| 82 |
1,696.1648921444839,5336.375056005516
|
| 83 |
-
1,847.
|
| 84 |
-
1,306.
|
| 85 |
-
1,584.
|
| 86 |
1,320.81565350241544,4936.522939377584
|
| 87 |
-
1,90.
|
| 88 |
1,403.10225090216045,5224.36913916284
|
| 89 |
-
1,83.
|
| 90 |
1,-278.22837426408205,5013.4219235420815
|
| 91 |
-
1,-594.
|
| 92 |
1,-638.5744723089219,4758.680377859922
|
| 93 |
1,-820.1630688997875,5052.951763736787
|
| 94 |
1,-777.5222929965912,5052.983144825591
|
| 95 |
-
1,-937.
|
| 96 |
-
1,-766.
|
| 97 |
1,-601.9624005578062,5336.127374237805
|
| 98 |
1,-43.38206579649068,5912.821508406491
|
| 99 |
-
1,783.
|
| 100 |
-
1,1048.
|
| 101 |
1,942.7156660498758,5657.687045620124
|
| 102 |
1,459.9194845831371,5422.163026676863
|
| 103 |
1,-457.0944462735897,5375.19352051359
|
| 104 |
-
1,-547.
|
| 105 |
1,-1007.0066006714123,5301.205921773412
|
|
|
|
| 2 |
1,-890.9083269913208,5690.218095071322
|
| 3 |
1,-475.04172715926325,5552.575607149263
|
| 4 |
1,-3.0084997762223793,5560.943568626222
|
| 5 |
+
1,-55.835992656834605,5425.282497616835
|
| 6 |
1,-556.3423571615149,5516.629386071515
|
| 7 |
1,-798.7293276068531,5445.739089056853
|
| 8 |
+
1,-831.8661367345012,5200.425346684501
|
| 9 |
+
1,-747.198588652529,5374.30297088253
|
| 10 |
+
1,-420.26596056385824,5332.913056923858
|
| 11 |
1,-271.1869770058056,5319.342549515806
|
| 12 |
+
1,36.61704801202086,5391.429887731979
|
| 13 |
1,301.40268262302834,5389.612139710971
|
| 14 |
+
1,-149.43278291676052,5242.934237956761
|
| 15 |
1,-178.18371062845563,5131.547398718455
|
| 16 |
1,-344.31242848795137,5289.838616957952
|
| 17 |
1,-230.8534688342088,5451.796660734209
|
| 18 |
1,123.81965248641245,5218.377356663587
|
| 19 |
1,-346.37018641133545,5376.028569331336
|
| 20 |
+
1,-271.2351337049322,5328.863885024933
|
| 21 |
1,-354.554715570026,5403.077810960025
|
| 22 |
+
1,-19.421853013877808,5485.364920013878
|
| 23 |
1,280.9211846086464,5590.702815091353
|
| 24 |
1,219.92735776987683,5516.867885530122
|
| 25 |
+
1,781.0334815120614,5679.80980158794
|
| 26 |
+
1,1294.2147923458097,5794.625140154191
|
| 27 |
1,738.501471567386,5867.577001832614
|
| 28 |
1,796.9528952899409,5766.399026290058
|
| 29 |
+
1,415.4269982696269,5870.755899660373
|
| 30 |
1,786.9046031624202,5653.93211614758
|
| 31 |
+
1,699.8259613792043,5780.494561230796
|
| 32 |
1,539.745101025057,5709.584150782943
|
| 33 |
1,377.1008301603306,5701.305955438669
|
| 34 |
1,-171.62603119793766,5654.003287164937
|
| 35 |
1,2.582553312521668,5483.3585810364775
|
| 36 |
+
1,-34.22562033747454,5514.875846412474
|
| 37 |
1,-232.94753657288948,5380.036090195889
|
| 38 |
+
1,-468.2093499173461,5549.191483465347
|
| 39 |
+
1,-322.5520717213194,5460.39469793332
|
| 40 |
1,-286.06881459022316,4870.059378248223
|
| 41 |
1,-567.8495337345976,5126.330691409597
|
| 42 |
1,-178.17958404447836,4755.834189569478
|
| 43 |
+
1,-138.0179383988434,4914.458840338843
|
| 44 |
+
1,-224.74888886520603,5285.073225435205
|
| 45 |
+
1,792.8627605627144,5620.162487447286
|
| 46 |
1,1355.6289643675454,6164.484521602455
|
| 47 |
+
1,986.2797608661922,6162.196124983807
|
| 48 |
1,1059.455769237742,6192.769529952258
|
| 49 |
1,383.32346060172495,6147.518456028276
|
| 50 |
1,-187.89672830752534,5715.406060937526
|
| 51 |
+
1,-212.61946644455384,5361.829613484554
|
| 52 |
1,72.72524427662756,5103.391602309372
|
| 53 |
1,-95.74246649852375,5238.581337104524
|
| 54 |
1,-120.67574389038145,5559.6276727923805
|
| 55 |
+
1,-129.05511796418887,5484.728899857189
|
| 56 |
+
1,-225.81656994822242,5494.042520330223
|
| 57 |
+
1,-218.372927237976,5302.365695685977
|
| 58 |
1,-527.1306773658152,5229.707354409815
|
| 59 |
+
1,-787.2566332929837,5426.519151763984
|
| 60 |
+
1,-1039.0776762177757,5539.477859049775
|
| 61 |
1,-753.3501980635592,5429.758980752559
|
| 62 |
1,-357.5844211273052,5439.306041177304
|
| 63 |
+
1,-324.8985270979456,5678.245679517945
|
| 64 |
1,-133.5001332835127,5695.581704533513
|
| 65 |
+
1,-45.999364494503425,5662.909688574503
|
| 66 |
+
1,-198.8982053530035,5516.533581953002
|
| 67 |
1,-140.84226664971084,5403.844047839711
|
| 68 |
1,-328.0694341550152,5409.443929865015
|
| 69 |
+
1,-471.04691505620394,5319.741307806204
|
| 70 |
1,-340.9581299499314,4979.624243809932
|
| 71 |
1,-451.5102744182759,4939.252369518276
|
| 72 |
+
1,-470.3738494522704,5272.67316311227
|
| 73 |
+
1,-241.2091197384807,5185.855093778481
|
| 74 |
+
1,208.857413296284,5444.7313112837155
|
| 75 |
1,515.8201019324006,5531.571609717599
|
| 76 |
+
1,645.0637292085721,5567.486440531427
|
| 77 |
+
1,600.0432433501519,5726.386967019847
|
| 78 |
1,991.718208446463,5546.432488283537
|
| 79 |
1,1013.1534153918865,5402.554699058114
|
| 80 |
1,917.9498416432871,5331.587882096714
|
| 81 |
1,1015.0218196550877,5173.547445494913
|
| 82 |
1,696.1648921444839,5336.375056005516
|
| 83 |
+
1,847.2335698491943,5141.959263320807
|
| 84 |
+
1,306.9893113897788,5080.857405947221
|
| 85 |
+
1,584.0043413540352,4984.766656686965
|
| 86 |
1,320.81565350241544,4936.522939377584
|
| 87 |
+
1,90.34779668819283,5252.465610906806
|
| 88 |
1,403.10225090216045,5224.36913916284
|
| 89 |
+
1,83.73958567298087,5191.8993596540195
|
| 90 |
1,-278.22837426408205,5013.4219235420815
|
| 91 |
+
1,-594.5906903171735,5002.829538211174
|
| 92 |
1,-638.5744723089219,4758.680377859922
|
| 93 |
1,-820.1630688997875,5052.951763736787
|
| 94 |
1,-777.5222929965912,5052.983144825591
|
| 95 |
+
1,-937.3473140298456,5133.260108853846
|
| 96 |
+
1,-766.0759176046413,5175.75865344264
|
| 97 |
1,-601.9624005578062,5336.127374237805
|
| 98 |
1,-43.38206579649068,5912.821508406491
|
| 99 |
+
1,783.555777528677,5872.092495641322
|
| 100 |
+
1,1048.3380060975587,5779.248749202441
|
| 101 |
1,942.7156660498758,5657.687045620124
|
| 102 |
1,459.9194845831371,5422.163026676863
|
| 103 |
1,-457.0944462735897,5375.19352051359
|
| 104 |
+
1,-547.8520567101295,4949.859554259129
|
| 105 |
1,-1007.0066006714123,5301.205921773412
|
__pycache__/Streamlit_functions.cpython-310.pyc
ADDED
|
Binary file (14.1 kB). View file
|
|
|
__pycache__/response_curves_model_quality.cpython-310.pyc
ADDED
|
Binary file (6.4 kB). View file
|
|
|
__pycache__/response_curves_model_quality_base.cpython-310.pyc
ADDED
|
Binary file (4.97 kB). View file
|
|
|
all_solutions_2024-05-09.json
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"solution_0": {
|
| 3 |
+
"solution": {
|
| 4 |
+
"media_params": {
|
| 5 |
+
"Broadcast TV": {
|
| 6 |
+
"spend_col": "tv_broadcast_spend",
|
| 7 |
+
"metric_col": "tv_broadcast_grp",
|
| 8 |
+
"half_life": 1.5125527782612616,
|
| 9 |
+
"penetration": 0.7306710045225968,
|
| 10 |
+
"scale": 6661261.92832161,
|
| 11 |
+
"shape": 2.8161330383341983,
|
| 12 |
+
"coeff": 0.04000000000000001
|
| 13 |
+
},
|
| 14 |
+
"Cable TV": {
|
| 15 |
+
"spend_col": "tv_cable_spend",
|
| 16 |
+
"metric_col": "tv_cable_grp",
|
| 17 |
+
"half_life": 1.0115283431416273,
|
| 18 |
+
"penetration": 0.8645370354816814,
|
| 19 |
+
"scale": 6904015.732432723,
|
| 20 |
+
"shape": 0.38530421046089125,
|
| 21 |
+
"coeff": 0.04000000069639975
|
| 22 |
+
},
|
| 23 |
+
"Connected & OTT TV": {
|
| 24 |
+
"spend_col": "stream_video_spend",
|
| 25 |
+
"metric_col": "stream_video_imp",
|
| 26 |
+
"half_life": 0.5387407126474395,
|
| 27 |
+
"penetration": 0.37479811952034575,
|
| 28 |
+
"scale": 52395243.68927538,
|
| 29 |
+
"shape": 0.9891886048110367,
|
| 30 |
+
"coeff": 0.06999999999999999
|
| 31 |
+
},
|
| 32 |
+
"Video": {
|
| 33 |
+
"spend_col": "olv_spend",
|
| 34 |
+
"metric_col": "olv_imp",
|
| 35 |
+
"half_life": 0.9628402347042399,
|
| 36 |
+
"penetration": 0.1339338890582195,
|
| 37 |
+
"scale": 2404356.5978383864,
|
| 38 |
+
"shape": 1.7605314295228363,
|
| 39 |
+
"coeff": 0.04000000000026293
|
| 40 |
+
},
|
| 41 |
+
"Display Prospecting": {
|
| 42 |
+
"spend_col": "disp_prospect_spend",
|
| 43 |
+
"metric_col": "disp_prospect_imp",
|
| 44 |
+
"half_life": 0.3185770016152706,
|
| 45 |
+
"penetration": 0.1470527101992185,
|
| 46 |
+
"scale": 65218855.319753565,
|
| 47 |
+
"shape": 2.2826537278140124,
|
| 48 |
+
"coeff": 0.9999999999999999
|
| 49 |
+
},
|
| 50 |
+
"Display Retargeting": {
|
| 51 |
+
"spend_col": "disp_retarget_spend",
|
| 52 |
+
"metric_col": "disp_retarget_imp",
|
| 53 |
+
"half_life": 0.19649840139877658,
|
| 54 |
+
"penetration": 0.2136738043476821,
|
| 55 |
+
"scale": 52456194.86356406,
|
| 56 |
+
"shape": 1.1049988693888833,
|
| 57 |
+
"coeff": 0.9999999999999999
|
| 58 |
+
},
|
| 59 |
+
"Social Prospecting": {
|
| 60 |
+
"spend_col": "social_prospect_spend",
|
| 61 |
+
"metric_col": "social_prospect_imp",
|
| 62 |
+
"half_life": 0.23348992868088775,
|
| 63 |
+
"penetration": 0.23170623958443773,
|
| 64 |
+
"scale": 18416.13907661135,
|
| 65 |
+
"shape": 2.7416996439407058,
|
| 66 |
+
"coeff": 0.04000000000027258
|
| 67 |
+
},
|
| 68 |
+
"Social Retargeting": {
|
| 69 |
+
"spend_col": "social_retarget_spend",
|
| 70 |
+
"metric_col": "social_retarget_imp",
|
| 71 |
+
"half_life": 0.311145083025538,
|
| 72 |
+
"penetration": 0.07864050141293169,
|
| 73 |
+
"scale": 5734521.583224347,
|
| 74 |
+
"shape": 2.12760192364264,
|
| 75 |
+
"coeff": 0.09999999999999999
|
| 76 |
+
},
|
| 77 |
+
"Search Brand": {
|
| 78 |
+
"spend_col": "search_brand_spend",
|
| 79 |
+
"metric_col": "search_brand_imp",
|
| 80 |
+
"half_life": 0.6410768184353358,
|
| 81 |
+
"penetration": 0.9478746429718543,
|
| 82 |
+
"scale": 64091.710880368904,
|
| 83 |
+
"shape": 1.8822844845279567,
|
| 84 |
+
"coeff": 0.008000000000010674
|
| 85 |
+
},
|
| 86 |
+
"Search Non-brand": {
|
| 87 |
+
"spend_col": "search_nonbrand_spend",
|
| 88 |
+
"metric_col": "search_nonbrand_imp",
|
| 89 |
+
"half_life": 0.021077826067475037,
|
| 90 |
+
"penetration": 0.9970358969389862,
|
| 91 |
+
"scale": 20075934.15955288,
|
| 92 |
+
"shape": 1.0095598649180348,
|
| 93 |
+
"coeff": 0.17500000000000002
|
| 94 |
+
},
|
| 95 |
+
"Digital Partners": {
|
| 96 |
+
"spend_col": "cm_spend",
|
| 97 |
+
"metric_col": "cm_spend",
|
| 98 |
+
"half_life": 0.035288031130940034,
|
| 99 |
+
"penetration": 0.7900892706847193,
|
| 100 |
+
"scale": 16774246.211700687,
|
| 101 |
+
"shape": 0.7709184982171291,
|
| 102 |
+
"coeff": 0.5199999999999999
|
| 103 |
+
},
|
| 104 |
+
"Audio": {
|
| 105 |
+
"spend_col": "audio_spend",
|
| 106 |
+
"metric_col": "audio_imp",
|
| 107 |
+
"half_life": 0.2825119018286497,
|
| 108 |
+
"penetration": 0.5297375597273687,
|
| 109 |
+
"scale": 2068888.3017906512,
|
| 110 |
+
"shape": 1.801467700171006,
|
| 111 |
+
"coeff": 0.018000000000031005
|
| 112 |
+
},
|
| 113 |
+
"Email": {
|
| 114 |
+
"spend_col": "email_spend",
|
| 115 |
+
"metric_col": "email_imp",
|
| 116 |
+
"half_life": 0.27709327146939394,
|
| 117 |
+
"penetration": 0.3730988846104394,
|
| 118 |
+
"scale": 4565323.224523856,
|
| 119 |
+
"shape": 0.4019891584296605,
|
| 120 |
+
"coeff": 0.015000000000000001
|
| 121 |
+
}
|
| 122 |
+
},
|
| 123 |
+
"control_params": {
|
| 124 |
+
"Unemployment": {
|
| 125 |
+
"metric_col": "unemp",
|
| 126 |
+
"coeff": 1.7500000000000413
|
| 127 |
+
},
|
| 128 |
+
"Competitors spending": {
|
| 129 |
+
"metric_col": "comp_spend_log",
|
| 130 |
+
"coeff": -0.2650000008161444
|
| 131 |
+
}
|
| 132 |
+
},
|
| 133 |
+
"other_params": {
|
| 134 |
+
"Trend": {
|
| 135 |
+
"coeff": 1.0639086682507188
|
| 136 |
+
},
|
| 137 |
+
"Seasonality": {
|
| 138 |
+
"coeff": 0.9338025000581551
|
| 139 |
+
},
|
| 140 |
+
"Intercept": {
|
| 141 |
+
"coeff": -0.9999999999990916
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
},
|
| 145 |
+
"Base-Media": "Base : Media = 51% : 49%.",
|
| 146 |
+
"Best_Soln": false
|
| 147 |
+
}
|
| 148 |
+
}
|
pages/2_Scenario_Planner.py
ADDED
|
@@ -0,0 +1,1532 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from numerize.numerize import numerize
|
| 3 |
+
import numpy as np
|
| 4 |
+
from functools import partial
|
| 5 |
+
from collections import OrderedDict
|
| 6 |
+
from plotly.subplots import make_subplots
|
| 7 |
+
import plotly.graph_objects as go
|
| 8 |
+
from utilities import (
|
| 9 |
+
format_numbers,format_numbers_f,
|
| 10 |
+
load_local_css,
|
| 11 |
+
set_header,
|
| 12 |
+
initialize_data,
|
| 13 |
+
load_authenticator,
|
| 14 |
+
send_email,
|
| 15 |
+
channel_name_formating,
|
| 16 |
+
)
|
| 17 |
+
from classes import class_from_dict, class_to_dict
|
| 18 |
+
import pickle
|
| 19 |
+
import streamlit_authenticator as stauth
|
| 20 |
+
import yaml
|
| 21 |
+
from yaml import SafeLoader
|
| 22 |
+
import re
|
| 23 |
+
import pandas as pd
|
| 24 |
+
import plotly.express as px
|
| 25 |
+
import response_curves_model_quality as rc
|
| 26 |
+
|
| 27 |
+
st.set_page_config(layout="wide")
|
| 28 |
+
load_local_css("styles.css")
|
| 29 |
+
set_header()
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
for k, v in st.session_state.items():
|
| 33 |
+
if k not in ["logout", "login", "config"] and not k.startswith("FormSubmitter"):
|
| 34 |
+
st.session_state[k] = v
|
| 35 |
+
# ======================================================== #
|
| 36 |
+
# ======================= Functions ====================== #
|
| 37 |
+
# ======================================================== #
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def optimize(key, status_placeholder):
|
| 41 |
+
"""
|
| 42 |
+
Optimize the spends for the sales
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
channel_list = [
|
| 46 |
+
key for key, value in st.session_state["optimization_channels"].items() if value
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
if len(channel_list) > 0:
|
| 50 |
+
scenario = st.session_state["scenario"]
|
| 51 |
+
if key.lower() == "media spends":
|
| 52 |
+
with status_placeholder:
|
| 53 |
+
with st.spinner("Optimizing"):
|
| 54 |
+
result = st.session_state["scenario"].optimize(
|
| 55 |
+
st.session_state["total_spends_change"], channel_list
|
| 56 |
+
)
|
| 57 |
+
# elif key.lower() == "revenue":
|
| 58 |
+
else:
|
| 59 |
+
with status_placeholder:
|
| 60 |
+
with st.spinner("Optimizing"):
|
| 61 |
+
|
| 62 |
+
result = st.session_state["scenario"].optimize_spends(
|
| 63 |
+
st.session_state["total_sales_change"], channel_list
|
| 64 |
+
)
|
| 65 |
+
for channel_name, modified_spends in result:
|
| 66 |
+
|
| 67 |
+
st.session_state[channel_name] = numerize(
|
| 68 |
+
modified_spends * scenario.channels[channel_name].conversion_rate,
|
| 69 |
+
1,
|
| 70 |
+
)
|
| 71 |
+
prev_spends = (
|
| 72 |
+
st.session_state["scenario"].channels[channel_name].actual_total_spends
|
| 73 |
+
)
|
| 74 |
+
st.session_state[f"{channel_name}_change"] = round(
|
| 75 |
+
100 * (modified_spends - prev_spends) / prev_spends, 2
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def save_scenario(scenario_name):
|
| 80 |
+
"""
|
| 81 |
+
Save the current scenario with the mentioned name in the session state
|
| 82 |
+
|
| 83 |
+
Parameters
|
| 84 |
+
----------
|
| 85 |
+
scenario_name
|
| 86 |
+
Name of the scenario to be saved
|
| 87 |
+
"""
|
| 88 |
+
if "saved_scenarios" not in st.session_state:
|
| 89 |
+
st.session_state = OrderedDict()
|
| 90 |
+
|
| 91 |
+
# st.session_state['saved_scenarios'][scenario_name] = st.session_state['scenario'].save()
|
| 92 |
+
st.session_state["saved_scenarios"][scenario_name] = class_to_dict(
|
| 93 |
+
st.session_state["scenario"]
|
| 94 |
+
)
|
| 95 |
+
st.session_state["scenario_input"] = ""
|
| 96 |
+
# print(type(st.session_state['saved_scenarios']))
|
| 97 |
+
with open("../saved_scenarios.pkl", "wb") as f:
|
| 98 |
+
pickle.dump(st.session_state["saved_scenarios"], f)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
if "allow_spends_update" not in st.session_state:
|
| 102 |
+
st.session_state["allow_spends_update"] = True
|
| 103 |
+
|
| 104 |
+
if "allow_sales_update" not in st.session_state:
|
| 105 |
+
st.session_state["allow_sales_update"] = True
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def update_sales_abs_slider():
|
| 109 |
+
actual_sales = _scenario.actual_total_sales
|
| 110 |
+
if validate_input(st.session_state["total_sales_change_abs_slider"]):
|
| 111 |
+
modified_sales = extract_number_for_string(
|
| 112 |
+
st.session_state["total_sales_change_abs_slider"]
|
| 113 |
+
)
|
| 114 |
+
st.session_state["total_sales_change"] = round(
|
| 115 |
+
((modified_sales / actual_sales) - 1) * 100
|
| 116 |
+
)
|
| 117 |
+
st.session_state["total_sales_change_abs"] = numerize(modified_sales, 1)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def update_sales_abs():
|
| 121 |
+
if (
|
| 122 |
+
st.session_state["total_sales_change_abs"]
|
| 123 |
+
in st.session_state["total_sales_change_abs_slider_options"]
|
| 124 |
+
):
|
| 125 |
+
st.session_state["allow_sales_update"] = True
|
| 126 |
+
else:
|
| 127 |
+
st.session_state["allow_sales_update"] = False
|
| 128 |
+
|
| 129 |
+
actual_sales = _scenario.actual_total_sales
|
| 130 |
+
if (
|
| 131 |
+
validate_input(st.session_state["total_sales_change_abs"])
|
| 132 |
+
and st.session_state["allow_sales_update"]
|
| 133 |
+
):
|
| 134 |
+
modified_sales = extract_number_for_string(
|
| 135 |
+
st.session_state["total_sales_change_abs"]
|
| 136 |
+
)
|
| 137 |
+
st.session_state["total_sales_change"] = round(
|
| 138 |
+
((modified_sales / actual_sales) - 1) * 100
|
| 139 |
+
)
|
| 140 |
+
st.session_state["total_sales_change_abs_slider"] = numerize(modified_sales, 1)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def update_sales():
|
| 144 |
+
st.session_state["total_sales_change_abs"] = numerize(
|
| 145 |
+
(1 + st.session_state["total_sales_change"] / 100)
|
| 146 |
+
* _scenario.actual_total_sales,
|
| 147 |
+
1,
|
| 148 |
+
)
|
| 149 |
+
st.session_state["total_sales_change_abs_slider"] = numerize(
|
| 150 |
+
(1 + st.session_state["total_sales_change"] / 100)
|
| 151 |
+
* _scenario.actual_total_sales,
|
| 152 |
+
1,
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def update_all_spends_abs_slider():
|
| 157 |
+
actual_spends = _scenario.actual_total_spends
|
| 158 |
+
if validate_input(st.session_state["total_spends_change_abs_slider"]):
|
| 159 |
+
modified_spends = extract_number_for_string(
|
| 160 |
+
st.session_state["total_spends_change_abs_slider"]
|
| 161 |
+
)
|
| 162 |
+
st.session_state["total_spends_change"] = round(
|
| 163 |
+
((modified_spends / actual_spends) - 1) * 100
|
| 164 |
+
)
|
| 165 |
+
st.session_state["total_spends_change_abs"] = numerize(modified_spends, 1)
|
| 166 |
+
|
| 167 |
+
update_all_spends()
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
# def update_all_spends_abs_slider():
|
| 171 |
+
# actual_spends = _scenario.actual_total_spends
|
| 172 |
+
# if validate_input(st.session_state["total_spends_change_abs_slider"]):
|
| 173 |
+
# print("#" * 100)
|
| 174 |
+
# print(st.session_state["total_spends_change_abs_slider"])C:\Users\PragyaJatav\Downloads\Untitled Folder 2\simulatorAldi\pages\8_Scenario_Planner.py
|
| 175 |
+
# print("#" * 100)
|
| 176 |
+
|
| 177 |
+
# modified_spends = extract_number_for_string(
|
| 178 |
+
# st.session_state["total_spends_change_abs_slider"]
|
| 179 |
+
# )
|
| 180 |
+
# st.session_state["total_spends_change"] = (
|
| 181 |
+
# (modified_spends / actual_spends) - 1
|
| 182 |
+
# ) * 100
|
| 183 |
+
# st.session_state["total_spends_change_abs"] = st.session_state[
|
| 184 |
+
# "total_spends_change_abs_slider"
|
| 185 |
+
# ]
|
| 186 |
+
|
| 187 |
+
# update_all_spends()
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def update_all_spends_abs():
|
| 191 |
+
if (
|
| 192 |
+
st.session_state["total_spends_change_abs"]
|
| 193 |
+
in st.session_state["total_spends_change_abs_slider_options"]
|
| 194 |
+
):
|
| 195 |
+
st.session_state["allow_spends_update"] = True
|
| 196 |
+
else:
|
| 197 |
+
st.session_state["allow_spends_update"] = False
|
| 198 |
+
|
| 199 |
+
actual_spends = _scenario.actual_total_spends
|
| 200 |
+
if (
|
| 201 |
+
validate_input(st.session_state["total_spends_change_abs"])
|
| 202 |
+
and st.session_state["allow_spends_update"]
|
| 203 |
+
):
|
| 204 |
+
modified_spends = extract_number_for_string(
|
| 205 |
+
st.session_state["total_spends_change_abs"]
|
| 206 |
+
)
|
| 207 |
+
st.session_state["total_spends_change"] = (
|
| 208 |
+
(modified_spends / actual_spends) - 1
|
| 209 |
+
) * 100
|
| 210 |
+
st.session_state["total_spends_change_abs_slider"] = st.session_state[
|
| 211 |
+
"total_spends_change_abs"
|
| 212 |
+
]
|
| 213 |
+
|
| 214 |
+
update_all_spends()
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def update_spends():
|
| 218 |
+
st.session_state["total_spends_change_abs"] = numerize(
|
| 219 |
+
(1 + st.session_state["total_spends_change"] / 100)
|
| 220 |
+
* _scenario.actual_total_spends,
|
| 221 |
+
1,
|
| 222 |
+
)
|
| 223 |
+
st.session_state["total_spends_change_abs_slider"] = numerize(
|
| 224 |
+
(1 + st.session_state["total_spends_change"] / 100)
|
| 225 |
+
* _scenario.actual_total_spends,
|
| 226 |
+
1,
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
update_all_spends()
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def update_all_spends():
|
| 233 |
+
"""
|
| 234 |
+
Updates spends for all the channels with the given overall spends change
|
| 235 |
+
"""
|
| 236 |
+
percent_change = st.session_state["total_spends_change"]
|
| 237 |
+
|
| 238 |
+
for channel_name in st.session_state["channels_list"]:
|
| 239 |
+
channel = st.session_state["scenario"].channels[channel_name]
|
| 240 |
+
current_spends = channel.actual_total_spends
|
| 241 |
+
modified_spends = (1 + percent_change / 100) * current_spends
|
| 242 |
+
st.session_state["scenario"].update(channel_name, modified_spends)
|
| 243 |
+
st.session_state[channel_name] = numerize(
|
| 244 |
+
modified_spends * channel.conversion_rate, 1
|
| 245 |
+
)
|
| 246 |
+
st.session_state[f"{channel_name}_change"] = percent_change
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def extract_number_for_string(string_input):
|
| 250 |
+
string_input = string_input.upper()
|
| 251 |
+
if string_input.endswith("K"):
|
| 252 |
+
return float(string_input[:-1]) * 10**3
|
| 253 |
+
elif string_input.endswith("M"):
|
| 254 |
+
return float(string_input[:-1]) * 10**6
|
| 255 |
+
elif string_input.endswith("B"):
|
| 256 |
+
return float(string_input[:-1]) * 10**9
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def validate_input(string_input):
|
| 260 |
+
pattern = r"\d+\.?\d*[K|M|B]$"
|
| 261 |
+
match = re.match(pattern, string_input)
|
| 262 |
+
if match is None:
|
| 263 |
+
return False
|
| 264 |
+
return True
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def update_data_by_percent(channel_name):
|
| 268 |
+
prev_spends = (
|
| 269 |
+
st.session_state["scenario"].channels[channel_name].actual_total_spends
|
| 270 |
+
* st.session_state["scenario"].channels[channel_name].conversion_rate
|
| 271 |
+
)
|
| 272 |
+
modified_spends = prev_spends * (
|
| 273 |
+
1 + st.session_state[f"{channel_name}_change"] / 100
|
| 274 |
+
)
|
| 275 |
+
st.session_state[channel_name] = numerize(modified_spends, 1)
|
| 276 |
+
st.session_state["scenario"].update(
|
| 277 |
+
channel_name,
|
| 278 |
+
modified_spends
|
| 279 |
+
/ st.session_state["scenario"].channels[channel_name].conversion_rate,
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def update_data(channel_name):
|
| 284 |
+
"""
|
| 285 |
+
Updates the spends for the given channel
|
| 286 |
+
"""
|
| 287 |
+
|
| 288 |
+
if validate_input(st.session_state[channel_name]):
|
| 289 |
+
modified_spends = extract_number_for_string(st.session_state[channel_name])
|
| 290 |
+
prev_spends = (
|
| 291 |
+
st.session_state["scenario"].channels[channel_name].actual_total_spends
|
| 292 |
+
* st.session_state["scenario"].channels[channel_name].conversion_rate
|
| 293 |
+
)
|
| 294 |
+
st.session_state[f"{channel_name}_change"] = round(
|
| 295 |
+
100 * (modified_spends - prev_spends) / prev_spends, 2
|
| 296 |
+
)
|
| 297 |
+
st.session_state["scenario"].update(
|
| 298 |
+
channel_name,
|
| 299 |
+
modified_spends
|
| 300 |
+
/ st.session_state["scenario"].channels[channel_name].conversion_rate,
|
| 301 |
+
)
|
| 302 |
+
# st.session_state['scenario'].update(channel_name, modified_spends)
|
| 303 |
+
# else:
|
| 304 |
+
# try:
|
| 305 |
+
# modified_spends = float(st.session_state[channel_name])
|
| 306 |
+
# prev_spends = st.session_state['scenario'].channels[channel_name].actual_total_spends * st.session_state['scenario'].channels[channel_name].conversion_rate
|
| 307 |
+
# st.session_state[f'{channel_name}_change'] = round(100*(modified_spends - prev_spends) / prev_spends,2)
|
| 308 |
+
# st.session_state['scenario'].update(channel_name, modified_spends/st.session_state['scenario'].channels[channel_name].conversion_rate)
|
| 309 |
+
# st.session_state[f'{channel_name}'] = numerize(modified_spends,1)
|
| 310 |
+
# except ValueError:
|
| 311 |
+
# st.write('Invalid input')
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def select_channel_for_optimization(channel_name):
|
| 315 |
+
"""
|
| 316 |
+
Marks the given channel for optimization
|
| 317 |
+
"""
|
| 318 |
+
st.session_state["optimization_channels"][channel_name] = st.session_state[
|
| 319 |
+
f"{channel_name}_selected"
|
| 320 |
+
]
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def select_all_channels_for_optimization():
|
| 324 |
+
"""
|
| 325 |
+
Marks all the channel for optimization
|
| 326 |
+
"""
|
| 327 |
+
for channel_name in st.session_state["optimization_channels"].keys():
|
| 328 |
+
st.session_state[f"{channel_name}_selected"] = st.session_state[
|
| 329 |
+
"optimze_all_channels"
|
| 330 |
+
]
|
| 331 |
+
st.session_state["optimization_channels"][channel_name] = st.session_state[
|
| 332 |
+
"optimze_all_channels"
|
| 333 |
+
]
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def update_penalty():
|
| 337 |
+
"""
|
| 338 |
+
Updates the penalty flag for sales calculation
|
| 339 |
+
"""
|
| 340 |
+
st.session_state["scenario"].update_penalty(st.session_state["apply_penalty"])
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def reset_scenario(panel_selected, file_selected, updated_rcs):
|
| 344 |
+
# #print(st.session_state['default_scenario_dict'])
|
| 345 |
+
# st.session_state['scenario'] = class_from_dict(st.session_state['default_scenario_dict'])
|
| 346 |
+
# for channel in st.session_state['scenario'].channels.values():
|
| 347 |
+
# st.session_state[channel.name] = float(channel.actual_total_spends * channel.conversion_rate)
|
| 348 |
+
# initialize_data()
|
| 349 |
+
|
| 350 |
+
if panel_selected == "Total Market":
|
| 351 |
+
initialize_data(
|
| 352 |
+
panel=panel_selected,
|
| 353 |
+
target_file=file_selected,
|
| 354 |
+
updated_rcs=updated_rcs,
|
| 355 |
+
metrics=metrics_selected,
|
| 356 |
+
)
|
| 357 |
+
panel = None
|
| 358 |
+
else:
|
| 359 |
+
initialize_data(
|
| 360 |
+
panel=panel_selected,
|
| 361 |
+
target_file=file_selected,
|
| 362 |
+
updated_rcs=updated_rcs,
|
| 363 |
+
metrics=metrics_selected,
|
| 364 |
+
)
|
| 365 |
+
|
| 366 |
+
for channel_name in st.session_state["channels_list"]:
|
| 367 |
+
st.session_state[f"{channel_name}_selected"] = False
|
| 368 |
+
st.session_state[f"{channel_name}_change"] = 0
|
| 369 |
+
st.session_state["optimze_all_channels"] = False
|
| 370 |
+
|
| 371 |
+
st.session_state["total_sales_change"] = 0
|
| 372 |
+
|
| 373 |
+
update_spends()
|
| 374 |
+
update_sales()
|
| 375 |
+
|
| 376 |
+
reset_inputs()
|
| 377 |
+
|
| 378 |
+
# st.rerun()
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
def format_number(num):
|
| 382 |
+
if num >= 1_000_000:
|
| 383 |
+
return f"{num / 1_000_000:.2f}M"
|
| 384 |
+
elif num >= 1_000:
|
| 385 |
+
return f"{num / 1_000:.0f}K"
|
| 386 |
+
else:
|
| 387 |
+
return f"{num:.2f}"
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def summary_plot(data, x, y, title, text_column):
|
| 391 |
+
fig = px.bar(
|
| 392 |
+
data,
|
| 393 |
+
x=x,
|
| 394 |
+
y=y,
|
| 395 |
+
orientation="h",
|
| 396 |
+
title=title,
|
| 397 |
+
text=text_column,
|
| 398 |
+
color="Channel_name",
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
# Convert text_column to numeric values
|
| 402 |
+
data[text_column] = pd.to_numeric(data[text_column], errors="coerce")
|
| 403 |
+
|
| 404 |
+
# Update the format of the displayed text based on magnitude
|
| 405 |
+
fig.update_traces(
|
| 406 |
+
texttemplate="%{text:.2s}",
|
| 407 |
+
textposition="outside",
|
| 408 |
+
hovertemplate="%{x:.2s}",
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
fig.update_layout(xaxis_title=x, yaxis_title="Channel Name", showlegend=False)
|
| 412 |
+
return fig
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
def s_curve(x, K, b, a, x0):
|
| 416 |
+
return K / (1 + b * np.exp(-a * (x - x0)))
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
def find_segment_value(x, roi, mroi):
|
| 420 |
+
start_value = x[0]
|
| 421 |
+
end_value = x[len(x) - 1]
|
| 422 |
+
|
| 423 |
+
# Condition for green region: Both MROI and ROI > 1
|
| 424 |
+
green_condition = (roi > 1) & (mroi > 1)
|
| 425 |
+
left_indices = np.where(green_condition)[0]
|
| 426 |
+
left_value = x[left_indices[0]] if left_indices.size > 0 else x[0]
|
| 427 |
+
|
| 428 |
+
right_indices = np.where(green_condition)[0]
|
| 429 |
+
right_value = x[right_indices[-1]] if right_indices.size > 0 else x[0]
|
| 430 |
+
|
| 431 |
+
return start_value, end_value, left_value, right_value
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
def calculate_rgba(
|
| 435 |
+
start_value, end_value, left_value, right_value, current_channel_spends
|
| 436 |
+
):
|
| 437 |
+
# Initialize alpha to None for clarity
|
| 438 |
+
alpha = None
|
| 439 |
+
|
| 440 |
+
# Determine the color and calculate relative_position and alpha based on the point's position
|
| 441 |
+
if start_value <= current_channel_spends <= left_value:
|
| 442 |
+
color = "yellow"
|
| 443 |
+
relative_position = (current_channel_spends - start_value) / (
|
| 444 |
+
left_value - start_value
|
| 445 |
+
)
|
| 446 |
+
alpha = 0.8 - (0.6 * relative_position) # Alpha decreases from start to end
|
| 447 |
+
|
| 448 |
+
elif left_value < current_channel_spends <= right_value:
|
| 449 |
+
color = "green"
|
| 450 |
+
relative_position = (current_channel_spends - left_value) / (
|
| 451 |
+
right_value - left_value
|
| 452 |
+
)
|
| 453 |
+
alpha = 0.8 - (0.6 * relative_position) # Alpha decreases from start to end
|
| 454 |
+
|
| 455 |
+
elif right_value < current_channel_spends <= end_value:
|
| 456 |
+
color = "red"
|
| 457 |
+
relative_position = (current_channel_spends - right_value) / (
|
| 458 |
+
end_value - right_value
|
| 459 |
+
)
|
| 460 |
+
alpha = 0.2 + (0.6 * relative_position) # Alpha increases from start to end
|
| 461 |
+
|
| 462 |
+
else:
|
| 463 |
+
# Default case, if the spends are outside the defined ranges
|
| 464 |
+
return "rgba(136, 136, 136, 0.5)" # Grey for values outside the range
|
| 465 |
+
|
| 466 |
+
# Ensure alpha is within the intended range in case of any calculation overshoot
|
| 467 |
+
alpha = max(0.2, min(alpha, 0.8))
|
| 468 |
+
|
| 469 |
+
# Define color codes for RGBA
|
| 470 |
+
color_codes = {
|
| 471 |
+
"yellow": "255, 255, 0", # RGB for yellow
|
| 472 |
+
"green": "0, 128, 0", # RGB for green
|
| 473 |
+
"red": "255, 0, 0", # RGB for red
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
rgba = f"rgba({color_codes[color]}, {alpha})"
|
| 477 |
+
return rgba
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
def debug_temp(x_test, power, K, b, a, x0):
|
| 481 |
+
print("*" * 100)
|
| 482 |
+
# Calculate the count of bins
|
| 483 |
+
count_lower_bin = sum(1 for x in x_test if x <= 2524)
|
| 484 |
+
count_center_bin = sum(1 for x in x_test if x > 2524 and x <= 3377)
|
| 485 |
+
count_ = sum(1 for x in x_test if x > 3377)
|
| 486 |
+
|
| 487 |
+
print(
|
| 488 |
+
f"""
|
| 489 |
+
lower : {count_lower_bin}
|
| 490 |
+
center : {count_center_bin}
|
| 491 |
+
upper : {count_}
|
| 492 |
+
"""
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
# @st.cache
|
| 497 |
+
def plot_response_curves(summary_df_sorted):
|
| 498 |
+
# rows = (
|
| 499 |
+
# len(channels_list) // cols
|
| 500 |
+
# if len(channels_list) % cols == 0
|
| 501 |
+
# else len(channels_list) // cols + 1
|
| 502 |
+
# )
|
| 503 |
+
# rcs = st.session_state["rcs"]
|
| 504 |
+
# shapes = []
|
| 505 |
+
# fig = make_subplots(rows=rows, cols=cols, subplot_titles=channels_list)
|
| 506 |
+
channel_cols = [
|
| 507 |
+
'BroadcastTV',
|
| 508 |
+
'CableTV',
|
| 509 |
+
'Connected&OTTTV',
|
| 510 |
+
'DisplayProspecting',
|
| 511 |
+
'DisplayRetargeting',
|
| 512 |
+
'Video',
|
| 513 |
+
'SocialProspecting',
|
| 514 |
+
'SocialRetargeting',
|
| 515 |
+
'SearchBrand',
|
| 516 |
+
'SearchNon-brand',
|
| 517 |
+
'DigitalPartners',
|
| 518 |
+
'Audio',
|
| 519 |
+
'Email']
|
| 520 |
+
summary_df_sorted.index = summary_df_sorted["Channel_name"]
|
| 521 |
+
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)]
|
| 522 |
+
|
| 523 |
+
# Display figures in a grid layout
|
| 524 |
+
cols = st.columns(3) # 4 columns for the grid
|
| 525 |
+
|
| 526 |
+
for idx, fig in enumerate(figures):
|
| 527 |
+
col = cols[idx % 3]
|
| 528 |
+
with col:
|
| 529 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 530 |
+
|
| 531 |
+
# cols = st.columns(3)
|
| 532 |
+
# for i in range(0, len(channels_list)):
|
| 533 |
+
|
| 534 |
+
# col = channels_list[i]
|
| 535 |
+
# if col == "Panel":
|
| 536 |
+
# continue
|
| 537 |
+
# st.write(col)
|
| 538 |
+
# x_modified = summary_df_sorted["Optimized_spend"][col]/104
|
| 539 |
+
# y_modified = summary_df_sorted["New_sales"][col]/104
|
| 540 |
+
# st.plotly_chart(rc.response_curves(col,x_modified,y_modified))
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
# @st.cache
|
| 544 |
+
# def plot_response_curves():
|
| 545 |
+
# cols = 4
|
| 546 |
+
# rcs = st.session_state["rcs"]
|
| 547 |
+
# shapes = []
|
| 548 |
+
# fig = make_subplots(rows=6, cols=cols, subplot_titles=channels_list)
|
| 549 |
+
# for i in range(0, len(channels_list)):
|
| 550 |
+
# col = channels_list[i]
|
| 551 |
+
# x = st.session_state["actual_df"][col].values
|
| 552 |
+
# spends = x.sum()
|
| 553 |
+
# power = np.ceil(np.log(x.max()) / np.log(10)) - 3
|
| 554 |
+
# x = np.linspace(0, 3 * x.max(), 200)
|
| 555 |
+
|
| 556 |
+
# K = rcs[col]["K"]
|
| 557 |
+
# b = rcs[col]["b"]
|
| 558 |
+
# a = rcs[col]["a"]
|
| 559 |
+
# x0 = rcs[col]["x0"]
|
| 560 |
+
|
| 561 |
+
# y = s_curve(x / 10**power, K, b, a, x0)
|
| 562 |
+
# roi = y / x
|
| 563 |
+
# marginal_roi = a * (y) * (1 - y / K)
|
| 564 |
+
# fig.add_trace(
|
| 565 |
+
# go.Scatter(
|
| 566 |
+
# x=52
|
| 567 |
+
# * x
|
| 568 |
+
# * st.session_state["scenario"].channels[col].conversion_rate,
|
| 569 |
+
# y=52 * y,
|
| 570 |
+
# name=col,
|
| 571 |
+
# customdata=np.stack((roi, marginal_roi), axis=-1),
|
| 572 |
+
# hovertemplate="Spend:%{x:$.2s}<br>Sale:%{y:$.2s}<br>ROI:%{customdata[0]:.3f}<br>MROI:%{customdata[1]:.3f}",
|
| 573 |
+
# ),
|
| 574 |
+
# row=1 + (i) // cols,
|
| 575 |
+
# col=i % cols + 1,
|
| 576 |
+
# )
|
| 577 |
+
|
| 578 |
+
# fig.add_trace(
|
| 579 |
+
# go.Scatter(
|
| 580 |
+
# x=[
|
| 581 |
+
# spends
|
| 582 |
+
# * st.session_state["scenario"]
|
| 583 |
+
# .channels[col]
|
| 584 |
+
# .conversion_rate
|
| 585 |
+
# ],
|
| 586 |
+
# y=[52 * s_curve(spends / (10**power * 52), K, b, a, x0)],
|
| 587 |
+
# name=col,
|
| 588 |
+
# legendgroup=col,
|
| 589 |
+
# showlegend=False,
|
| 590 |
+
# marker=dict(color=["black"]),
|
| 591 |
+
# ),
|
| 592 |
+
# row=1 + (i) // cols,
|
| 593 |
+
# col=i % cols + 1,
|
| 594 |
+
# )
|
| 595 |
+
|
| 596 |
+
# shapes.append(
|
| 597 |
+
# go.layout.Shape(
|
| 598 |
+
# type="line",
|
| 599 |
+
# x0=0,
|
| 600 |
+
# y0=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
|
| 601 |
+
# x1=spends
|
| 602 |
+
# * st.session_state["scenario"].channels[col].conversion_rate,
|
| 603 |
+
# y1=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
|
| 604 |
+
# line_width=1,
|
| 605 |
+
# line_dash="dash",
|
| 606 |
+
# line_color="black",
|
| 607 |
+
# xref=f"x{i+1}",
|
| 608 |
+
# yref=f"y{i+1}",
|
| 609 |
+
# )
|
| 610 |
+
# )
|
| 611 |
+
|
| 612 |
+
# shapes.append(
|
| 613 |
+
# go.layout.Shape(
|
| 614 |
+
# type="line",
|
| 615 |
+
# x0=spends
|
| 616 |
+
# * st.session_state["scenario"].channels[col].conversion_rate,
|
| 617 |
+
# y0=0,
|
| 618 |
+
# x1=spends
|
| 619 |
+
# * st.session_state["scenario"].channels[col].conversion_rate,
|
| 620 |
+
# y1=52 * s_curve(spends / (10**power * 52), K, b, a, x0),
|
| 621 |
+
# line_width=1,
|
| 622 |
+
# line_dash="dash",
|
| 623 |
+
# line_color="black",
|
| 624 |
+
# xref=f"x{i+1}",
|
| 625 |
+
# yref=f"y{i+1}",
|
| 626 |
+
# )
|
| 627 |
+
# )
|
| 628 |
+
|
| 629 |
+
# fig.update_layout(
|
| 630 |
+
# height=1500,
|
| 631 |
+
# width=1000,
|
| 632 |
+
# title_text="Response Curves",
|
| 633 |
+
# showlegend=False,
|
| 634 |
+
# shapes=shapes,
|
| 635 |
+
# )
|
| 636 |
+
# fig.update_annotations(font_size=10)
|
| 637 |
+
# fig.update_xaxes(title="Spends")
|
| 638 |
+
# fig.update_yaxes(title=target)
|
| 639 |
+
# return fig
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
# ======================================================== #
|
| 643 |
+
# ==================== HTML Components =================== #
|
| 644 |
+
# ======================================================== #
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
def generate_spending_header(heading):
|
| 648 |
+
return st.markdown(
|
| 649 |
+
f"""<h2 class="spends-header">{heading}</h2>""", unsafe_allow_html=True
|
| 650 |
+
)
|
| 651 |
+
|
| 652 |
+
|
| 653 |
+
# ======================================================== #
|
| 654 |
+
# =================== Session variables ================== #
|
| 655 |
+
# ======================================================== #
|
| 656 |
+
|
| 657 |
+
with open("config.yaml") as file:
|
| 658 |
+
config = yaml.load(file, Loader=SafeLoader)
|
| 659 |
+
st.session_state["config"] = config
|
| 660 |
+
|
| 661 |
+
authenticator = stauth.Authenticate(
|
| 662 |
+
config["credentials"],
|
| 663 |
+
config["cookie"]["name"],
|
| 664 |
+
config["cookie"]["key"],
|
| 665 |
+
config["cookie"]["expiry_days"],
|
| 666 |
+
config["preauthorized"],
|
| 667 |
+
)
|
| 668 |
+
st.session_state["authenticator"] = authenticator
|
| 669 |
+
name, authentication_status, username = authenticator.login("Login", "main")
|
| 670 |
+
auth_status = st.session_state.get("authentication_status")
|
| 671 |
+
|
| 672 |
+
import os
|
| 673 |
+
import glob
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
def get_excel_names(directory):
|
| 677 |
+
# Create a list to hold the final parts of the filenames
|
| 678 |
+
last_portions = []
|
| 679 |
+
|
| 680 |
+
# Patterns to match Excel files (.xlsx and .xls) that contain @#
|
| 681 |
+
patterns = [
|
| 682 |
+
os.path.join(directory, "*@#*.xlsx"),
|
| 683 |
+
os.path.join(directory, "*@#*.xls"),
|
| 684 |
+
]
|
| 685 |
+
|
| 686 |
+
# Process each pattern
|
| 687 |
+
for pattern in patterns:
|
| 688 |
+
files = glob.glob(pattern)
|
| 689 |
+
|
| 690 |
+
# Extracting the last portion after @# for each file
|
| 691 |
+
for file in files:
|
| 692 |
+
base_name = os.path.basename(file)
|
| 693 |
+
last_portion = base_name.split("@#")[-1]
|
| 694 |
+
last_portion = last_portion.replace(".xlsx", "").replace(
|
| 695 |
+
".xls", ""
|
| 696 |
+
) # Removing extensions
|
| 697 |
+
last_portions.append(last_portion)
|
| 698 |
+
|
| 699 |
+
return last_portions
|
| 700 |
+
|
| 701 |
+
|
| 702 |
+
def name_formating(channel_name):
|
| 703 |
+
# Replace underscores with spaces
|
| 704 |
+
name_mod = channel_name.replace("_", " ")
|
| 705 |
+
|
| 706 |
+
# Capitalize the first letter of each word
|
| 707 |
+
name_mod = name_mod.title()
|
| 708 |
+
|
| 709 |
+
return name_mod
|
| 710 |
+
|
| 711 |
+
|
| 712 |
+
@st.experimental_memo(show_spinner=False)
|
| 713 |
+
def panel_fetch(file_selected):
|
| 714 |
+
raw_data_mmm_df = pd.read_excel(file_selected, sheet_name="RAW DATA MMM")
|
| 715 |
+
|
| 716 |
+
# if "Panel" in raw_data_mmm_df.columns:
|
| 717 |
+
# panel = list(set(raw_data_mmm_df["Panel"]))
|
| 718 |
+
# else:
|
| 719 |
+
# raw_data_mmm_df = None
|
| 720 |
+
# panel = None
|
| 721 |
+
# raw_data_mmm_df = None
|
| 722 |
+
panel = None
|
| 723 |
+
return panel
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
def reset_inputs():
|
| 727 |
+
if "total_spends_change_abs" in st.session_state:
|
| 728 |
+
del st.session_state.total_spends_change_abs
|
| 729 |
+
if "total_spends_change" in st.session_state:
|
| 730 |
+
del st.session_state.total_spends_change
|
| 731 |
+
if "total_spends_change_abs_slider" in st.session_state:
|
| 732 |
+
del st.session_state.total_spends_change_abs_slider
|
| 733 |
+
|
| 734 |
+
if "total_sales_change_abs" in st.session_state:
|
| 735 |
+
del st.session_state.total_sales_change_abs
|
| 736 |
+
if "total_sales_change" in st.session_state:
|
| 737 |
+
del st.session_state.total_sales_change
|
| 738 |
+
if "total_sales_change_abs_slider" in st.session_state:
|
| 739 |
+
del st.session_state.total_sales_change_abs_slider
|
| 740 |
+
|
| 741 |
+
st.session_state["initialized"] = False
|
| 742 |
+
|
| 743 |
+
|
| 744 |
+
if auth_status == True:
|
| 745 |
+
authenticator.logout("Logout", "main")
|
| 746 |
+
st.header("Scenario Planner")
|
| 747 |
+
def scenario_planner_plots():
|
| 748 |
+
|
| 749 |
+
with st.expander('Optimized Spends Overview'):
|
| 750 |
+
# if st.button('Refresh'):
|
| 751 |
+
# st.experimental_rerun()
|
| 752 |
+
|
| 753 |
+
import plotly.graph_objects as go
|
| 754 |
+
from plotly.subplots import make_subplots
|
| 755 |
+
|
| 756 |
+
# Define light colors for bars
|
| 757 |
+
import plotly.graph_objects as go
|
| 758 |
+
from plotly.subplots import make_subplots
|
| 759 |
+
|
| 760 |
+
st.empty()
|
| 761 |
+
#st.header('Model Result Analysis')
|
| 762 |
+
spends_data=pd.read_excel('Overview_data_test.xlsx')
|
| 763 |
+
|
| 764 |
+
with open('summary_df.pkl', 'rb') as file:
|
| 765 |
+
summary_df_sorted = pickle.load(file)
|
| 766 |
+
#st.write(summary_df_sorted)
|
| 767 |
+
|
| 768 |
+
# selected_scenario= st.selectbox('Select Saved Scenarios',['S1','S2'])
|
| 769 |
+
summary_df_sorted=summary_df_sorted.sort_values(by=['Optimized_spend'],ascending=False)
|
| 770 |
+
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())
|
| 771 |
+
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())
|
| 772 |
+
|
| 773 |
+
summary_df_sorted['old_roi']=summary_df_sorted['Old_sales']/summary_df_sorted['Actual_spend']
|
| 774 |
+
summary_df_sorted['new_roi']=summary_df_sorted['New_sales']/summary_df_sorted['Optimized_spend']
|
| 775 |
+
|
| 776 |
+
total_actual_spend = summary_df_sorted['Actual_spend'].sum()
|
| 777 |
+
total_optimized_spend = summary_df_sorted['Optimized_spend'].sum()
|
| 778 |
+
|
| 779 |
+
actual_spend_percentage = (summary_df_sorted['Actual_spend'] / total_actual_spend) * 100
|
| 780 |
+
optimized_spend_percentage = (summary_df_sorted['Optimized_spend'] / total_optimized_spend) * 100
|
| 781 |
+
|
| 782 |
+
|
| 783 |
+
|
| 784 |
+
light_blue = 'rgba(0, 31, 120, 0.7)'
|
| 785 |
+
light_orange = 'rgba(0, 181, 219, 0.7)'
|
| 786 |
+
light_green = 'rgba(240, 61, 20, 0.7)'
|
| 787 |
+
light_red = 'rgba(250, 110, 10, 0.7)'
|
| 788 |
+
light_purple = 'rgba(255, 191, 69, 0.7)'
|
| 789 |
+
|
| 790 |
+
|
| 791 |
+
# # Create subplots with one row and two columns
|
| 792 |
+
# fig = make_subplots(rows=3, cols=1, subplot_titles=("Actual vs. Optimized Spend", "Actual vs. Optimized Contribution", "Actual vs. Optimized ROI"))
|
| 793 |
+
|
| 794 |
+
# # Add actual vs optimized spend bars
|
| 795 |
+
|
| 796 |
+
|
| 797 |
+
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Actual_spend'], name='Actual',
|
| 798 |
+
# text=summary_df_sorted['Actual_spend'].apply(format_number) + ' '+' (' + actual_spend_percentage.round(2).astype(str) + '%)',
|
| 799 |
+
# marker_color=light_blue, orientation='h'),
|
| 800 |
+
# row=1,
|
| 801 |
+
# col=1)
|
| 802 |
+
|
| 803 |
+
|
| 804 |
+
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Optimized_spend'], name='Optimized',
|
| 805 |
+
# text=summary_df_sorted['Optimized_spend'].apply(format_number) + ' (' + optimized_spend_percentage.round(2).astype(str) + '%)',
|
| 806 |
+
# marker_color=light_orange,
|
| 807 |
+
# orientation='h'),
|
| 808 |
+
# row=1,
|
| 809 |
+
# col=1)
|
| 810 |
+
|
| 811 |
+
# fig.update_xaxes(title_text="Amount", row=1, col=1)
|
| 812 |
+
|
| 813 |
+
# # Add actual vs optimized Contribution
|
| 814 |
+
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['New_sales'],
|
| 815 |
+
# name='Optimized Contribution',text=summary_df_sorted['New_sales'].apply(format_number),
|
| 816 |
+
# marker_color=light_orange, orientation='h',showlegend=False), row=2, col=1)
|
| 817 |
+
|
| 818 |
+
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['Old_sales'],
|
| 819 |
+
# name='Actual Contribution',text=summary_df_sorted['Old_sales'].apply(format_number),
|
| 820 |
+
# marker_color=light_blue, orientation='h',showlegend=False), row=2, col=1)
|
| 821 |
+
|
| 822 |
+
|
| 823 |
+
# fig.update_xaxes(title_text="Contribution", row=2, col=1)
|
| 824 |
+
|
| 825 |
+
# # Add actual vs optimized ROI bars
|
| 826 |
+
|
| 827 |
+
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['new_roi'],
|
| 828 |
+
# name='Optimized ROI',text=summary_df_sorted['new_roi'].apply(format_number) ,
|
| 829 |
+
# marker_color=light_orange, orientation='h',showlegend=False), row=3, col=1)
|
| 830 |
+
|
| 831 |
+
# fig.add_trace(go.Bar(y=summary_df_sorted['Channel_name'], x=summary_df_sorted['old_roi'],
|
| 832 |
+
# name='Actual ROI', text=summary_df_sorted['old_roi'].apply(format_number) ,
|
| 833 |
+
# marker_color=light_blue, orientation='h',showlegend=False), row=3, col=1)
|
| 834 |
+
|
| 835 |
+
# fig.update_xaxes(title_text="ROI", row=3, col=1)
|
| 836 |
+
|
| 837 |
+
# # Update layout
|
| 838 |
+
# fig.update_layout(title_text="Actual vs. Optimized Metrics for Media Channels",
|
| 839 |
+
# showlegend=True, yaxis=dict(title='Media Channels', autorange="reversed"))
|
| 840 |
+
|
| 841 |
+
# st.plotly_chart(fig,use_container_width=True)
|
| 842 |
+
|
| 843 |
+
# Create subplots with one row and two columns
|
| 844 |
+
fig = go.Figure()
|
| 845 |
+
# Add actual vs optimized spend bars
|
| 846 |
+
|
| 847 |
+
|
| 848 |
+
fig.add_trace(go.Bar(x=summary_df_sorted['Channel_name'], y=summary_df_sorted['Actual_spend'], name='Actual',
|
| 849 |
+
text=summary_df_sorted['Actual_spend'].apply(format_number) + ' '
|
| 850 |
+
# +
|
| 851 |
+
# ' '+
|
| 852 |
+
# '</br> (' + actual_spend_percentage.astype(int).astype(str) + '%)'
|
| 853 |
+
,textposition='outside',#textfont=dict(size=30),
|
| 854 |
+
marker_color=light_blue))
|
| 855 |
+
|
| 856 |
+
|
| 857 |
+
fig.add_trace(go.Bar(x=summary_df_sorted['Channel_name'], y=summary_df_sorted['Optimized_spend'], name='Optimized',
|
| 858 |
+
text=summary_df_sorted['Optimized_spend'].apply(format_number) + ' '
|
| 859 |
+
# +
|
| 860 |
+
# '</br> (' + optimized_spend_percentage.astype(int).astype(str) + '%)'
|
| 861 |
+
,textposition='outside',#textfont=dict(size=30),
|
| 862 |
+
marker_color=light_orange))
|
| 863 |
+
|
| 864 |
+
fig.update_xaxes(title_text="Channels")
|
| 865 |
+
fig.update_yaxes(title_text="Spends ($)")
|
| 866 |
+
fig.update_layout(
|
| 867 |
+
title = "Actual vs. Optimized Spends",
|
| 868 |
+
margin=dict(t=40, b=40, l=40, r=40)
|
| 869 |
+
)
|
| 870 |
+
|
| 871 |
+
st.plotly_chart(fig,use_container_width=True)
|
| 872 |
+
|
| 873 |
+
# Add actual vs optimized Contribution
|
| 874 |
+
fig = go.Figure()
|
| 875 |
+
fig.add_trace(go.Bar(x=summary_df_sorted['Channel_name'], y=summary_df_sorted['Old_sales'],
|
| 876 |
+
name='Actual Contribution',text=summary_df_sorted['Old_sales'].apply(format_number),textposition='outside',
|
| 877 |
+
marker_color=light_blue,showlegend=True))
|
| 878 |
+
|
| 879 |
+
fig.add_trace(go.Bar(x=summary_df_sorted['Channel_name'], y=summary_df_sorted['New_sales'],
|
| 880 |
+
name='Optimized Contribution',text=summary_df_sorted['New_sales'].apply(format_number),textposition='outside',
|
| 881 |
+
marker_color=light_orange, showlegend=True))
|
| 882 |
+
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
fig.update_yaxes(title_text="Contribution")
|
| 886 |
+
fig.update_xaxes(title_text="Channels")
|
| 887 |
+
fig.update_layout(
|
| 888 |
+
title = "Actual vs. Optimized Contributions",
|
| 889 |
+
margin=dict(t=40, b=40, l=40, r=40)
|
| 890 |
+
# yaxis=dict(range=[0, 0.002]),
|
| 891 |
+
)
|
| 892 |
+
st.plotly_chart(fig,use_container_width=True)
|
| 893 |
+
|
| 894 |
+
# Add actual vs optimized Efficiency bars
|
| 895 |
+
fig = go.Figure()
|
| 896 |
+
summary_df_sorted_p = summary_df_sorted[summary_df_sorted['Channel_name']!="Panel"]
|
| 897 |
+
fig.add_trace(go.Bar(x=summary_df_sorted_p['Channel_name'], y=summary_df_sorted_p['old_efficiency'],
|
| 898 |
+
name='Actual Efficiency', text=summary_df_sorted_p['old_efficiency'].apply(format_number) ,textposition='outside',
|
| 899 |
+
marker_color=light_blue,showlegend=True))
|
| 900 |
+
fig.add_trace(go.Bar(x=summary_df_sorted_p['Channel_name'], y=summary_df_sorted_p['new_efficiency'],
|
| 901 |
+
name='Optimized Efficiency',text=summary_df_sorted_p['new_efficiency'].apply(format_number),textposition='outside' ,
|
| 902 |
+
marker_color=light_orange,showlegend=True))
|
| 903 |
+
|
| 904 |
+
fig.update_xaxes(title_text="Channels")
|
| 905 |
+
fig.update_yaxes(title_text="ROI")
|
| 906 |
+
fig.update_layout(
|
| 907 |
+
title = "Actual vs. Optimized ROI",
|
| 908 |
+
margin=dict(t=40, b=40, l=40, r=40),
|
| 909 |
+
# yaxis=dict(range=[0, 0.002]),
|
| 910 |
+
)
|
| 911 |
+
|
| 912 |
+
st.plotly_chart(fig,use_container_width=True)
|
| 913 |
+
|
| 914 |
+
|
| 915 |
+
# Response Metrics
|
| 916 |
+
directory = "metrics_level_data"
|
| 917 |
+
metrics_list = get_excel_names(directory)
|
| 918 |
+
|
| 919 |
+
# metrics_selected = col1.selectbox(
|
| 920 |
+
# "Response Metrics",
|
| 921 |
+
# metrics_list,
|
| 922 |
+
# format_func=name_formating,
|
| 923 |
+
# index=0,
|
| 924 |
+
# on_change=reset_inputs,
|
| 925 |
+
# )
|
| 926 |
+
|
| 927 |
+
metrics_selected='prospects'
|
| 928 |
+
# Target
|
| 929 |
+
target = name_formating(metrics_selected)
|
| 930 |
+
|
| 931 |
+
file_selected = (
|
| 932 |
+
f"Overview_data_test_panel@#{metrics_selected}.xlsx"
|
| 933 |
+
)
|
| 934 |
+
|
| 935 |
+
# Panel List
|
| 936 |
+
panel_list = panel_fetch(file_selected)
|
| 937 |
+
|
| 938 |
+
# # Panel Selected
|
| 939 |
+
# panel_selected = st.selectbox(
|
| 940 |
+
# "Markets",
|
| 941 |
+
# ["Total Market"] + panel_list,
|
| 942 |
+
# index=0,
|
| 943 |
+
# on_change=reset_inputs,
|
| 944 |
+
# )
|
| 945 |
+
|
| 946 |
+
# st.write(panel_selected)
|
| 947 |
+
panel_selected = "Total Market"
|
| 948 |
+
st.session_state['selected_markets']=panel_selected
|
| 949 |
+
|
| 950 |
+
if "update_rcs" in st.session_state:
|
| 951 |
+
updated_rcs = st.session_state["update_rcs"]
|
| 952 |
+
else:
|
| 953 |
+
updated_rcs = None
|
| 954 |
+
|
| 955 |
+
if "first_time" not in st.session_state:
|
| 956 |
+
st.session_state["first_time"] = True
|
| 957 |
+
|
| 958 |
+
# Check if state is initiaized
|
| 959 |
+
is_state_initiaized = st.session_state.get("initialized", False)
|
| 960 |
+
if not is_state_initiaized or st.session_state["first_time"]:
|
| 961 |
+
# initialize_data()
|
| 962 |
+
if panel_selected == "Total Market":
|
| 963 |
+
initialize_data(
|
| 964 |
+
panel=panel_selected,
|
| 965 |
+
target_file=file_selected,
|
| 966 |
+
updated_rcs=updated_rcs,
|
| 967 |
+
metrics=metrics_selected,
|
| 968 |
+
)
|
| 969 |
+
panel = None
|
| 970 |
+
else:
|
| 971 |
+
initialize_data(
|
| 972 |
+
panel=panel_selected,
|
| 973 |
+
target_file=file_selected,
|
| 974 |
+
updated_rcs=updated_rcs,
|
| 975 |
+
metrics=metrics_selected,
|
| 976 |
+
)
|
| 977 |
+
st.session_state["initialized"] = True
|
| 978 |
+
st.session_state["first_time"] = False
|
| 979 |
+
|
| 980 |
+
# initialize_data(
|
| 981 |
+
# panel=panel_selected,
|
| 982 |
+
# target_file=file_selected,
|
| 983 |
+
# updated_rcs=updated_rcs,
|
| 984 |
+
# metrics=metrics_selected,
|
| 985 |
+
# )
|
| 986 |
+
# st.session_state["initialized"] = True
|
| 987 |
+
# st.session_state["first_time"] = False
|
| 988 |
+
|
| 989 |
+
# Channels List
|
| 990 |
+
channels_list = st.session_state["channels_list"]
|
| 991 |
+
|
| 992 |
+
# ======================================================== #
|
| 993 |
+
# ========================== UI ========================== #
|
| 994 |
+
# ======================================================== #
|
| 995 |
+
|
| 996 |
+
# print(list(st.session_state.keys()))
|
| 997 |
+
main_header = st.columns((2, 2))
|
| 998 |
+
sub_header = st.columns((1, 1, 1, 1))
|
| 999 |
+
_scenario = st.session_state["scenario"]
|
| 1000 |
+
|
| 1001 |
+
if "total_spends_change" not in st.session_state:
|
| 1002 |
+
st.session_state.total_spends_change = 0
|
| 1003 |
+
|
| 1004 |
+
if "total_sales_change" not in st.session_state:
|
| 1005 |
+
st.session_state.total_sales_change = 0
|
| 1006 |
+
|
| 1007 |
+
if "total_spends_change_abs" not in st.session_state:
|
| 1008 |
+
st.session_state["total_spends_change_abs"] = numerize(
|
| 1009 |
+
_scenario.actual_total_spends, 1
|
| 1010 |
+
)
|
| 1011 |
+
|
| 1012 |
+
if "total_sales_change_abs" not in st.session_state:
|
| 1013 |
+
st.session_state["total_sales_change_abs"] = numerize(
|
| 1014 |
+
_scenario.actual_total_sales, 1
|
| 1015 |
+
)
|
| 1016 |
+
|
| 1017 |
+
if "total_spends_change_abs_slider" not in st.session_state:
|
| 1018 |
+
st.session_state.total_spends_change_abs_slider = numerize(
|
| 1019 |
+
_scenario.actual_total_spends, 1
|
| 1020 |
+
)
|
| 1021 |
+
|
| 1022 |
+
if "total_sales_change_abs_slider" not in st.session_state:
|
| 1023 |
+
st.session_state.total_sales_change_abs_slider = numerize(
|
| 1024 |
+
_scenario.actual_total_sales, 1
|
| 1025 |
+
)
|
| 1026 |
+
|
| 1027 |
+
with main_header[0]:
|
| 1028 |
+
st.subheader("Actual")
|
| 1029 |
+
|
| 1030 |
+
with main_header[-1]:
|
| 1031 |
+
st.subheader("Simulated")
|
| 1032 |
+
|
| 1033 |
+
with sub_header[0]:
|
| 1034 |
+
st.metric(label="Spends", value=format_numbers(_scenario.actual_total_spends))
|
| 1035 |
+
|
| 1036 |
+
with sub_header[1]:
|
| 1037 |
+
st.metric(
|
| 1038 |
+
label=target,
|
| 1039 |
+
value=format_numbers_f(
|
| 1040 |
+
float(_scenario.actual_total_sales)
|
| 1041 |
+
),
|
| 1042 |
+
)
|
| 1043 |
+
|
| 1044 |
+
with sub_header[2]:
|
| 1045 |
+
st.metric(
|
| 1046 |
+
label="Spends",
|
| 1047 |
+
value=format_numbers(_scenario.modified_total_spends),
|
| 1048 |
+
delta=numerize(_scenario.delta_spends, 1),
|
| 1049 |
+
)
|
| 1050 |
+
|
| 1051 |
+
with sub_header[3]:
|
| 1052 |
+
st.metric(
|
| 1053 |
+
label=target,
|
| 1054 |
+
value=format_numbers_f(
|
| 1055 |
+
float(_scenario.modified_total_sales)
|
| 1056 |
+
),
|
| 1057 |
+
delta=numerize(_scenario.delta_sales, 1),
|
| 1058 |
+
)
|
| 1059 |
+
|
| 1060 |
+
with st.expander("Channel Spends Simulator", expanded=True):
|
| 1061 |
+
_columns1 = st.columns((2, 2, 1, 1))
|
| 1062 |
+
with _columns1[0]:
|
| 1063 |
+
optimization_selection = st.selectbox(
|
| 1064 |
+
"Optimize", options=["Media Spends", target], key="optimization_key"
|
| 1065 |
+
)
|
| 1066 |
+
|
| 1067 |
+
with _columns1[1]:
|
| 1068 |
+
st.markdown("#")
|
| 1069 |
+
# if st.checkbox(
|
| 1070 |
+
# label="Optimize all Channels",
|
| 1071 |
+
# key="optimze_all_channels",
|
| 1072 |
+
# value=False,
|
| 1073 |
+
# # on_change=select_all_channels_for_optimization,
|
| 1074 |
+
# ):
|
| 1075 |
+
# select_all_channels_for_optimization()
|
| 1076 |
+
|
| 1077 |
+
st.checkbox(
|
| 1078 |
+
label="Optimize all Channels",
|
| 1079 |
+
key="optimze_all_channels",
|
| 1080 |
+
value=False,
|
| 1081 |
+
on_change=select_all_channels_for_optimization,
|
| 1082 |
+
)
|
| 1083 |
+
|
| 1084 |
+
with _columns1[2]:
|
| 1085 |
+
st.markdown("#")
|
| 1086 |
+
# st.button(
|
| 1087 |
+
# "Optimize",
|
| 1088 |
+
# on_click=optimize,
|
| 1089 |
+
# args=(st.session_state["optimization_key"]),
|
| 1090 |
+
# use_container_width=True,
|
| 1091 |
+
# )
|
| 1092 |
+
|
| 1093 |
+
optimize_placeholder = st.empty()
|
| 1094 |
+
|
| 1095 |
+
with _columns1[3]:
|
| 1096 |
+
st.markdown("#")
|
| 1097 |
+
st.button(
|
| 1098 |
+
"Reset",
|
| 1099 |
+
on_click=reset_scenario,
|
| 1100 |
+
args=(panel_selected, file_selected, updated_rcs),
|
| 1101 |
+
# use_container_width=True,
|
| 1102 |
+
)
|
| 1103 |
+
# st.write(target)
|
| 1104 |
+
|
| 1105 |
+
|
| 1106 |
+
_columns2 = st.columns((2, 2, 2))
|
| 1107 |
+
if st.session_state["optimization_key"] == "Media Spends":
|
| 1108 |
+
with _columns2[0]:
|
| 1109 |
+
spend_input = st.text_input(
|
| 1110 |
+
"Absolute",
|
| 1111 |
+
key="total_spends_change_abs",
|
| 1112 |
+
# label_visibility="collapsed",
|
| 1113 |
+
on_change=update_all_spends_abs,
|
| 1114 |
+
)
|
| 1115 |
+
|
| 1116 |
+
with _columns2[1]:
|
| 1117 |
+
st.number_input(
|
| 1118 |
+
"Percent Change",
|
| 1119 |
+
key="total_spends_change",
|
| 1120 |
+
min_value=-50,
|
| 1121 |
+
max_value=50,
|
| 1122 |
+
step=1,
|
| 1123 |
+
on_change=update_spends,
|
| 1124 |
+
)
|
| 1125 |
+
|
| 1126 |
+
with _columns2[2]:
|
| 1127 |
+
min_value = round(_scenario.actual_total_spends * 0.5)
|
| 1128 |
+
max_value = round(_scenario.actual_total_spends * 1.5)
|
| 1129 |
+
st.session_state["total_spends_change_abs_slider_options"] = [
|
| 1130 |
+
numerize(value, 1)
|
| 1131 |
+
for value in range(min_value, max_value + 1, int(1e4))
|
| 1132 |
+
]
|
| 1133 |
+
|
| 1134 |
+
# st.select_slider(
|
| 1135 |
+
# "Absolute Slider",
|
| 1136 |
+
# options=st.session_state["total_spends_change_abs_slider_options"],
|
| 1137 |
+
# key="total_spends_change_abs_slider",
|
| 1138 |
+
# on_change=update_all_spends_abs_slider,
|
| 1139 |
+
# )
|
| 1140 |
+
|
| 1141 |
+
elif st.session_state["optimization_key"] == target:
|
| 1142 |
+
# st.write(target)
|
| 1143 |
+
with _columns2[0]:
|
| 1144 |
+
sales_input = st.text_input(
|
| 1145 |
+
"Absolute",
|
| 1146 |
+
key="total_sales_change_abs",
|
| 1147 |
+
on_change=update_sales_abs,
|
| 1148 |
+
)
|
| 1149 |
+
|
| 1150 |
+
with _columns2[1]:
|
| 1151 |
+
st.number_input(
|
| 1152 |
+
"Percent Change",
|
| 1153 |
+
key="total_sales_change",
|
| 1154 |
+
min_value=-50,
|
| 1155 |
+
max_value=50,
|
| 1156 |
+
step=1,
|
| 1157 |
+
on_change=update_sales,
|
| 1158 |
+
)
|
| 1159 |
+
with _columns2[2]:
|
| 1160 |
+
min_value = round(_scenario.actual_total_sales * 0.5)
|
| 1161 |
+
max_value = round(_scenario.actual_total_sales * 1.5)
|
| 1162 |
+
st.write(min_value)
|
| 1163 |
+
st.write(max_value)
|
| 1164 |
+
# for value in range(min_value, max_value + 1, int(100)):
|
| 1165 |
+
# st.write(numerize(value, 1))
|
| 1166 |
+
st.session_state["total_sales_change_abs_slider_options"] = [
|
| 1167 |
+
numerize(value, 1)
|
| 1168 |
+
for value in range(min_value, max_value + 1, int(100))
|
| 1169 |
+
]
|
| 1170 |
+
|
| 1171 |
+
st.select_slider(
|
| 1172 |
+
"Absolute Slider",
|
| 1173 |
+
options=st.session_state["total_sales_change_abs_slider_options"],
|
| 1174 |
+
key="total_sales_change_abs_slider",
|
| 1175 |
+
on_change=update_sales_abs_slider,
|
| 1176 |
+
# value=numerize(min_value, 1)
|
| 1177 |
+
)
|
| 1178 |
+
|
| 1179 |
+
if (
|
| 1180 |
+
not st.session_state["allow_sales_update"]
|
| 1181 |
+
and optimization_selection == target
|
| 1182 |
+
):
|
| 1183 |
+
st.warning("Invalid Input")
|
| 1184 |
+
|
| 1185 |
+
if (
|
| 1186 |
+
not st.session_state["allow_spends_update"]
|
| 1187 |
+
and optimization_selection == "Media Spends"
|
| 1188 |
+
):
|
| 1189 |
+
st.warning("Invalid Input")
|
| 1190 |
+
|
| 1191 |
+
status_placeholder = st.empty()
|
| 1192 |
+
|
| 1193 |
+
# if optimize_placeholder.button("Optimize", use_container_width=True):
|
| 1194 |
+
# optimize(st.session_state["optimization_key"], status_placeholder)
|
| 1195 |
+
# st.rerun()
|
| 1196 |
+
|
| 1197 |
+
optimize_placeholder.button(
|
| 1198 |
+
"Optimize",
|
| 1199 |
+
on_click=optimize,
|
| 1200 |
+
args=(st.session_state["optimization_key"], status_placeholder),
|
| 1201 |
+
# use_container_width=True,
|
| 1202 |
+
)
|
| 1203 |
+
|
| 1204 |
+
st.markdown("""<hr class="spends-heading-seperator">""", unsafe_allow_html=True)
|
| 1205 |
+
_columns = st.columns((2.5, 2, 1.5, 1.5, 1))
|
| 1206 |
+
with _columns[0]:
|
| 1207 |
+
generate_spending_header("Channel")
|
| 1208 |
+
with _columns[1]:
|
| 1209 |
+
generate_spending_header("Spends Input")
|
| 1210 |
+
with _columns[2]:
|
| 1211 |
+
generate_spending_header("Spends")
|
| 1212 |
+
with _columns[3]:
|
| 1213 |
+
generate_spending_header(target)
|
| 1214 |
+
with _columns[4]:
|
| 1215 |
+
generate_spending_header("Optimize")
|
| 1216 |
+
|
| 1217 |
+
st.markdown("""<hr class="spends-heading-seperator">""", unsafe_allow_html=True)
|
| 1218 |
+
|
| 1219 |
+
if "acutual_predicted" not in st.session_state:
|
| 1220 |
+
st.session_state["acutual_predicted"] = {
|
| 1221 |
+
"Channel_name": [],
|
| 1222 |
+
"Actual_spend": [],
|
| 1223 |
+
"Optimized_spend": [],
|
| 1224 |
+
"Delta": [],
|
| 1225 |
+
"New_sales":[],
|
| 1226 |
+
"Old_sales":[]
|
| 1227 |
+
}
|
| 1228 |
+
for i, channel_name in enumerate(channels_list):
|
| 1229 |
+
# st.write(channel_name)
|
| 1230 |
+
_channel_class = st.session_state["scenario"].channels[channel_name]
|
| 1231 |
+
_columns = st.columns((2.5, 1.5, 1.5, 1.5, 1))
|
| 1232 |
+
with _columns[0]:
|
| 1233 |
+
st.write(channel_name_formating(channel_name))
|
| 1234 |
+
bin_placeholder = st.container()
|
| 1235 |
+
|
| 1236 |
+
with _columns[1]:
|
| 1237 |
+
channel_bounds = _channel_class.bounds
|
| 1238 |
+
channel_spends = float(_channel_class.actual_total_spends)
|
| 1239 |
+
min_value = float((1 + channel_bounds[0] / 100) * channel_spends)
|
| 1240 |
+
max_value = float((1 + channel_bounds[1] / 100) * channel_spends)
|
| 1241 |
+
##print(st.session_state[channel_name])
|
| 1242 |
+
spend_input = st.text_input(
|
| 1243 |
+
channel_name,
|
| 1244 |
+
key=channel_name,
|
| 1245 |
+
label_visibility="collapsed",
|
| 1246 |
+
on_change=partial(update_data, channel_name),
|
| 1247 |
+
)
|
| 1248 |
+
if not validate_input(spend_input):
|
| 1249 |
+
st.error("Invalid input")
|
| 1250 |
+
|
| 1251 |
+
channel_name_current = f"{channel_name}_change"
|
| 1252 |
+
|
| 1253 |
+
st.number_input(
|
| 1254 |
+
"Percent Change",
|
| 1255 |
+
key=channel_name_current,
|
| 1256 |
+
step=1,
|
| 1257 |
+
on_change=partial(update_data_by_percent, channel_name),
|
| 1258 |
+
)
|
| 1259 |
+
|
| 1260 |
+
with _columns[2]:
|
| 1261 |
+
# spends
|
| 1262 |
+
current_channel_spends = float(
|
| 1263 |
+
_channel_class.modified_total_spends
|
| 1264 |
+
* _channel_class.conversion_rate
|
| 1265 |
+
)
|
| 1266 |
+
actual_channel_spends = float(
|
| 1267 |
+
_channel_class.actual_total_spends * _channel_class.conversion_rate
|
| 1268 |
+
)
|
| 1269 |
+
spends_delta = float(
|
| 1270 |
+
_channel_class.delta_spends * _channel_class.conversion_rate
|
| 1271 |
+
)
|
| 1272 |
+
st.session_state["acutual_predicted"]["Channel_name"].append(
|
| 1273 |
+
channel_name
|
| 1274 |
+
)
|
| 1275 |
+
st.session_state["acutual_predicted"]["Actual_spend"].append(
|
| 1276 |
+
actual_channel_spends
|
| 1277 |
+
)
|
| 1278 |
+
st.session_state["acutual_predicted"]["Optimized_spend"].append(
|
| 1279 |
+
current_channel_spends
|
| 1280 |
+
)
|
| 1281 |
+
st.session_state["acutual_predicted"]["Delta"].append(spends_delta)
|
| 1282 |
+
## REMOVE
|
| 1283 |
+
st.metric(
|
| 1284 |
+
"Spends",
|
| 1285 |
+
format_numbers(current_channel_spends),
|
| 1286 |
+
delta=numerize(spends_delta, 1),
|
| 1287 |
+
label_visibility="collapsed",
|
| 1288 |
+
)
|
| 1289 |
+
|
| 1290 |
+
with _columns[3]:
|
| 1291 |
+
# sales
|
| 1292 |
+
current_channel_sales = float(_channel_class.modified_total_sales)
|
| 1293 |
+
actual_channel_sales = float(_channel_class.actual_total_sales)
|
| 1294 |
+
sales_delta = float(_channel_class.delta_sales)
|
| 1295 |
+
st.session_state["acutual_predicted"]["Old_sales"].append(actual_channel_sales)
|
| 1296 |
+
st.session_state["acutual_predicted"]["New_sales"].append(current_channel_sales)
|
| 1297 |
+
#st.write(actual_channel_sales)
|
| 1298 |
+
|
| 1299 |
+
st.metric(
|
| 1300 |
+
target,
|
| 1301 |
+
format_numbers_f(current_channel_sales),
|
| 1302 |
+
delta=numerize(sales_delta, 1),
|
| 1303 |
+
label_visibility="collapsed",
|
| 1304 |
+
)
|
| 1305 |
+
|
| 1306 |
+
with _columns[4]:
|
| 1307 |
+
|
| 1308 |
+
# if st.checkbox(
|
| 1309 |
+
# label="select for optimization",
|
| 1310 |
+
# key=f"{channel_name}_selected",
|
| 1311 |
+
# value=False,
|
| 1312 |
+
# # on_change=partial(select_channel_for_optimization, channel_name),
|
| 1313 |
+
# label_visibility="collapsed",
|
| 1314 |
+
# ):
|
| 1315 |
+
# select_channel_for_optimization(channel_name)
|
| 1316 |
+
|
| 1317 |
+
st.checkbox(
|
| 1318 |
+
label="select for optimization",
|
| 1319 |
+
key=f"{channel_name}_selected",
|
| 1320 |
+
value=False,
|
| 1321 |
+
on_change=partial(select_channel_for_optimization, channel_name),
|
| 1322 |
+
label_visibility="collapsed",
|
| 1323 |
+
)
|
| 1324 |
+
|
| 1325 |
+
st.markdown(
|
| 1326 |
+
"""<hr class="spends-child-seperator">""",
|
| 1327 |
+
unsafe_allow_html=True,
|
| 1328 |
+
)
|
| 1329 |
+
|
| 1330 |
+
# Bins
|
| 1331 |
+
col = channels_list[i]
|
| 1332 |
+
x_actual = st.session_state["scenario"].channels[col].actual_spends
|
| 1333 |
+
x_modified = st.session_state["scenario"].channels[col].modified_spends
|
| 1334 |
+
# x_modified_total = 0
|
| 1335 |
+
# for c in channels_list:
|
| 1336 |
+
# # st.write(c)
|
| 1337 |
+
# # st.write(st.session_state["scenario"].channels[c].modified_spends)
|
| 1338 |
+
# x_modified_total = x_modified_total + st.session_state["scenario"].channels[c].modified_spends.sum()
|
| 1339 |
+
# st.write(x_modified_total)
|
| 1340 |
+
|
| 1341 |
+
x_total = x_modified.sum()
|
| 1342 |
+
power = np.ceil(np.log(x_actual.max()) / np.log(10)) - 3
|
| 1343 |
+
|
| 1344 |
+
updated_rcs_key = f"{metrics_selected}#@{panel_selected}#@{channel_name}"
|
| 1345 |
+
|
| 1346 |
+
if updated_rcs and updated_rcs_key in list(updated_rcs.keys()):
|
| 1347 |
+
K = updated_rcs[updated_rcs_key]["K"]
|
| 1348 |
+
b = updated_rcs[updated_rcs_key]["b"]
|
| 1349 |
+
a = updated_rcs[updated_rcs_key]["a"]
|
| 1350 |
+
x0 = updated_rcs[updated_rcs_key]["x0"]
|
| 1351 |
+
else:
|
| 1352 |
+
K = st.session_state["rcs"][col]["K"]
|
| 1353 |
+
b = st.session_state["rcs"][col]["b"]
|
| 1354 |
+
a = st.session_state["rcs"][col]["a"]
|
| 1355 |
+
x0 = st.session_state["rcs"][col]["x0"]
|
| 1356 |
+
|
| 1357 |
+
x_plot = np.linspace(0, 5 * x_actual.sum(), 200)
|
| 1358 |
+
|
| 1359 |
+
# Append current_channel_spends to the end of x_plot
|
| 1360 |
+
x_plot = np.append(x_plot, current_channel_spends)
|
| 1361 |
+
|
| 1362 |
+
x, y, marginal_roi = [], [], []
|
| 1363 |
+
for x_p in x_plot:
|
| 1364 |
+
x.append(x_p * x_actual / x_actual.sum())
|
| 1365 |
+
|
| 1366 |
+
for index in range(len(x_plot)):
|
| 1367 |
+
y.append(s_curve(x[index] / 10**power, K, b, a, x0))
|
| 1368 |
+
|
| 1369 |
+
for index in range(len(x_plot)):
|
| 1370 |
+
marginal_roi.append(
|
| 1371 |
+
a * y[index] * (1 - y[index] / np.maximum(K, np.finfo(float).eps))
|
| 1372 |
+
)
|
| 1373 |
+
|
| 1374 |
+
x = (
|
| 1375 |
+
np.sum(x, axis=1)
|
| 1376 |
+
* st.session_state["scenario"].channels[col].conversion_rate
|
| 1377 |
+
)
|
| 1378 |
+
y = np.sum(y, axis=1)
|
| 1379 |
+
marginal_roi = (
|
| 1380 |
+
np.average(marginal_roi, axis=1)
|
| 1381 |
+
/ st.session_state["scenario"].channels[col].conversion_rate
|
| 1382 |
+
)
|
| 1383 |
+
|
| 1384 |
+
roi = y / np.maximum(x, np.finfo(float).eps)
|
| 1385 |
+
# roi = (y/np.sum(y))/(x/np.sum(x))
|
| 1386 |
+
# st.write(x)
|
| 1387 |
+
# st.write(y)
|
| 1388 |
+
# st.write(roi)
|
| 1389 |
+
|
| 1390 |
+
# st.write(roi[-1])
|
| 1391 |
+
|
| 1392 |
+
roi_current, marginal_roi_current = roi[-1], marginal_roi[-1]
|
| 1393 |
+
x, y, roi, marginal_roi = (
|
| 1394 |
+
x[:-1],
|
| 1395 |
+
y[:-1],
|
| 1396 |
+
roi[:-1],
|
| 1397 |
+
marginal_roi[:-1],
|
| 1398 |
+
) # Drop data for current spends
|
| 1399 |
+
|
| 1400 |
+
# roi_current =
|
| 1401 |
+
|
| 1402 |
+
start_value, end_value, left_value, right_value = find_segment_value(
|
| 1403 |
+
x,
|
| 1404 |
+
roi,
|
| 1405 |
+
marginal_roi,
|
| 1406 |
+
)
|
| 1407 |
+
|
| 1408 |
+
#st.write(roi_current)
|
| 1409 |
+
|
| 1410 |
+
rgba = calculate_rgba(
|
| 1411 |
+
start_value,
|
| 1412 |
+
end_value,
|
| 1413 |
+
left_value,
|
| 1414 |
+
right_value,
|
| 1415 |
+
current_channel_spends,
|
| 1416 |
+
)
|
| 1417 |
+
|
| 1418 |
+
summary_df = pd.DataFrame(st.session_state["acutual_predicted"])
|
| 1419 |
+
# st.dataframe(summary_df)
|
| 1420 |
+
summary_df.drop_duplicates(subset="Channel_name", keep="last", inplace=True)
|
| 1421 |
+
# st.dataframe(summary_df)
|
| 1422 |
+
|
| 1423 |
+
summary_df_sorted = summary_df.sort_values(by="Delta", ascending=False)
|
| 1424 |
+
summary_df_sorted["Delta_percent"] = np.round(
|
| 1425 |
+
((summary_df_sorted["Optimized_spend"] / summary_df_sorted["Actual_spend"]) - 1)
|
| 1426 |
+
* 100,
|
| 1427 |
+
2,
|
| 1428 |
+
)
|
| 1429 |
+
|
| 1430 |
+
summary_df_sorted=summary_df_sorted.sort_values(by=['Optimized_spend'],ascending=False)
|
| 1431 |
+
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())
|
| 1432 |
+
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())
|
| 1433 |
+
|
| 1434 |
+
a = (summary_df_sorted[summary_df_sorted['Channel_name']== col]).reset_index()['new_efficiency'][0]
|
| 1435 |
+
# st.write(a)
|
| 1436 |
+
|
| 1437 |
+
with bin_placeholder:
|
| 1438 |
+
if a> 1:
|
| 1439 |
+
fill_color_box = "#98fb98"
|
| 1440 |
+
elif a <1:
|
| 1441 |
+
fill_color_box = "#ff6868"
|
| 1442 |
+
else:
|
| 1443 |
+
fill_color_box = "#ff6868"
|
| 1444 |
+
st.markdown(
|
| 1445 |
+
f"""
|
| 1446 |
+
<div style="
|
| 1447 |
+
border-radius: 12px;
|
| 1448 |
+
background-color: {fill_color_box};
|
| 1449 |
+
padding: 10px;
|
| 1450 |
+
text-align: center;
|
| 1451 |
+
color: {'black'};
|
| 1452 |
+
">
|
| 1453 |
+
<p style="margin: 0; font-size: 20px;">Efficiency: {round(a,2)}</p>
|
| 1454 |
+
<!--<p style="margin: 0; font-size: 20px;">Marginal ROI: {round(marginal_roi_current,1)}</p>-->
|
| 1455 |
+
</div>
|
| 1456 |
+
""",
|
| 1457 |
+
unsafe_allow_html=True,
|
| 1458 |
+
)
|
| 1459 |
+
|
| 1460 |
+
with st.expander("See Response Curves", expanded=True):
|
| 1461 |
+
fig = plot_response_curves(summary_df_sorted)
|
| 1462 |
+
# st.plotly_chart(rc.response_curves(col))
|
| 1463 |
+
# st.plotly_chart(fig, use_container_width=True)
|
| 1464 |
+
|
| 1465 |
+
summary_df = pd.DataFrame(st.session_state["acutual_predicted"])
|
| 1466 |
+
# st.dataframe(summary_df)
|
| 1467 |
+
summary_df.drop_duplicates(subset="Channel_name", keep="last", inplace=True)
|
| 1468 |
+
# st.dataframe(summary_df)
|
| 1469 |
+
|
| 1470 |
+
summary_df_sorted = summary_df.sort_values(by="Delta", ascending=False)
|
| 1471 |
+
summary_df_sorted["Delta_percent"] = np.round(
|
| 1472 |
+
((summary_df_sorted["Optimized_spend"] / summary_df_sorted["Actual_spend"]) - 1)
|
| 1473 |
+
* 100,
|
| 1474 |
+
2,
|
| 1475 |
+
)
|
| 1476 |
+
|
| 1477 |
+
|
| 1478 |
+
|
| 1479 |
+
|
| 1480 |
+
|
| 1481 |
+
with open("summary_df.pkl", "wb") as f:
|
| 1482 |
+
pickle.dump(summary_df_sorted, f)
|
| 1483 |
+
# st.dataframe(summary_df_sorted)
|
| 1484 |
+
# ___columns=st.columns(3)
|
| 1485 |
+
# with ___columns[2]:
|
| 1486 |
+
# fig=summary_plot(summary_df_sorted, x='Delta_percent', y='Channel_name', title='Delta', text_column='Delta_percent')
|
| 1487 |
+
# st.plotly_chart(fig,use_container_width=True)
|
| 1488 |
+
# with ___columns[0]:
|
| 1489 |
+
# fig=summary_plot(summary_df_sorted, x='Actual_spend', y='Channel_name', title='Actual Spend', text_column='Actual_spend')
|
| 1490 |
+
# st.plotly_chart(fig,use_container_width=True)
|
| 1491 |
+
# with ___columns[1]:
|
| 1492 |
+
# fig=summary_plot(summary_df_sorted, x='Optimized_spend', y='Channel_name', title='Planned Spend', text_column='Optimized_spend')
|
| 1493 |
+
# st.plotly_chart(fig,use_container_width=True)
|
| 1494 |
+
|
| 1495 |
+
scenario_planner_plots()
|
| 1496 |
+
|
| 1497 |
+
_columns = st.columns(2)
|
| 1498 |
+
# with _columns[0]:
|
| 1499 |
+
st.subheader("Save Scenario")
|
| 1500 |
+
scenario_name = st.text_input(
|
| 1501 |
+
"Scenario name",
|
| 1502 |
+
key="scenario_input",
|
| 1503 |
+
placeholder="Scenario name",
|
| 1504 |
+
label_visibility="collapsed",
|
| 1505 |
+
)
|
| 1506 |
+
st.button(
|
| 1507 |
+
"Save",
|
| 1508 |
+
on_click=lambda: save_scenario(scenario_name),
|
| 1509 |
+
disabled=len(st.session_state["scenario_input"]) == 0,#use_container_width=True
|
| 1510 |
+
)
|
| 1511 |
+
|
| 1512 |
+
|
| 1513 |
+
|
| 1514 |
+
elif auth_status == False:
|
| 1515 |
+
st.error("Username/Password is incorrect")
|
| 1516 |
+
|
| 1517 |
+
if auth_status != True:
|
| 1518 |
+
try:
|
| 1519 |
+
username_forgot_pw, email_forgot_password, random_password = (
|
| 1520 |
+
authenticator.forgot_password("Forgot password")
|
| 1521 |
+
)
|
| 1522 |
+
if username_forgot_pw:
|
| 1523 |
+
st.session_state["config"]["credentials"]["usernames"][username_forgot_pw][
|
| 1524 |
+
"password"
|
| 1525 |
+
] = stauth.Hasher([random_password]).generate()[0]
|
| 1526 |
+
send_email(email_forgot_password, random_password)
|
| 1527 |
+
st.success("New password sent securely")
|
| 1528 |
+
# Random password to be transferred to user securely
|
| 1529 |
+
elif username_forgot_pw == False:
|
| 1530 |
+
st.error("Username not found")
|
| 1531 |
+
except Exception as e:
|
| 1532 |
+
st.error(e)
|
pages/3_Saved_Scenarios.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from numerize.numerize import numerize
|
| 3 |
+
import io
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from utilities import (format_numbers,decimal_formater,
|
| 6 |
+
channel_name_formating,
|
| 7 |
+
load_local_css,set_header,
|
| 8 |
+
initialize_data,
|
| 9 |
+
load_authenticator)
|
| 10 |
+
from openpyxl import Workbook
|
| 11 |
+
from openpyxl.styles import Alignment,Font,PatternFill
|
| 12 |
+
import pickle
|
| 13 |
+
import streamlit_authenticator as stauth
|
| 14 |
+
import yaml
|
| 15 |
+
from yaml import SafeLoader
|
| 16 |
+
from classes import class_from_dict
|
| 17 |
+
import plotly.graph_objects as go
|
| 18 |
+
|
| 19 |
+
st.set_page_config(layout='wide')
|
| 20 |
+
load_local_css('styles.css')
|
| 21 |
+
set_header()
|
| 22 |
+
|
| 23 |
+
# for k, v in st.session_state.items():
|
| 24 |
+
# if k not in ['logout', 'login','config'] and not k.startswith('FormSubmitter'):
|
| 25 |
+
# st.session_state[k] = v
|
| 26 |
+
def comparision_scenarios_df():
|
| 27 |
+
|
| 28 |
+
## create summary page
|
| 29 |
+
if len(scenarios_to_compare) == 0:
|
| 30 |
+
return
|
| 31 |
+
summary_df_spend = None
|
| 32 |
+
summary_df_prospect = None
|
| 33 |
+
# summary_df_efficiency = None
|
| 34 |
+
#=print(scenarios_to_download)
|
| 35 |
+
for scenario_name in scenarios_to_compare:
|
| 36 |
+
scenario_dict = st.session_state['saved_scenarios'][scenario_name]
|
| 37 |
+
_spends = []
|
| 38 |
+
column_names = ['Date']
|
| 39 |
+
_sales = None
|
| 40 |
+
dates = None
|
| 41 |
+
summary_rows_spend = []
|
| 42 |
+
summary_rows_prospects = []
|
| 43 |
+
for channel in scenario_dict['channels']:
|
| 44 |
+
if dates is None:
|
| 45 |
+
dates = channel.get('dates')
|
| 46 |
+
_spends.append(dates)
|
| 47 |
+
if _sales is None:
|
| 48 |
+
_sales = channel.get('modified_sales')
|
| 49 |
+
else:
|
| 50 |
+
_sales += channel.get('modified_sales')
|
| 51 |
+
_spends.append(channel.get('modified_spends') * channel.get('conversion_rate'))
|
| 52 |
+
column_names.append(channel.get('name'))
|
| 53 |
+
|
| 54 |
+
name_mod = channel_name_formating(channel['name'])
|
| 55 |
+
summary_rows_spend.append([name_mod,
|
| 56 |
+
channel.get('modified_total_spends') * channel.get('conversion_rate')])
|
| 57 |
+
summary_rows_prospects.append([name_mod,
|
| 58 |
+
channel.get('modified_total_sales')])
|
| 59 |
+
|
| 60 |
+
_spends.append(_sales)
|
| 61 |
+
# column_names.append('NRPU')
|
| 62 |
+
# scenario_df = pd.DataFrame(_spends).T
|
| 63 |
+
# scenario_df.columns = column_names
|
| 64 |
+
|
| 65 |
+
# summary_rows.append(['Total',
|
| 66 |
+
# scenario_dict.get('modified_total_spends') ,
|
| 67 |
+
# scenario_dict.get('modified_total_sales'),
|
| 68 |
+
# scenario_dict.get('modified_total_sales') / scenario_dict.get('modified_total_spends'),
|
| 69 |
+
# '-',
|
| 70 |
+
# scenario_dict.get('modified_total_spends') / scenario_dict.get('modified_total_sales')])
|
| 71 |
+
# columns_index = pd.MultiIndex.from_product([[''],['Channel']], names=["first", "second"])
|
| 72 |
+
# columns_index = columns_index.append(pd.MultiIndex.from_product([[scenario_name],['Spends','NRPU','ROI','MROI','Spends per NRPU']], names=["first", "second"]))
|
| 73 |
+
columns_index = ['Channel',scenario_name]
|
| 74 |
+
if summary_df_spend is None:
|
| 75 |
+
summary_df_spend = pd.DataFrame(summary_rows_spend, columns = columns_index)
|
| 76 |
+
summary_df_spend = summary_df_spend.set_index('Channel')
|
| 77 |
+
else:
|
| 78 |
+
_df = pd.DataFrame(summary_rows_spend, columns = columns_index)
|
| 79 |
+
_df = _df.set_index('Channel')
|
| 80 |
+
summary_df_spend = summary_df_spend.merge(_df, left_index=True, right_index=True)
|
| 81 |
+
|
| 82 |
+
if summary_df_prospect is None:
|
| 83 |
+
summary_df_prospect = pd.DataFrame(summary_rows_prospects, columns = columns_index)
|
| 84 |
+
summary_df_prospect = summary_df_prospect.set_index('Channel')
|
| 85 |
+
else:
|
| 86 |
+
_df = pd.DataFrame(summary_rows_prospects, columns = columns_index)
|
| 87 |
+
_df = _df.set_index('Channel')
|
| 88 |
+
summary_df_prospect = summary_df_prospect.merge(_df, left_index=True, right_index=True)
|
| 89 |
+
st.session_state['disable_download_button'] = False
|
| 90 |
+
return summary_df_spend,summary_df_prospect
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def plot_comparision_chart(df,metric):
|
| 95 |
+
|
| 96 |
+
# Create traces for each column
|
| 97 |
+
traces = []
|
| 98 |
+
for column in df.columns:
|
| 99 |
+
traces.append(go.Bar(
|
| 100 |
+
x=df.index,
|
| 101 |
+
y=df[column],
|
| 102 |
+
name=column,
|
| 103 |
+
text=df[column].apply(numerize), # Adding text for each point
|
| 104 |
+
textposition='outside',
|
| 105 |
+
hoverinfo='x+y+text',
|
| 106 |
+
))
|
| 107 |
+
|
| 108 |
+
# Create the layout
|
| 109 |
+
layout = go.Layout(
|
| 110 |
+
title='Scenario Comparision '+ metric,
|
| 111 |
+
xaxis_title="Channels",
|
| 112 |
+
yaxis_title=metric,
|
| 113 |
+
barmode='group'
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# Create the figure
|
| 117 |
+
fig = go.Figure(data=traces, layout=layout)
|
| 118 |
+
|
| 119 |
+
return fig
|
| 120 |
+
|
| 121 |
+
def create_comparision_plots():
|
| 122 |
+
comparision_scenarios_df()
|
| 123 |
+
spends_df, prospects_df = comparision_scenarios_df()
|
| 124 |
+
|
| 125 |
+
st.plotly_chart(plot_comparision_chart(spends_df,"Spends"),use_container_width=True)
|
| 126 |
+
st.plotly_chart(plot_comparision_chart(prospects_df,"Contributions"),use_container_width=True)
|
| 127 |
+
|
| 128 |
+
def create_scenario_summary(scenario_dict):
|
| 129 |
+
summary_rows = []
|
| 130 |
+
actual_total_spends = scenario_dict.get('actual_total_spends'),
|
| 131 |
+
modified_total_spends = scenario_dict.get('modified_total_spends'),
|
| 132 |
+
actual_total_sales = scenario_dict.get('actual_total_sales'),
|
| 133 |
+
modified_total_sales = scenario_dict.get('modified_total_sales')
|
| 134 |
+
# st.write(modified_total_spends[0])
|
| 135 |
+
# st.write(actual_total_spends[0])
|
| 136 |
+
# st.write(modified_total_sales)
|
| 137 |
+
# st.write(actual_total_sales[0])
|
| 138 |
+
# st.write(modified_total_spends[0])
|
| 139 |
+
for channel_dict in scenario_dict['channels']:
|
| 140 |
+
name_mod = channel_name_formating(channel_dict['name'])
|
| 141 |
+
summary_rows.append([name_mod,
|
| 142 |
+
channel_dict.get('actual_total_spends') * channel_dict.get('conversion_rate'),
|
| 143 |
+
channel_dict.get('modified_total_spends') * channel_dict.get('conversion_rate'),
|
| 144 |
+
channel_dict.get('actual_total_sales') ,
|
| 145 |
+
channel_dict.get('modified_total_sales'),
|
| 146 |
+
# channel_dict.get('modified_total_sales')/modified_total_spends[0],
|
| 147 |
+
# channel_dict.get('modified_total_sales')/modified_total_spends[0]
|
| 148 |
+
|
| 149 |
+
# 1,2
|
| 150 |
+
(channel_dict.get('actual_total_sales') /actual_total_sales[0])/(channel_dict.get('actual_total_spends') /actual_total_spends[0] ),
|
| 151 |
+
(channel_dict.get('modified_total_sales') /modified_total_sales )/(channel_dict.get('modified_total_spends') /modified_total_spends[0] )
|
| 152 |
+
# # # channel_dict.get('actual_mroi'),
|
| 153 |
+
# channel_dict.get('modified_mroi'),
|
| 154 |
+
# channel_dict.get('actual_total_spends') * channel_dict.get('conversion_rate') / channel_dict.get('actual_total_sales'),
|
| 155 |
+
# channel_dict.get('modified_total_spends') * channel_dict.get('conversion_rate') / channel_dict.get('modified_total_sales')
|
| 156 |
+
])
|
| 157 |
+
|
| 158 |
+
summary_rows.append(['Total',
|
| 159 |
+
scenario_dict.get('actual_total_spends'),
|
| 160 |
+
scenario_dict.get('modified_total_spends'),
|
| 161 |
+
scenario_dict.get('actual_total_sales'),
|
| 162 |
+
scenario_dict.get('modified_total_sales'),
|
| 163 |
+
1.0,
|
| 164 |
+
1.0
|
| 165 |
+
# scenario_dict.get('actual_total_sales') / scenario_dict.get('actual_total_spends'),
|
| 166 |
+
# scenario_dict.get('modified_total_sales') / scenario_dict.get('modified_total_spends'),
|
| 167 |
+
# '-',
|
| 168 |
+
# '-',
|
| 169 |
+
# scenario_dict.get('actual_total_spends') / scenario_dict.get('actual_total_sales'),
|
| 170 |
+
# scenario_dict.get('modified_total_spends') / scenario_dict.get('modified_total_sales')
|
| 171 |
+
])
|
| 172 |
+
|
| 173 |
+
columns_index = pd.MultiIndex.from_product([[''],['Channel']], names=["first", "second"])
|
| 174 |
+
columns_index = columns_index.append(pd.MultiIndex.from_product([['Spends','Prospects',"Efficiency"],['Actual','Simulated']], names=["first", "second"]))
|
| 175 |
+
|
| 176 |
+
return pd.DataFrame(summary_rows, columns=columns_index)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def summary_df_to_worksheet(df, ws):
|
| 181 |
+
heading_fill = PatternFill(fill_type='solid',start_color='FF11B6BD',end_color='FF11B6BD')
|
| 182 |
+
for j,header in enumerate(df.columns.values):
|
| 183 |
+
col = j + 1
|
| 184 |
+
for i in range(1,3):
|
| 185 |
+
ws.cell(row=i, column=j + 1, value=header[i - 1]).font = Font(bold=True, color='FF11B6BD')
|
| 186 |
+
ws.cell(row=i,column=j+1).fill = heading_fill
|
| 187 |
+
if col > 1 and (col - 6)%5==0:
|
| 188 |
+
ws.merge_cells(start_row=1, end_row=1, start_column = col-3, end_column=col)
|
| 189 |
+
ws.cell(row=1,column=col).alignment = Alignment(horizontal='center')
|
| 190 |
+
for i,row in enumerate(df.itertuples()):
|
| 191 |
+
for j,value in enumerate(row):
|
| 192 |
+
if j == 0:
|
| 193 |
+
continue
|
| 194 |
+
elif (j-2)%4 == 0 or (j-3)%4 == 0:
|
| 195 |
+
ws.cell(row=i+3, column = j, value=value).number_format = '$#,##0.0'
|
| 196 |
+
else:
|
| 197 |
+
ws.cell(row=i+3, column = j, value=value)
|
| 198 |
+
|
| 199 |
+
from openpyxl.utils import get_column_letter
|
| 200 |
+
from openpyxl.styles import Font, PatternFill
|
| 201 |
+
import logging
|
| 202 |
+
|
| 203 |
+
def scenario_df_to_worksheet(df, ws):
|
| 204 |
+
heading_fill = PatternFill(start_color='FF11B6BD', end_color='FF11B6BD', fill_type='solid')
|
| 205 |
+
|
| 206 |
+
for j, header in enumerate(df.columns.values):
|
| 207 |
+
cell = ws.cell(row=1, column=j + 1, value=header)
|
| 208 |
+
cell.font = Font(bold=True, color='FF11B6BD')
|
| 209 |
+
cell.fill = heading_fill
|
| 210 |
+
|
| 211 |
+
for i, row in enumerate(df.itertuples()):
|
| 212 |
+
for j, value in enumerate(row[1:], start=1): # Start from index 1 to skip the index column
|
| 213 |
+
try:
|
| 214 |
+
cell = ws.cell(row=i + 2, column=j, value=value)
|
| 215 |
+
if isinstance(value, (int, float)):
|
| 216 |
+
cell.number_format = '$#,##0.0'
|
| 217 |
+
elif isinstance(value, str):
|
| 218 |
+
cell.value = value[:32767]
|
| 219 |
+
else:
|
| 220 |
+
cell.value = str(value)
|
| 221 |
+
except ValueError as e:
|
| 222 |
+
logging.error(f"Error assigning value '{value}' to cell {get_column_letter(j)}{i+2}: {e}")
|
| 223 |
+
cell.value = None # Assign None to the cell where the error occurred
|
| 224 |
+
|
| 225 |
+
return ws
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def download_scenarios():
|
| 233 |
+
"""
|
| 234 |
+
Makes a excel with all saved scenarios and saves it locally
|
| 235 |
+
"""
|
| 236 |
+
## create summary page
|
| 237 |
+
if len(scenarios_to_download) == 0:
|
| 238 |
+
return
|
| 239 |
+
wb = Workbook()
|
| 240 |
+
wb.iso_dates = True
|
| 241 |
+
wb.remove(wb.active)
|
| 242 |
+
st.session_state['xlsx_buffer'] = io.BytesIO()
|
| 243 |
+
summary_df = None
|
| 244 |
+
#print(scenarios_to_download)
|
| 245 |
+
for scenario_name in scenarios_to_download:
|
| 246 |
+
scenario_dict = st.session_state['saved_scenarios'][scenario_name]
|
| 247 |
+
_spends = []
|
| 248 |
+
column_names = ['Date']
|
| 249 |
+
_sales = None
|
| 250 |
+
dates = None
|
| 251 |
+
summary_rows = []
|
| 252 |
+
for channel in scenario_dict['channels']:
|
| 253 |
+
if dates is None:
|
| 254 |
+
dates = channel.get('dates')
|
| 255 |
+
_spends.append(dates)
|
| 256 |
+
if _sales is None:
|
| 257 |
+
_sales = channel.get('modified_sales')
|
| 258 |
+
else:
|
| 259 |
+
_sales += channel.get('modified_sales')
|
| 260 |
+
_spends.append(channel.get('modified_spends') * channel.get('conversion_rate'))
|
| 261 |
+
column_names.append(channel.get('name'))
|
| 262 |
+
|
| 263 |
+
name_mod = channel_name_formating(channel['name'])
|
| 264 |
+
summary_rows.append([name_mod,
|
| 265 |
+
channel.get('modified_total_spends') * channel.get('conversion_rate') ,
|
| 266 |
+
channel.get('modified_total_sales'),
|
| 267 |
+
channel.get('modified_total_sales') / channel.get('modified_total_spends') * channel.get('conversion_rate'),
|
| 268 |
+
channel.get('modified_mroi'),
|
| 269 |
+
channel.get('modified_total_sales') / channel.get('modified_total_spends') * channel.get('conversion_rate')])
|
| 270 |
+
_spends.append(_sales)
|
| 271 |
+
column_names.append('NRPU')
|
| 272 |
+
scenario_df = pd.DataFrame(_spends).T
|
| 273 |
+
scenario_df.columns = column_names
|
| 274 |
+
## write to sheet
|
| 275 |
+
ws = wb.create_sheet(scenario_name)
|
| 276 |
+
scenario_df_to_worksheet(scenario_df, ws)
|
| 277 |
+
summary_rows.append(['Total',
|
| 278 |
+
scenario_dict.get('modified_total_spends') ,
|
| 279 |
+
scenario_dict.get('modified_total_sales'),
|
| 280 |
+
scenario_dict.get('modified_total_sales') / scenario_dict.get('modified_total_spends'),
|
| 281 |
+
'-',
|
| 282 |
+
scenario_dict.get('modified_total_spends') / scenario_dict.get('modified_total_sales')])
|
| 283 |
+
columns_index = pd.MultiIndex.from_product([[''],['Channel']], names=["first", "second"])
|
| 284 |
+
columns_index = columns_index.append(pd.MultiIndex.from_product([[scenario_name],['Spends','NRPU','ROI','MROI','Spends per NRPU']], names=["first", "second"]))
|
| 285 |
+
if summary_df is None:
|
| 286 |
+
summary_df = pd.DataFrame(summary_rows, columns = columns_index)
|
| 287 |
+
summary_df = summary_df.set_index(('','Channel'))
|
| 288 |
+
else:
|
| 289 |
+
_df = pd.DataFrame(summary_rows, columns = columns_index)
|
| 290 |
+
_df = _df.set_index(('','Channel'))
|
| 291 |
+
summary_df = summary_df.merge(_df, left_index=True, right_index=True)
|
| 292 |
+
ws = wb.create_sheet('Summary',0)
|
| 293 |
+
summary_df_to_worksheet(summary_df.reset_index(), ws)
|
| 294 |
+
wb.save(st.session_state['xlsx_buffer'])
|
| 295 |
+
st.session_state['disable_download_button'] = False
|
| 296 |
+
|
| 297 |
+
def disable_download_button():
|
| 298 |
+
st.session_state['disable_download_button'] =True
|
| 299 |
+
|
| 300 |
+
def transform(x):
|
| 301 |
+
if x.name == ("",'Channel'):
|
| 302 |
+
return x
|
| 303 |
+
elif x.name[0] == 'ROI' or x.name[0] == 'MROI':
|
| 304 |
+
return x.apply(lambda y : y if isinstance(y,str) else decimal_formater(format_numbers(y,include_indicator=False,n_decimals=4),n_decimals=4))
|
| 305 |
+
else:
|
| 306 |
+
return x.apply(lambda y : y if isinstance(y,str) else format_numbers(y))
|
| 307 |
+
|
| 308 |
+
def delete_scenario():
|
| 309 |
+
if selected_scenario in st.session_state['saved_scenarios']:
|
| 310 |
+
del st.session_state['saved_scenarios'][selected_scenario]
|
| 311 |
+
with open('../saved_scenarios.pkl', 'wb') as f:
|
| 312 |
+
pickle.dump(st.session_state['saved_scenarios'],f)
|
| 313 |
+
|
| 314 |
+
def load_scenario():
|
| 315 |
+
if selected_scenario in st.session_state['saved_scenarios']:
|
| 316 |
+
st.session_state['scenario'] = class_from_dict(selected_scenario_details)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
authenticator = st.session_state.get('authenticator')
|
| 321 |
+
if authenticator is None:
|
| 322 |
+
authenticator = load_authenticator()
|
| 323 |
+
|
| 324 |
+
name, authentication_status, username = authenticator.login('Login', 'main')
|
| 325 |
+
auth_status = st.session_state.get('authentication_status')
|
| 326 |
+
|
| 327 |
+
if auth_status == True:
|
| 328 |
+
is_state_initiaized = st.session_state.get('initialized',False)
|
| 329 |
+
if not is_state_initiaized:
|
| 330 |
+
#print("Scenario page state reloaded")
|
| 331 |
+
initialize_data()
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
saved_scenarios = st.session_state['saved_scenarios']
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
if len(saved_scenarios) ==0:
|
| 338 |
+
st.header('No saved scenarios')
|
| 339 |
+
|
| 340 |
+
else:
|
| 341 |
+
|
| 342 |
+
with st.sidebar:
|
| 343 |
+
with st.expander('View Scenario Details'):
|
| 344 |
+
st.markdown("""<hr>""", unsafe_allow_html=True)
|
| 345 |
+
selected_scenario = st.selectbox('Select the scenario',list(saved_scenarios.keys()))
|
| 346 |
+
# selected_scenario = st.radio(
|
| 347 |
+
# 'Pick a scenario to view details',
|
| 348 |
+
# list(saved_scenarios.keys())
|
| 349 |
+
# )
|
| 350 |
+
with st.expander('Download Scenario'):
|
| 351 |
+
st.markdown("""<hr>""", unsafe_allow_html=True)
|
| 352 |
+
scenarios_to_download = st.multiselect('Select scenarios to download',
|
| 353 |
+
list(saved_scenarios.keys()))
|
| 354 |
+
|
| 355 |
+
st.button('Prepare download',on_click=download_scenarios)
|
| 356 |
+
st.download_button(
|
| 357 |
+
label="Download Scenarios",
|
| 358 |
+
data=st.session_state['xlsx_buffer'].getvalue(),
|
| 359 |
+
file_name="scenarios.xlsx",
|
| 360 |
+
mime="application/vnd.ms-excel",
|
| 361 |
+
disabled= st.session_state['disable_download_button'],
|
| 362 |
+
on_click= disable_download_button
|
| 363 |
+
)
|
| 364 |
+
with st.expander('Compare Scenarios'):
|
| 365 |
+
st.markdown("""<hr>""", unsafe_allow_html=True)
|
| 366 |
+
scenarios_to_compare = st.multiselect('Select scenarios to compare',
|
| 367 |
+
list(saved_scenarios.keys()))
|
| 368 |
+
st.button('Compare')
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
column_1, column_2,column_3 = st.columns((6,1,1))
|
| 372 |
+
with column_1:
|
| 373 |
+
st.header(selected_scenario)
|
| 374 |
+
with column_2:
|
| 375 |
+
st.button('Delete scenarios', on_click=delete_scenario)
|
| 376 |
+
with column_3:
|
| 377 |
+
st.button('Load Scenario', on_click=load_scenario)
|
| 378 |
+
|
| 379 |
+
selected_scenario_details = saved_scenarios[selected_scenario]
|
| 380 |
+
|
| 381 |
+
pd.set_option('display.max_colwidth', 100)
|
| 382 |
+
|
| 383 |
+
st.markdown(create_scenario_summary(selected_scenario_details).transform(transform).style.set_table_styles(
|
| 384 |
+
[{
|
| 385 |
+
'selector': 'th',
|
| 386 |
+
'props': [('background-color', '#11B6BD')]
|
| 387 |
+
},
|
| 388 |
+
{
|
| 389 |
+
'selector' : 'tr:nth-child(even)',
|
| 390 |
+
'props' : [('background-color', '#11B6BD')]
|
| 391 |
+
}
|
| 392 |
+
]).to_html(),unsafe_allow_html=True)
|
| 393 |
+
st.markdown("<br><br>", unsafe_allow_html=True)
|
| 394 |
+
|
| 395 |
+
with st.expander('Scenario Comparision'):
|
| 396 |
+
st.header("Scenario Comparision")
|
| 397 |
+
if len(scenarios_to_compare)== 0:
|
| 398 |
+
st.write("")
|
| 399 |
+
else:
|
| 400 |
+
create_comparision_plots()
|
| 401 |
+
|
| 402 |
+
elif auth_status == False:
|
| 403 |
+
st.error('Username/Password is incorrect')
|
| 404 |
+
|
| 405 |
+
if auth_status != True:
|
| 406 |
+
try:
|
| 407 |
+
username_forgot_pw, email_forgot_password, random_password = authenticator.forgot_password('Forgot password')
|
| 408 |
+
if username_forgot_pw:
|
| 409 |
+
st.success('New password sent securely')
|
| 410 |
+
# Random password to be transferred to user securely
|
| 411 |
+
elif username_forgot_pw == False:
|
| 412 |
+
st.error('Username not found')
|
| 413 |
+
except Exception as e:
|
| 414 |
+
st.error(e)
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
# create_comparision_plots()
|
pages/4_Model Quality.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import plotly.express as px
|
| 5 |
+
import Streamlit_functions as sf
|
| 6 |
+
import response_curves_model_quality_base as rc1
|
| 7 |
+
st.set_page_config(
|
| 8 |
+
layout="wide"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
st.header("Model Quality")
|
| 13 |
+
st.write("MMM Model Quality")
|
| 14 |
+
|
| 15 |
+
st.plotly_chart(sf.mmm_model_quality(),use_container_width=True)
|
| 16 |
+
|
| 17 |
+
media_df = sf.media_data()
|
| 18 |
+
# Create two columns for start date and end date input
|
| 19 |
+
col1, col2 = st.columns(2)
|
| 20 |
+
|
| 21 |
+
st.table(sf.model_metrics_table_func())
|
| 22 |
+
|
| 23 |
+
with col1:
|
| 24 |
+
st.plotly_chart(sf.elasticity(media_df))
|
| 25 |
+
with col2:
|
| 26 |
+
st.plotly_chart(sf.half_life(media_df))
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Dropdown menu options
|
| 30 |
+
options = [
|
| 31 |
+
'Broadcast TV',
|
| 32 |
+
'Cable TV',
|
| 33 |
+
'Connected & OTT TV',
|
| 34 |
+
'Display Prospecting',
|
| 35 |
+
'Display Retargeting',
|
| 36 |
+
'Video',
|
| 37 |
+
'Social Prospecting',
|
| 38 |
+
'Social Retargeting',
|
| 39 |
+
'Search Brand',
|
| 40 |
+
'Search Non-brand',
|
| 41 |
+
'Digital Partners',
|
| 42 |
+
'Audio',
|
| 43 |
+
'Email']
|
| 44 |
+
options1 = [
|
| 45 |
+
'View Line Plot',
|
| 46 |
+
'View Scattered Plot',
|
| 47 |
+
"View Both"]
|
| 48 |
+
col1, col2 = st.columns(2)
|
| 49 |
+
# Create a dropdown menu
|
| 50 |
+
with col1:
|
| 51 |
+
selected_option = st.selectbox('Select a media channel:', options)
|
| 52 |
+
selected_option2 = st.selectbox('Select a Chart Type', options1)
|
| 53 |
+
# Display the selected option
|
| 54 |
+
st.plotly_chart(rc1.response_curves(selected_option,selected_option2))
|
| 55 |
+
with col2:
|
| 56 |
+
st.write("")
|
| 57 |
+
|
pages/5_Glossary.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
|
| 3 |
+
# st.set_page_config(
|
| 4 |
+
# layout="wide"
|
| 5 |
+
# )
|
| 6 |
+
|
| 7 |
+
def glossary_run():
|
| 8 |
+
st.subheader("Glossary of MMM Terminology")
|
| 9 |
+
st.write("**• Model R-squared \(R\)\:** This is a statistical measure used to determine the percentage of variation in the dependent variable that the independent variables explain collectively. It ranges between 0 and 1, where 1 indicates a perfect fit and 0 indicates no linear relationship. An R2 greater than 0.8 usually indicates a great model fit.")
|
| 10 |
+
|
| 11 |
+
st.write("**• Mean Absolute Percentage Error \(MAPE\):** This is a measure used to determine the accuracy of a predictive model. It calculates the average absolute percentage difference between the actual and predicted values, expressing the result as a percentage to provide a sense of scale for the error.")
|
| 12 |
+
|
| 13 |
+
st.write("**• Media & Baseline Elasticity:** It refers to the percentage change in the number of prospects in response to a percentage change in a marketing input \(media channel spends\) or a baseline factor \(like seasonality. macro factors, competitors spending, etc.\). It is a measure of the responsiveness of the number of prospects to changes in the marketing input or the baseline factor")
|
| 14 |
+
|
| 15 |
+
st.write("**• Media Half-Life:** This represents the time it takes for a media spend's impact to reduce to half of its initial impact. It is a key aspect of media decay rates, which represent how the effect of advertising diminishes over time \(in weeks\). This term refers to a curve that illustrates the relationship between media spend and the resulting number of prospects.")
|
| 16 |
+
|
| 17 |
+
st.write("**• Support:** Equivalent to Impression or Click depending on the media channel.")
|
| 18 |
+
|
| 19 |
+
st.write("**• Contribution Share:** Unit is %. It refers to the percentage contribution of a specific marketing channel to the number of prospects. It is calculated by dividing the contribution from a particular channel by the total number of prospects from all media channels \(not including base contributions\).")
|
| 20 |
+
|
| 21 |
+
st.write("**• Spend Share:** Unit is %. It refers to the percentage of the total marketing budget that is allocated to a specific marketing channel. It is calculated by dividing the amount spent on a particular channel by the total marketing spend")
|
| 22 |
+
|
| 23 |
+
st.write("**• Support Share:** Unit is %. It refers to the percentage of the total media impression that is allocated to a specific marketing channel. It is calculated by dividing support on a particular channel by the total marketing spend")
|
| 24 |
+
|
| 25 |
+
st.write("**• Efficiency Index:** it is a metric that measures the cost-effectiveness of a campaign. It is calculated by dividing Contribution Share by Spend Share. An efficiency index above 1 suggests that a channel is more cost-effective than the benchmark, while an efficiency index below 1 suggests it is less cost-effective. The higher the efficiency index, the more cost-effective its channel is")
|
| 26 |
+
|
| 27 |
+
st.write("**• Effectiveness Index:** It is a metric that measures how well a particular marketing channel is performing relative to its support/impression. It is calculated by dividing the Contribution Share by the Spend Share for each channel")
|
| 28 |
+
|
| 29 |
+
st.write("**• Estimated CPM \(Cost Per Thousand Impressions\):** This is an estimation of the cost for every thousand impressions \(or views\) of its advertisement via that media channel. The default values are generated from historical averages.")
|
| 30 |
+
|
| 31 |
+
st.write("**• Estimated CPC \(Cost Per Click\):** This is an estimation of the cost for each time someone clicks on its advertisement via that media channel. The default values are generated from historical averages.")
|
| 32 |
+
|
| 33 |
+
glossary_run()
|
response_curves_model_quality.py
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
from scipy.optimize import curve_fit
|
| 5 |
+
from sklearn.preprocessing import MinMaxScaler
|
| 6 |
+
import warnings
|
| 7 |
+
warnings.filterwarnings("ignore")
|
| 8 |
+
import plotly.graph_objects as go
|
| 9 |
+
|
| 10 |
+
## reading input data
|
| 11 |
+
df= pd.read_csv('response_curves_input_file.csv')
|
| 12 |
+
df.dropna(inplace=True)
|
| 13 |
+
df['Date'] = pd.to_datetime(df['Date'])
|
| 14 |
+
df.reset_index(inplace=True)
|
| 15 |
+
|
| 16 |
+
channel_cols = [
|
| 17 |
+
'BroadcastTV',
|
| 18 |
+
'CableTV',
|
| 19 |
+
'Connected&OTTTV',
|
| 20 |
+
'DisplayProspecting',
|
| 21 |
+
'DisplayRetargeting',
|
| 22 |
+
'Video',
|
| 23 |
+
'SocialProspecting',
|
| 24 |
+
'SocialRetargeting',
|
| 25 |
+
'SearchBrand',
|
| 26 |
+
'SearchNon-brand',
|
| 27 |
+
'DigitalPartners',
|
| 28 |
+
'Audio',
|
| 29 |
+
'Email']
|
| 30 |
+
spend_cols = [
|
| 31 |
+
'tv_broadcast_spend',
|
| 32 |
+
'tv_cable_spend',
|
| 33 |
+
'stream_video_spend',
|
| 34 |
+
'disp_prospect_spend',
|
| 35 |
+
'disp_retarget_spend',
|
| 36 |
+
'olv_spend',
|
| 37 |
+
'social_prospect_spend',
|
| 38 |
+
'social_retarget_spend',
|
| 39 |
+
'search_brand_spend',
|
| 40 |
+
'search_nonbrand_spend',
|
| 41 |
+
'cm_spend',
|
| 42 |
+
'audio_spend',
|
| 43 |
+
'email_spend']
|
| 44 |
+
prospect_cols = [
|
| 45 |
+
'Broadcast TV_Prospects',
|
| 46 |
+
'Cable TV_Prospects',
|
| 47 |
+
'Connected & OTT TV_Prospects',
|
| 48 |
+
'Display Prospecting_Prospects',
|
| 49 |
+
'Display Retargeting_Prospects',
|
| 50 |
+
'Video_Prospects',
|
| 51 |
+
'Social Prospecting_Prospects',
|
| 52 |
+
'Social Retargeting_Prospects',
|
| 53 |
+
'Search Brand_Prospects',
|
| 54 |
+
'Search Non-brand_Prospects',
|
| 55 |
+
'Digital Partners_Prospects',
|
| 56 |
+
'Audio_Prospects',
|
| 57 |
+
'Email_Prospects']
|
| 58 |
+
|
| 59 |
+
def hill_equation(x, Kd, n):
|
| 60 |
+
return x**n / (Kd**n + x**n)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def hill_func(x_data,y_data,x_minmax,y_minmax):
|
| 64 |
+
# Fit the Hill equation to the data
|
| 65 |
+
initial_guess = [1, 1] # Initial guess for Kd and n
|
| 66 |
+
params, covariance = curve_fit(hill_equation, x_data, y_data, p0=initial_guess,maxfev = 1000)
|
| 67 |
+
|
| 68 |
+
# Extract the fitted parameters
|
| 69 |
+
Kd_fit, n_fit = params
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# Generate y values using the fitted parameters
|
| 73 |
+
y_fit = hill_equation(x_data, Kd_fit, n_fit)
|
| 74 |
+
|
| 75 |
+
x_data_inv = x_minmax.inverse_transform(np.array(x_data).reshape(-1,1))
|
| 76 |
+
y_data_inv = y_minmax.inverse_transform(np.array(y_data).reshape(-1,1))
|
| 77 |
+
y_fit_inv = y_minmax.inverse_transform(np.array(y_fit).reshape(-1,1))
|
| 78 |
+
|
| 79 |
+
# # Plot the original data and the fitted curve
|
| 80 |
+
# plt.scatter(x_data_inv, y_data_inv, label='Actual Data')
|
| 81 |
+
# plt.scatter(x_data_inv, y_fit_inv, label='Fit Data',color='red')
|
| 82 |
+
# # plt.line(x_data_inv, y_fit_inv, label=f'Fitted Hill Equation (Kd={Kd_fit:.2f}, n={n_fit:.2f})', color='red')
|
| 83 |
+
# plt.xlabel('Ligand Concentration')
|
| 84 |
+
# plt.ylabel('Fraction of Binding')
|
| 85 |
+
# plt.title('Fitting Hill Equation to Data')
|
| 86 |
+
# plt.legend()
|
| 87 |
+
# plt.show()
|
| 88 |
+
|
| 89 |
+
return y_fit,y_fit_inv,Kd_fit, n_fit
|
| 90 |
+
|
| 91 |
+
def data_output(channel,X,y,y_fit_inv,x_ext_data,y_fit_inv_ext):
|
| 92 |
+
fit_col = 'Fit_Data_'+channel
|
| 93 |
+
plot_df = pd.DataFrame()
|
| 94 |
+
|
| 95 |
+
plot_df[f'{channel}_Spends'] = X
|
| 96 |
+
|
| 97 |
+
plot_df['Date'] = df['Date']
|
| 98 |
+
plot_df['MAT'] = df['MAT']
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
y_fit_inv_v2 = []
|
| 103 |
+
for i in range(len(y_fit_inv)):
|
| 104 |
+
y_fit_inv_v2.append(y_fit_inv[i][0])
|
| 105 |
+
|
| 106 |
+
plot_df[fit_col] = y_fit_inv_v2
|
| 107 |
+
|
| 108 |
+
# adding extra data
|
| 109 |
+
|
| 110 |
+
y_fit_inv_v2_ext = []
|
| 111 |
+
for i in range(len(y_fit_inv_ext)):
|
| 112 |
+
y_fit_inv_v2_ext.append(y_fit_inv_ext[i][0])
|
| 113 |
+
|
| 114 |
+
# print(x_ext_data)
|
| 115 |
+
ext_df = pd.DataFrame()
|
| 116 |
+
ext_df[f'{channel}_Spends'] = x_ext_data
|
| 117 |
+
ext_df[fit_col] = y_fit_inv_v2_ext
|
| 118 |
+
|
| 119 |
+
ext_df['Date'] = [
|
| 120 |
+
np.datetime64('1950-01-01'),
|
| 121 |
+
np.datetime64('1950-06-15'),
|
| 122 |
+
np.datetime64('1950-12-31')
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
ext_df['MAT'] = ["ext","ext","ext"]
|
| 126 |
+
|
| 127 |
+
print(ext_df)
|
| 128 |
+
plot_df= plot_df.append(ext_df)
|
| 129 |
+
return plot_df
|
| 130 |
+
|
| 131 |
+
def input_data(df,spend_col,prospect_col):
|
| 132 |
+
X = np.array(df[spend_col].tolist())
|
| 133 |
+
y = np.array(df[prospect_col].tolist())
|
| 134 |
+
|
| 135 |
+
x_minmax = MinMaxScaler()
|
| 136 |
+
x_scaled = x_minmax.fit_transform(df[[spend_col]])
|
| 137 |
+
x_data = []
|
| 138 |
+
for i in range(len(x_scaled)):
|
| 139 |
+
x_data.append(x_scaled[i][0])
|
| 140 |
+
|
| 141 |
+
y_minmax = MinMaxScaler()
|
| 142 |
+
y_scaled = y_minmax.fit_transform(df[[prospect_col]])
|
| 143 |
+
y_data = []
|
| 144 |
+
for i in range(len(y_scaled)):
|
| 145 |
+
y_data.append(y_scaled[i][0])
|
| 146 |
+
|
| 147 |
+
return X,y,x_data,y_data,x_minmax,y_minmax
|
| 148 |
+
|
| 149 |
+
def extend_s_curve(x_max,x_minmax,y_minmax, Kd_fit, n_fit):
|
| 150 |
+
print(x_max)
|
| 151 |
+
x_ext_data = [x_max*1.2,x_max*1.3,x_max*1.5]
|
| 152 |
+
# x_ext_data = [1500000,2000000,2500000]
|
| 153 |
+
# x_ext_data = [x_max+100,x_max+200,x_max+5000]
|
| 154 |
+
x_scaled = x_minmax.transform(pd.DataFrame(x_ext_data))
|
| 155 |
+
x_data = []
|
| 156 |
+
for i in range(len(x_scaled)):
|
| 157 |
+
x_data.append(x_scaled[i][0])
|
| 158 |
+
|
| 159 |
+
print(x_data)
|
| 160 |
+
y_fit = hill_equation(x_data, Kd_fit, n_fit)
|
| 161 |
+
y_fit_inv = y_minmax.inverse_transform(np.array(y_fit).reshape(-1,1))
|
| 162 |
+
|
| 163 |
+
return x_ext_data,y_fit_inv
|
| 164 |
+
|
| 165 |
+
def fit_data(spend_col,prospect_col,channel):
|
| 166 |
+
### getting k and n parameters
|
| 167 |
+
temp_df = df[df[spend_col]>0]
|
| 168 |
+
temp_df.reset_index(inplace=True)
|
| 169 |
+
|
| 170 |
+
X,y,x_data,y_data,x_minmax,y_minmax = input_data(temp_df,spend_col,prospect_col)
|
| 171 |
+
y_fit, y_fit_inv, Kd_fit, n_fit = hill_func(x_data,y_data,x_minmax,y_minmax)
|
| 172 |
+
print('k: ',Kd_fit)
|
| 173 |
+
print('n: ', n_fit)
|
| 174 |
+
|
| 175 |
+
##### extend_s_curve
|
| 176 |
+
x_ext_data,y_fit_inv_ext= extend_s_curve(temp_df[spend_col].max(),x_minmax,y_minmax, Kd_fit, n_fit)
|
| 177 |
+
|
| 178 |
+
plot_df = data_output(channel,X,y,y_fit_inv,x_ext_data,y_fit_inv_ext)
|
| 179 |
+
return plot_df
|
| 180 |
+
|
| 181 |
+
plotly_data = fit_data(spend_cols[0],prospect_cols[0],channel_cols[0])
|
| 182 |
+
plotly_data.tail()
|
| 183 |
+
|
| 184 |
+
for i in range(1,13):
|
| 185 |
+
print(i)
|
| 186 |
+
pdf = fit_data(spend_cols[i],prospect_cols[i],channel_cols[i])
|
| 187 |
+
plotly_data = plotly_data.merge(pdf,on = ["Date","MAT"],how = "left")
|
| 188 |
+
|
| 189 |
+
def response_curves(channel,x_modified,y_modified):
|
| 190 |
+
|
| 191 |
+
# Initialize the Plotly figure
|
| 192 |
+
fig = go.Figure()
|
| 193 |
+
|
| 194 |
+
x_col = (channel+"_Spends").replace('\xa0', '')
|
| 195 |
+
y_col = ("Fit_Data_"+channel).replace('\xa0', '')
|
| 196 |
+
|
| 197 |
+
# fig.add_trace(go.Scatter(
|
| 198 |
+
# x=plotly_data[x_col],
|
| 199 |
+
# y=plotly_data[y_col],
|
| 200 |
+
# mode='markers',
|
| 201 |
+
# name=x_col.replace('_Spends', '')
|
| 202 |
+
# ))
|
| 203 |
+
|
| 204 |
+
fig.add_trace(go.Scatter(
|
| 205 |
+
x=plotly_data.sort_values(by=x_col, ascending=True)[x_col],
|
| 206 |
+
y=plotly_data.sort_values(by=x_col, ascending=True)[y_col],
|
| 207 |
+
mode='lines+markers',
|
| 208 |
+
name=x_col.replace('_Spends', '')
|
| 209 |
+
))
|
| 210 |
+
|
| 211 |
+
plotly_data2 = plotly_data.copy()
|
| 212 |
+
# .dropna(subset=[x_col]).reset_index(inplace = True)
|
| 213 |
+
fig.add_trace(go.Scatter(
|
| 214 |
+
x=plotly_data[plotly_data2['Date'] == plotly_data2['Date'].max()][x_col],
|
| 215 |
+
y=plotly_data[plotly_data2['Date'] == plotly_data2['Date'].max()][y_col],
|
| 216 |
+
mode='markers',
|
| 217 |
+
marker=dict(
|
| 218 |
+
size=13 # Adjust the size value to make the markers larger or smaller
|
| 219 |
+
, color = 'green'
|
| 220 |
+
),
|
| 221 |
+
name="Current Spends"
|
| 222 |
+
))
|
| 223 |
+
|
| 224 |
+
fig.add_trace(go.Scatter(
|
| 225 |
+
x=[x_modified],
|
| 226 |
+
y=[y_modified],
|
| 227 |
+
mode='markers',
|
| 228 |
+
marker=dict(
|
| 229 |
+
size=13 # Adjust the size value to make the markers larger or smaller
|
| 230 |
+
, color = 'blue'
|
| 231 |
+
),
|
| 232 |
+
name="Optimised Spends"
|
| 233 |
+
))
|
| 234 |
+
|
| 235 |
+
# Update layout with titles
|
| 236 |
+
fig.update_layout(
|
| 237 |
+
title=channel+' Response Curve',
|
| 238 |
+
xaxis_title='Weekly Spends',
|
| 239 |
+
yaxis_title='Prospects'
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
# Show the figure
|
| 243 |
+
return fig
|
| 244 |
+
|
| 245 |
+
import pandas as pd
|
| 246 |
+
import numpy as np
|
| 247 |
+
import matplotlib.pyplot as plt
|
| 248 |
+
from scipy.optimize import curve_fit
|
| 249 |
+
from sklearn.preprocessing import MinMaxScaler
|
| 250 |
+
import warnings
|
| 251 |
+
warnings.filterwarnings("ignore")
|
| 252 |
+
import plotly.graph_objects as go
|
| 253 |
+
|
| 254 |
+
## reading input data
|
| 255 |
+
df= pd.read_csv('response_curves_input_file.csv')
|
| 256 |
+
df.dropna(inplace=True)
|
| 257 |
+
df['Date'] = pd.to_datetime(df['Date'])
|
| 258 |
+
df.reset_index(inplace=True)
|
| 259 |
+
|
| 260 |
+
channel_cols = [
|
| 261 |
+
'BroadcastTV',
|
| 262 |
+
'CableTV',
|
| 263 |
+
'Connected&OTTTV',
|
| 264 |
+
'DisplayProspecting',
|
| 265 |
+
'DisplayRetargeting',
|
| 266 |
+
'Video',
|
| 267 |
+
'SocialProspecting',
|
| 268 |
+
'SocialRetargeting',
|
| 269 |
+
'SearchBrand',
|
| 270 |
+
'SearchNon-brand',
|
| 271 |
+
'DigitalPartners',
|
| 272 |
+
'Audio',
|
| 273 |
+
'Email']
|
| 274 |
+
spend_cols = [
|
| 275 |
+
'tv_broadcast_spend',
|
| 276 |
+
'tv_cable_spend',
|
| 277 |
+
'stream_video_spend',
|
| 278 |
+
'disp_prospect_spend',
|
| 279 |
+
'disp_retarget_spend',
|
| 280 |
+
'olv_spend',
|
| 281 |
+
'social_prospect_spend',
|
| 282 |
+
'social_retarget_spend',
|
| 283 |
+
'search_brand_spend',
|
| 284 |
+
'search_nonbrand_spend',
|
| 285 |
+
'cm_spend',
|
| 286 |
+
'audio_spend',
|
| 287 |
+
'email_spend']
|
| 288 |
+
prospect_cols = [
|
| 289 |
+
'Broadcast TV_Prospects',
|
| 290 |
+
'Cable TV_Prospects',
|
| 291 |
+
'Connected & OTT TV_Prospects',
|
| 292 |
+
'Display Prospecting_Prospects',
|
| 293 |
+
'Display Retargeting_Prospects',
|
| 294 |
+
'Video_Prospects',
|
| 295 |
+
'Social Prospecting_Prospects',
|
| 296 |
+
'Social Retargeting_Prospects',
|
| 297 |
+
'Search Brand_Prospects',
|
| 298 |
+
'Search Non-brand_Prospects',
|
| 299 |
+
'Digital Partners_Prospects',
|
| 300 |
+
'Audio_Prospects',
|
| 301 |
+
'Email_Prospects']
|
| 302 |
+
|
| 303 |
+
def hill_equation(x, Kd, n):
|
| 304 |
+
return x**n / (Kd**n + x**n)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def hill_func(x_data,y_data,x_minmax,y_minmax):
|
| 308 |
+
# Fit the Hill equation to the data
|
| 309 |
+
initial_guess = [1, 1] # Initial guess for Kd and n
|
| 310 |
+
params, covariance = curve_fit(hill_equation, x_data, y_data, p0=initial_guess,maxfev = 1000)
|
| 311 |
+
|
| 312 |
+
# Extract the fitted parameters
|
| 313 |
+
Kd_fit, n_fit = params
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
# Generate y values using the fitted parameters
|
| 317 |
+
y_fit = hill_equation(x_data, Kd_fit, n_fit)
|
| 318 |
+
|
| 319 |
+
x_data_inv = x_minmax.inverse_transform(np.array(x_data).reshape(-1,1))
|
| 320 |
+
y_data_inv = y_minmax.inverse_transform(np.array(y_data).reshape(-1,1))
|
| 321 |
+
y_fit_inv = y_minmax.inverse_transform(np.array(y_fit).reshape(-1,1))
|
| 322 |
+
|
| 323 |
+
# # Plot the original data and the fitted curve
|
| 324 |
+
# plt.scatter(x_data_inv, y_data_inv, label='Actual Data')
|
| 325 |
+
# plt.scatter(x_data_inv, y_fit_inv, label='Fit Data',color='red')
|
| 326 |
+
# # plt.line(x_data_inv, y_fit_inv, label=f'Fitted Hill Equation (Kd={Kd_fit:.2f}, n={n_fit:.2f})', color='red')
|
| 327 |
+
# plt.xlabel('Ligand Concentration')
|
| 328 |
+
# plt.ylabel('Fraction of Binding')
|
| 329 |
+
# plt.title('Fitting Hill Equation to Data')
|
| 330 |
+
# plt.legend()
|
| 331 |
+
# plt.show()
|
| 332 |
+
|
| 333 |
+
return y_fit,y_fit_inv,Kd_fit, n_fit
|
| 334 |
+
|
| 335 |
+
def data_output(channel,X,y,y_fit_inv,x_ext_data,y_fit_inv_ext):
|
| 336 |
+
fit_col = 'Fit_Data_'+channel
|
| 337 |
+
plot_df = pd.DataFrame()
|
| 338 |
+
|
| 339 |
+
plot_df[f'{channel}_Spends'] = X
|
| 340 |
+
|
| 341 |
+
plot_df['Date'] = df['Date']
|
| 342 |
+
plot_df['MAT'] = df['MAT']
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
y_fit_inv_v2 = []
|
| 347 |
+
for i in range(len(y_fit_inv)):
|
| 348 |
+
y_fit_inv_v2.append(y_fit_inv[i][0])
|
| 349 |
+
|
| 350 |
+
plot_df[fit_col] = y_fit_inv_v2
|
| 351 |
+
|
| 352 |
+
# adding extra data
|
| 353 |
+
|
| 354 |
+
y_fit_inv_v2_ext = []
|
| 355 |
+
for i in range(len(y_fit_inv_ext)):
|
| 356 |
+
y_fit_inv_v2_ext.append(y_fit_inv_ext[i][0])
|
| 357 |
+
|
| 358 |
+
# print(x_ext_data)
|
| 359 |
+
ext_df = pd.DataFrame()
|
| 360 |
+
ext_df[f'{channel}_Spends'] = x_ext_data
|
| 361 |
+
ext_df[fit_col] = y_fit_inv_v2_ext
|
| 362 |
+
|
| 363 |
+
ext_df['Date'] = [
|
| 364 |
+
np.datetime64('1950-01-01'),
|
| 365 |
+
np.datetime64('1950-06-15'),
|
| 366 |
+
np.datetime64('1950-12-31')
|
| 367 |
+
]
|
| 368 |
+
|
| 369 |
+
ext_df['MAT'] = ["ext","ext","ext"]
|
| 370 |
+
|
| 371 |
+
print(ext_df)
|
| 372 |
+
plot_df= plot_df.append(ext_df)
|
| 373 |
+
return plot_df
|
| 374 |
+
|
| 375 |
+
def input_data(df,spend_col,prospect_col):
|
| 376 |
+
X = np.array(df[spend_col].tolist())
|
| 377 |
+
y = np.array(df[prospect_col].tolist())
|
| 378 |
+
|
| 379 |
+
x_minmax = MinMaxScaler()
|
| 380 |
+
x_scaled = x_minmax.fit_transform(df[[spend_col]])
|
| 381 |
+
x_data = []
|
| 382 |
+
for i in range(len(x_scaled)):
|
| 383 |
+
x_data.append(x_scaled[i][0])
|
| 384 |
+
|
| 385 |
+
y_minmax = MinMaxScaler()
|
| 386 |
+
y_scaled = y_minmax.fit_transform(df[[prospect_col]])
|
| 387 |
+
y_data = []
|
| 388 |
+
for i in range(len(y_scaled)):
|
| 389 |
+
y_data.append(y_scaled[i][0])
|
| 390 |
+
|
| 391 |
+
return X,y,x_data,y_data,x_minmax,y_minmax
|
| 392 |
+
|
| 393 |
+
def extend_s_curve(x_max,x_minmax,y_minmax, Kd_fit, n_fit):
|
| 394 |
+
print(x_max)
|
| 395 |
+
x_ext_data = [x_max*1.2,x_max*1.3,x_max*1.5]
|
| 396 |
+
# x_ext_data = [1500000,2000000,2500000]
|
| 397 |
+
# x_ext_data = [x_max+100,x_max+200,x_max+5000]
|
| 398 |
+
x_scaled = x_minmax.transform(pd.DataFrame(x_ext_data))
|
| 399 |
+
x_data = []
|
| 400 |
+
for i in range(len(x_scaled)):
|
| 401 |
+
x_data.append(x_scaled[i][0])
|
| 402 |
+
|
| 403 |
+
print(x_data)
|
| 404 |
+
y_fit = hill_equation(x_data, Kd_fit, n_fit)
|
| 405 |
+
y_fit_inv = y_minmax.inverse_transform(np.array(y_fit).reshape(-1,1))
|
| 406 |
+
|
| 407 |
+
return x_ext_data,y_fit_inv
|
| 408 |
+
|
| 409 |
+
def fit_data(spend_col,prospect_col,channel):
|
| 410 |
+
### getting k and n parameters
|
| 411 |
+
temp_df = df[df[spend_col]>0]
|
| 412 |
+
temp_df.reset_index(inplace=True)
|
| 413 |
+
|
| 414 |
+
X,y,x_data,y_data,x_minmax,y_minmax = input_data(temp_df,spend_col,prospect_col)
|
| 415 |
+
y_fit, y_fit_inv, Kd_fit, n_fit = hill_func(x_data,y_data,x_minmax,y_minmax)
|
| 416 |
+
print('k: ',Kd_fit)
|
| 417 |
+
print('n: ', n_fit)
|
| 418 |
+
|
| 419 |
+
##### extend_s_curve
|
| 420 |
+
x_ext_data,y_fit_inv_ext= extend_s_curve(temp_df[spend_col].max(),x_minmax,y_minmax, Kd_fit, n_fit)
|
| 421 |
+
|
| 422 |
+
plot_df = data_output(channel,X,y,y_fit_inv,x_ext_data,y_fit_inv_ext)
|
| 423 |
+
return plot_df
|
| 424 |
+
|
| 425 |
+
plotly_data = fit_data(spend_cols[0],prospect_cols[0],channel_cols[0])
|
| 426 |
+
plotly_data.tail()
|
| 427 |
+
|
| 428 |
+
for i in range(1,13):
|
| 429 |
+
print(i)
|
| 430 |
+
pdf = fit_data(spend_cols[i],prospect_cols[i],channel_cols[i])
|
| 431 |
+
plotly_data = plotly_data.merge(pdf,on = ["Date","MAT"],how = "left")
|
| 432 |
+
|
| 433 |
+
def response_curves(channel,x_modified,y_modified):
|
| 434 |
+
|
| 435 |
+
# Initialize the Plotly figure
|
| 436 |
+
fig = go.Figure()
|
| 437 |
+
|
| 438 |
+
x_col = (channel+"_Spends").replace('\xa0', '')
|
| 439 |
+
y_col = ("Fit_Data_"+channel).replace('\xa0', '')
|
| 440 |
+
|
| 441 |
+
# fig.add_trace(go.Scatter(
|
| 442 |
+
# x=plotly_data[x_col],
|
| 443 |
+
# y=plotly_data[y_col],
|
| 444 |
+
# mode='markers',
|
| 445 |
+
# name=x_col.replace('_Spends', '')
|
| 446 |
+
# ))
|
| 447 |
+
|
| 448 |
+
fig.add_trace(go.Scatter(
|
| 449 |
+
x=plotly_data.sort_values(by=x_col, ascending=True)[x_col],
|
| 450 |
+
y=plotly_data.sort_values(by=x_col, ascending=True)[y_col],
|
| 451 |
+
mode='lines',
|
| 452 |
+
marker=dict(color = 'blue'),
|
| 453 |
+
name=x_col.replace('_Spends', '')
|
| 454 |
+
))
|
| 455 |
+
|
| 456 |
+
plotly_data2 = plotly_data.copy()
|
| 457 |
+
# .dropna(subset=[x_col]).reset_index(inplace = True)
|
| 458 |
+
fig.add_trace(go.Scatter(
|
| 459 |
+
x=plotly_data[plotly_data2['Date'] == plotly_data2['Date'].max()][x_col],
|
| 460 |
+
y=plotly_data[plotly_data2['Date'] == plotly_data2['Date'].max()][y_col],
|
| 461 |
+
mode='markers',
|
| 462 |
+
marker=dict(
|
| 463 |
+
size=13 # Adjust the size value to make the markers larger or smaller
|
| 464 |
+
, color = '#516DA6'
|
| 465 |
+
),
|
| 466 |
+
name="Current Spends"
|
| 467 |
+
))
|
| 468 |
+
|
| 469 |
+
fig.add_trace(go.Scatter(
|
| 470 |
+
x=[x_modified],
|
| 471 |
+
y=[y_modified],
|
| 472 |
+
mode='markers',
|
| 473 |
+
marker=dict(
|
| 474 |
+
size=13 # Adjust the size value to make the markers larger or smaller
|
| 475 |
+
, color = '#4ACAD9'
|
| 476 |
+
),
|
| 477 |
+
name="Optimised Spends"
|
| 478 |
+
))
|
| 479 |
+
|
| 480 |
+
# Update layout with titles
|
| 481 |
+
fig.update_layout(
|
| 482 |
+
title=channel+' Response Curve',
|
| 483 |
+
xaxis_title='Weekly Spends',
|
| 484 |
+
yaxis_title='Prospects'
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
# Show the figure
|
| 488 |
+
return fig
|
| 489 |
+
|
response_curves_model_quality_base.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import numpy as np
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
from scipy.optimize import curve_fit
|
| 5 |
+
from sklearn.preprocessing import MinMaxScaler
|
| 6 |
+
import warnings
|
| 7 |
+
warnings.filterwarnings("ignore")
|
| 8 |
+
import plotly.graph_objects as go
|
| 9 |
+
|
| 10 |
+
## reading input data
|
| 11 |
+
df= pd.read_csv('response_curves_input_file.csv')
|
| 12 |
+
df.dropna(inplace=True)
|
| 13 |
+
df['Date'] = pd.to_datetime(df['Date'])
|
| 14 |
+
df.reset_index(inplace=True)
|
| 15 |
+
|
| 16 |
+
channel_cols = [
|
| 17 |
+
'Broadcast TV',
|
| 18 |
+
'Cable TV',
|
| 19 |
+
'Connected & OTT TV',
|
| 20 |
+
'Display Prospecting',
|
| 21 |
+
'Display Retargeting',
|
| 22 |
+
'Video',
|
| 23 |
+
'Social Prospecting',
|
| 24 |
+
'Social Retargeting',
|
| 25 |
+
'Search Brand',
|
| 26 |
+
'Search Non-brand',
|
| 27 |
+
'Digital Partners',
|
| 28 |
+
'Audio',
|
| 29 |
+
'Email']
|
| 30 |
+
spend_cols = [
|
| 31 |
+
'tv_broadcast_spend',
|
| 32 |
+
'tv_cable_spend',
|
| 33 |
+
'stream_video_spend',
|
| 34 |
+
'disp_prospect_spend',
|
| 35 |
+
'disp_retarget_spend',
|
| 36 |
+
'olv_spend',
|
| 37 |
+
'social_prospect_spend',
|
| 38 |
+
'social_retarget_spend',
|
| 39 |
+
'search_brand_spend',
|
| 40 |
+
'search_nonbrand_spend',
|
| 41 |
+
'cm_spend',
|
| 42 |
+
'audio_spend',
|
| 43 |
+
'email_spend']
|
| 44 |
+
prospect_cols = [
|
| 45 |
+
'Broadcast TV_Prospects',
|
| 46 |
+
'Cable TV_Prospects',
|
| 47 |
+
'Connected & OTT TV_Prospects',
|
| 48 |
+
'Display Prospecting_Prospects',
|
| 49 |
+
'Display Retargeting_Prospects',
|
| 50 |
+
'Video_Prospects',
|
| 51 |
+
'Social Prospecting_Prospects',
|
| 52 |
+
'Social Retargeting_Prospects',
|
| 53 |
+
'Search Brand_Prospects',
|
| 54 |
+
'Search Non-brand_Prospects',
|
| 55 |
+
'Digital Partners_Prospects',
|
| 56 |
+
'Audio_Prospects',
|
| 57 |
+
'Email_Prospects']
|
| 58 |
+
|
| 59 |
+
def hill_equation(x, Kd, n):
|
| 60 |
+
return x**n / (Kd**n + x**n)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def hill_func(x_data,y_data,x_minmax,y_minmax):
|
| 64 |
+
# Fit the Hill equation to the data
|
| 65 |
+
initial_guess = [1, 1] # Initial guess for Kd and n
|
| 66 |
+
params, covariance = curve_fit(hill_equation, x_data, y_data, p0=initial_guess,maxfev = 1000)
|
| 67 |
+
|
| 68 |
+
# Extract the fitted parameters
|
| 69 |
+
Kd_fit, n_fit = params
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
# Generate y values using the fitted parameters
|
| 73 |
+
y_fit = hill_equation(x_data, Kd_fit, n_fit)
|
| 74 |
+
|
| 75 |
+
x_data_inv = x_minmax.inverse_transform(np.array(x_data).reshape(-1,1))
|
| 76 |
+
y_data_inv = y_minmax.inverse_transform(np.array(y_data).reshape(-1,1))
|
| 77 |
+
y_fit_inv = y_minmax.inverse_transform(np.array(y_fit).reshape(-1,1))
|
| 78 |
+
|
| 79 |
+
# # Plot the original data and the fitted curve
|
| 80 |
+
# plt.scatter(x_data_inv, y_data_inv, label='Actual Data')
|
| 81 |
+
# plt.scatter(x_data_inv, y_fit_inv, label='Fit Data',color='red')
|
| 82 |
+
# # plt.line(x_data_inv, y_fit_inv, label=f'Fitted Hill Equation (Kd={Kd_fit:.2f}, n={n_fit:.2f})', color='red')
|
| 83 |
+
# plt.xlabel('Ligand Concentration')
|
| 84 |
+
# plt.ylabel('Fraction of Binding')
|
| 85 |
+
# plt.title('Fitting Hill Equation to Data')
|
| 86 |
+
# plt.legend()
|
| 87 |
+
# plt.show()
|
| 88 |
+
|
| 89 |
+
return y_fit,y_fit_inv,Kd_fit, n_fit
|
| 90 |
+
|
| 91 |
+
def data_output(channel,X,y,y_fit_inv,x_ext_data,y_fit_inv_ext):
|
| 92 |
+
fit_col = 'Fit_Data_'+channel
|
| 93 |
+
plot_df = pd.DataFrame()
|
| 94 |
+
|
| 95 |
+
plot_df[f'{channel}_Spends'] = X
|
| 96 |
+
|
| 97 |
+
plot_df['Date'] = df['Date']
|
| 98 |
+
plot_df['MAT'] = df['MAT']
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
y_fit_inv_v2 = []
|
| 103 |
+
for i in range(len(y_fit_inv)):
|
| 104 |
+
y_fit_inv_v2.append(y_fit_inv[i][0])
|
| 105 |
+
|
| 106 |
+
plot_df[fit_col] = y_fit_inv_v2
|
| 107 |
+
|
| 108 |
+
# adding extra data
|
| 109 |
+
|
| 110 |
+
y_fit_inv_v2_ext = []
|
| 111 |
+
for i in range(len(y_fit_inv_ext)):
|
| 112 |
+
y_fit_inv_v2_ext.append(y_fit_inv_ext[i][0])
|
| 113 |
+
|
| 114 |
+
# print(x_ext_data)
|
| 115 |
+
ext_df = pd.DataFrame()
|
| 116 |
+
ext_df[f'{channel}_Spends'] = x_ext_data
|
| 117 |
+
ext_df[fit_col] = y_fit_inv_v2_ext
|
| 118 |
+
|
| 119 |
+
ext_df['Date'] = [
|
| 120 |
+
np.datetime64('1950-01-01'),
|
| 121 |
+
np.datetime64('1950-06-15'),
|
| 122 |
+
np.datetime64('1950-12-31')
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
ext_df['MAT'] = ["ext","ext","ext"]
|
| 126 |
+
|
| 127 |
+
print(ext_df)
|
| 128 |
+
plot_df= plot_df.append(ext_df)
|
| 129 |
+
return plot_df
|
| 130 |
+
|
| 131 |
+
def input_data(df,spend_col,prospect_col):
|
| 132 |
+
X = np.array(df[spend_col].tolist())
|
| 133 |
+
y = np.array(df[prospect_col].tolist())
|
| 134 |
+
|
| 135 |
+
x_minmax = MinMaxScaler()
|
| 136 |
+
x_scaled = x_minmax.fit_transform(df[[spend_col]])
|
| 137 |
+
x_data = []
|
| 138 |
+
for i in range(len(x_scaled)):
|
| 139 |
+
x_data.append(x_scaled[i][0])
|
| 140 |
+
|
| 141 |
+
y_minmax = MinMaxScaler()
|
| 142 |
+
y_scaled = y_minmax.fit_transform(df[[prospect_col]])
|
| 143 |
+
y_data = []
|
| 144 |
+
for i in range(len(y_scaled)):
|
| 145 |
+
y_data.append(y_scaled[i][0])
|
| 146 |
+
|
| 147 |
+
return X,y,x_data,y_data,x_minmax,y_minmax
|
| 148 |
+
|
| 149 |
+
def extend_s_curve(x_max,x_minmax,y_minmax, Kd_fit, n_fit):
|
| 150 |
+
print(x_max)
|
| 151 |
+
x_ext_data = [x_max*1.2,x_max*1.3,x_max*1.5]
|
| 152 |
+
# x_ext_data = [1500000,2000000,2500000]
|
| 153 |
+
# x_ext_data = [x_max+100,x_max+200,x_max+5000]
|
| 154 |
+
x_scaled = x_minmax.transform(pd.DataFrame(x_ext_data))
|
| 155 |
+
x_data = []
|
| 156 |
+
for i in range(len(x_scaled)):
|
| 157 |
+
x_data.append(x_scaled[i][0])
|
| 158 |
+
|
| 159 |
+
print(x_data)
|
| 160 |
+
y_fit = hill_equation(x_data, Kd_fit, n_fit)
|
| 161 |
+
y_fit_inv = y_minmax.inverse_transform(np.array(y_fit).reshape(-1,1))
|
| 162 |
+
|
| 163 |
+
return x_ext_data,y_fit_inv
|
| 164 |
+
|
| 165 |
+
def fit_data(spend_col,prospect_col,channel):
|
| 166 |
+
### getting k and n parameters
|
| 167 |
+
temp_df = df[df[spend_col]>0]
|
| 168 |
+
temp_df.reset_index(inplace=True)
|
| 169 |
+
|
| 170 |
+
X,y,x_data,y_data,x_minmax,y_minmax = input_data(temp_df,spend_col,prospect_col)
|
| 171 |
+
y_fit, y_fit_inv, Kd_fit, n_fit = hill_func(x_data,y_data,x_minmax,y_minmax)
|
| 172 |
+
print('k: ',Kd_fit)
|
| 173 |
+
print('n: ', n_fit)
|
| 174 |
+
|
| 175 |
+
##### extend_s_curve
|
| 176 |
+
x_ext_data,y_fit_inv_ext= extend_s_curve(temp_df[spend_col].max(),x_minmax,y_minmax, Kd_fit, n_fit)
|
| 177 |
+
|
| 178 |
+
plot_df = data_output(channel,X,y,y_fit_inv,x_ext_data,y_fit_inv_ext)
|
| 179 |
+
return plot_df
|
| 180 |
+
|
| 181 |
+
plotly_data = fit_data(spend_cols[0],prospect_cols[0],channel_cols[0])
|
| 182 |
+
plotly_data.tail()
|
| 183 |
+
|
| 184 |
+
for i in range(1,13):
|
| 185 |
+
print(i)
|
| 186 |
+
pdf = fit_data(spend_cols[i],prospect_cols[i],channel_cols[i])
|
| 187 |
+
plotly_data = plotly_data.merge(pdf,on = ["Date","MAT"],how = "left")
|
| 188 |
+
|
| 189 |
+
def response_curves(channel,chart_typ):
|
| 190 |
+
if chart_typ == 'View Scattered Plot':
|
| 191 |
+
mode_f1 = "markers"
|
| 192 |
+
elif chart_typ == 'View Line Plot':
|
| 193 |
+
mode_f1 = "lines"
|
| 194 |
+
else:
|
| 195 |
+
mode_f1 = "lines+markers"
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
# Initialize the Plotly figure
|
| 199 |
+
fig = go.Figure()
|
| 200 |
+
|
| 201 |
+
x_col = channel+"_Spends"
|
| 202 |
+
y_col = "Fit_Data_"+channel
|
| 203 |
+
fig.add_trace(go.Scatter(
|
| 204 |
+
x=plotly_data.sort_values(by=x_col, ascending=True)[x_col],
|
| 205 |
+
y=plotly_data.sort_values(by=x_col, ascending=True)[y_col],
|
| 206 |
+
mode=mode_f1,
|
| 207 |
+
name=x_col.replace('_Spends', '')
|
| 208 |
+
))
|
| 209 |
+
|
| 210 |
+
fig.add_trace(go.Scatter(
|
| 211 |
+
x=plotly_data[plotly_data['Date'] == plotly_data['Date'].max()][x_col],
|
| 212 |
+
y=plotly_data[plotly_data['Date'] == plotly_data['Date'].max()][y_col],
|
| 213 |
+
mode='markers',
|
| 214 |
+
marker=dict(
|
| 215 |
+
size=13 # Adjust the size value to make the markers larger or smaller
|
| 216 |
+
, color = 'green'
|
| 217 |
+
),
|
| 218 |
+
name="Current Spends"
|
| 219 |
+
))
|
| 220 |
+
|
| 221 |
+
# Update layout with titles
|
| 222 |
+
fig.update_layout(
|
| 223 |
+
title=channel+' Response Curve',
|
| 224 |
+
xaxis_title='Weekly Spends',
|
| 225 |
+
yaxis_title='Prospects'
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
# Show the figure
|
| 229 |
+
return fig
|
| 230 |
+
|
summary_df.pkl
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6e33a4c4ff46b7f8107d89facbd624828fae4b25965ede19b314805579134823
|
| 3 |
+
size 1822
|