Pragya Jatav commited on
Commit
85d2c7e
·
1 Parent(s): c84cfaa
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
- raw_data_mmm_df = pd.read_excel(file_selected, sheet_name="RAW DATA MMM")
110
 
111
- if "Panel" in raw_data_mmm_df.columns:
112
- panel = list(set(raw_data_mmm_df["Panel"]))
113
- else:
114
- raw_data_mmm_df = None
115
- panel = None
116
 
117
- return panel
118
 
119
- def rerun():
120
- st.rerun()
121
 
122
- metrics_selected='prospects'
123
 
124
- file_selected = (
125
- f"Overview_data_test_panel@#{metrics_selected}.xlsx"
126
- )
127
- panel_list = panel_fetch(file_selected)
128
 
129
- if "selected_markets" not in st.session_state:
130
- st.session_state['selected_markets']='DMA1'
131
 
132
 
133
- st.header('Overview of previous spends')
134
 
135
- selected_market= st.selectbox(
136
- "Select Markets",
137
- ["Total Market"] + panel_list
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
- st.metric(label='Spends', value=format_numbers(float(scenario.actual_total_spends)))
151
- ###print(f"##################### {scenario.actual_total_sales} ##################")
152
- with columns[1]:
153
- st.metric(label=target, value=format_numbers(float(scenario.actual_total_sales),include_indicator=False))
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
- st.markdown(actual_summary_df.style.set_table_styles(
163
- [{
164
- 'selector': 'th',
165
- 'props': [('background-color', '#FFFFF')]
166
- },
167
- {
168
- 'selector' : 'tr:nth-child(even)',
169
- 'props' : [('background-color', '#FFFFF')]
170
- }]).to_html(), unsafe_allow_html=True)
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.835992656835515,5425.282497616836
6
  1,-556.3423571615149,5516.629386071515
7
  1,-798.7293276068531,5445.739089056853
8
- 1,-831.8661367345003,5200.4253466845
9
- 1,-747.1985886525281,5374.302970882529
10
- 1,-420.26596056385733,5332.913056923857
11
  1,-271.1869770058056,5319.342549515806
12
- 1,36.61704801202177,5391.429887731978
13
  1,301.40268262302834,5389.612139710971
14
- 1,-149.4327829167596,5242.93423795676
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.2351337049313,5328.863885024932
21
  1,-354.554715570026,5403.077810960025
22
- 1,-19.42185301387599,5485.364920013876
23
  1,280.9211846086464,5590.702815091353
24
  1,219.92735776987683,5516.867885530122
25
- 1,781.0334815120623,5679.809801587939
26
- 1,1294.2147923458106,5794.62514015419
27
  1,738.501471567386,5867.577001832614
28
  1,796.9528952899409,5766.399026290058
29
- 1,415.426998269626,5870.755899660374
30
  1,786.9046031624202,5653.93211614758
31
- 1,699.8259613792034,5780.4945612307965
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.22562033747363,5514.875846412473
37
  1,-232.94753657288948,5380.036090195889
38
- 1,-468.2093499173443,5549.191483465345
39
- 1,-322.5520717213203,5460.3946979333205
40
  1,-286.06881459022316,4870.059378248223
41
  1,-567.8495337345976,5126.330691409597
42
  1,-178.17958404447836,4755.834189569478
43
- 1,-138.0179383988425,4914.4588403388425
44
- 1,-224.74888886520512,5285.073225435204
45
- 1,792.8627605627134,5620.162487447287
46
  1,1355.6289643675454,6164.484521602455
47
- 1,986.2797608661913,6162.196124983808
48
  1,1059.455769237742,6192.769529952258
49
  1,383.32346060172495,6147.518456028276
50
  1,-187.89672830752534,5715.406060937526
51
- 1,-212.61946644455293,5361.829613484553
52
  1,72.72524427662756,5103.391602309372
53
  1,-95.74246649852375,5238.581337104524
54
  1,-120.67574389038145,5559.6276727923805
55
- 1,-129.05511796418978,5484.72889985719
56
- 1,-225.81656994822333,5494.042520330224
57
- 1,-218.3729272379751,5302.365695685976
58
  1,-527.1306773658152,5229.707354409815
59
- 1,-787.2566332929828,5426.519151763983
60
- 1,-1039.0776762177748,5539.477859049774
61
  1,-753.3501980635592,5429.758980752559
62
  1,-357.5844211273052,5439.306041177304
63
- 1,-324.8985270979465,5678.245679517946
64
  1,-133.5001332835127,5695.581704533513
65
- 1,-45.999364494504334,5662.909688574504
66
- 1,-198.8982053530026,5516.533581953001
67
  1,-140.84226664971084,5403.844047839711
68
  1,-328.0694341550152,5409.443929865015
69
- 1,-471.0469150562021,5319.741307806202
70
  1,-340.9581299499314,4979.624243809932
71
  1,-451.5102744182759,4939.252369518276
72
- 1,-470.3738494522695,5272.673163112269
73
- 1,-241.20911973847979,5185.85509377848
74
- 1,208.8574132962849,5444.731311283715
75
  1,515.8201019324006,5531.571609717599
76
- 1,645.0637292085712,5567.486440531428
77
- 1,600.0432433501528,5726.386967019846
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.2335698491934,5141.959263320808
84
- 1,306.98931138977787,5080.857405947222
85
- 1,584.0043413540361,4984.766656686964
86
  1,320.81565350241544,4936.522939377584
87
- 1,90.34779668819192,5252.465610906807
88
  1,403.10225090216045,5224.36913916284
89
- 1,83.73958567298178,5191.899359654019
90
  1,-278.22837426408205,5013.4219235420815
91
- 1,-594.5906903171744,5002.829538211175
92
  1,-638.5744723089219,4758.680377859922
93
  1,-820.1630688997875,5052.951763736787
94
  1,-777.5222929965912,5052.983144825591
95
- 1,-937.3473140298447,5133.260108853845
96
- 1,-766.0759176046422,5175.758653442641
97
  1,-601.9624005578062,5336.127374237805
98
  1,-43.38206579649068,5912.821508406491
99
- 1,783.5557775286761,5872.092495641323
100
- 1,1048.3380060975596,5779.24874920244
101
  1,942.7156660498758,5657.687045620124
102
  1,459.9194845831371,5422.163026676863
103
  1,-457.0944462735897,5375.19352051359
104
- 1,-547.8520567101305,4949.85955425913
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:41027251e75b11465e5cec329b14a8e35d4791aa6fdbbc948a0f32b4c1acc286
3
- size 1886
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6e33a4c4ff46b7f8107d89facbd624828fae4b25965ede19b314805579134823
3
+ size 1822