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
|