BlendMMM commited on
Commit
0ddfcf7
·
verified ·
1 Parent(s): bc33c17

Update utilities.py

Browse files
Files changed (1) hide show
  1. utilities.py +965 -965
utilities.py CHANGED
@@ -1,965 +1,965 @@
1
- from numerize.numerize import numerize
2
- import streamlit as st
3
- import pandas as pd
4
- import json
5
- from classes import Channel, Scenario
6
- import numpy as np
7
- from plotly.subplots import make_subplots
8
- import plotly.graph_objects as go
9
- from classes import class_to_dict
10
- from collections import OrderedDict
11
- import io
12
- import plotly
13
- from pathlib import Path
14
- import pickle
15
- import yaml
16
- from yaml import SafeLoader
17
- from streamlit.components.v1 import html
18
- import smtplib
19
- from scipy.optimize import curve_fit
20
- from sklearn.metrics import r2_score
21
- from classes import class_from_dict
22
- import os
23
- import base64
24
-
25
-
26
- color_palette = [
27
- "#F3F3F0",
28
- "#5E7D7E",
29
- "#2FA1FF",
30
- "#00EDED",
31
- "#00EAE4",
32
- "#304550",
33
- "#EDEBEB",
34
- "#7FBEFD",
35
- "#003059",
36
- "#A2F3F3",
37
- "#E1D6E2",
38
- "#B6B6B6",
39
- ]
40
-
41
-
42
- CURRENCY_INDICATOR = "$"
43
-
44
- import streamlit_authenticator as stauth
45
-
46
-
47
- def load_authenticator():
48
- with open("config.yaml") as file:
49
- config = yaml.load(file, Loader=SafeLoader)
50
- st.session_state["config"] = config
51
- authenticator = stauth.Authenticate(
52
- credentials=config["credentials"],
53
- cookie_name=config["cookie"]["name"],
54
- key=config["cookie"]["key"],
55
- cookie_expiry_days=config["cookie"]["expiry_days"],
56
- preauthorized=config["preauthorized"],
57
- )
58
- st.session_state["authenticator"] = authenticator
59
- return authenticator
60
-
61
-
62
- # Authentication
63
- def authentication():
64
- with open("config.yaml") as file:
65
- config = yaml.load(file, Loader=SafeLoader)
66
-
67
- authenticator = stauth.Authenticate(
68
- config["credentials"],
69
- config["cookie"]["name"],
70
- config["cookie"]["key"],
71
- config["cookie"]["expiry_days"],
72
- config["preauthorized"],
73
- )
74
-
75
- name, authentication_status, username = authenticator.login("Login", "main")
76
- return authenticator, name, authentication_status, username
77
-
78
-
79
- def nav_page(page_name, timeout_secs=3):
80
- nav_script = """
81
- <script type="text/javascript">
82
- function attempt_nav_page(page_name, start_time, timeout_secs) {
83
- var links = window.parent.document.getElementsByTagName("a");
84
- for (var i = 0; i < links.length; i++) {
85
- if (links[i].href.toLowerCase().endsWith("/" + page_name.toLowerCase())) {
86
- links[i].click();
87
- return;
88
- }
89
- }
90
- var elasped = new Date() - start_time;
91
- if (elasped < timeout_secs * 1000) {
92
- setTimeout(attempt_nav_page, 100, page_name, start_time, timeout_secs);
93
- } else {
94
- alert("Unable to navigate to page '" + page_name + "' after " + timeout_secs + " second(s).");
95
- }
96
- }
97
- window.addEventListener("load", function() {
98
- attempt_nav_page("%s", new Date(), %d);
99
- });
100
- </script>
101
- """ % (
102
- page_name,
103
- timeout_secs,
104
- )
105
- html(nav_script)
106
-
107
-
108
- # def load_local_css(file_name):
109
- # with open(file_name) as f:
110
- # st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
111
-
112
-
113
- # def set_header():
114
- # return st.markdown(f"""<div class='main-header'>
115
- # <h1>MMM LiME</h1>
116
- # <img src="https://assets-global.website-files.com/64c8fffb0e95cbc525815b79/64df84637f83a891c1473c51_Vector%20(Stroke).svg ">
117
- # </div>""", unsafe_allow_html=True)
118
-
119
- path = os.path.dirname(__file__)
120
-
121
- file_ = open(f"{path}/ALDI_2017.png", "rb")
122
-
123
- contents = file_.read()
124
-
125
- data_url = base64.b64encode(contents).decode("utf-8")
126
-
127
- file_.close()
128
-
129
-
130
- DATA_PATH = "./data"
131
-
132
- IMAGES_PATH = "./data/images_224_224"
133
-
134
-
135
- def load_local_css(file_name):
136
-
137
- with open(file_name) as f:
138
-
139
- st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
140
-
141
-
142
- # def set_header():
143
-
144
- # return st.markdown(f"""<div class='main-header'>
145
-
146
- # <h1>H & M Recommendations</h1>
147
-
148
- # <img src="data:image;base64,{data_url}", alt="Logo">
149
-
150
- # </div>""", unsafe_allow_html=True)
151
- path1 = os.path.dirname(__file__)
152
-
153
- file_1 = open(f"{path}/ALDI_2017.png", "rb")
154
-
155
- contents1 = file_1.read()
156
-
157
- data_url1 = base64.b64encode(contents1).decode("utf-8")
158
-
159
- file_1.close()
160
-
161
-
162
- DATA_PATH1 = "./data"
163
-
164
- IMAGES_PATH1 = "./data/images_224_224"
165
-
166
-
167
- def set_header():
168
- return st.markdown(
169
- f"""<div class='main-header'>
170
- <!-- <h1></h1> -->
171
- <div >
172
- <img class='blend-logo' src="data:image;base64,{data_url1}", alt="Logo">
173
- </div>""",
174
- unsafe_allow_html=True,
175
- )
176
-
177
-
178
- # def set_header():
179
- # logo_path = "./path/to/your/local/LIME_logo.png" # Replace with the actual file path
180
- # text = "LiME"
181
- # return st.markdown(f"""<div class='main-header'>
182
- # <img src="data:image/png;base64,{data_url}" alt="Logo" style="float: left; margin-right: 10px; width: 100px; height: auto;">
183
- # <h1>{text}</h1>
184
- # </div>""", unsafe_allow_html=True)
185
-
186
-
187
- def s_curve(x, K, b, a, x0):
188
- return K / (1 + b * np.exp(-a * (x - x0)))
189
-
190
-
191
- def panel_level(input_df, date_column="Date"):
192
- # Ensure 'Date' is set as the index
193
- if date_column not in input_df.index.names:
194
- input_df = input_df.set_index(date_column)
195
-
196
- # Select numeric columns only (excluding 'Date' since it's now the index)
197
- numeric_columns_df = input_df.select_dtypes(include="number")
198
-
199
- # Group by 'Date' (which is the index) and sum the numeric columns
200
- aggregated_df = numeric_columns_df.groupby(input_df.index).sum()
201
-
202
- # Reset index if you want 'Date' back as a column
203
- aggregated_df = aggregated_df.reset_index()
204
-
205
- return aggregated_df
206
-
207
-
208
- def initialize_data(
209
- panel=None, target_file=r"metrics_level_data\Overview_data_test_panel@#revenue.xlsx", updated_rcs=None, metrics=None
210
- ):
211
- # uopx_conv_rates = {'streaming_impressions' : 0.007,'digital_impressions' : 0.007,'search_clicks' : 0.00719,'tv_impressions' : 0.000173,
212
- # "digital_clicks":0.005,"streaming_clicks":0.004,'streaming_spends':1,"tv_spends":1,"search_spends":1,
213
- # "digital_spends":1}
214
- # print('State initialized')
215
-
216
- excel = pd.read_excel(target_file, sheet_name=None)
217
-
218
- # Extract dataframes for raw data, spend input, and contribution MMM
219
- raw_df = excel["RAW DATA MMM"]
220
- spend_df = excel["SPEND INPUT"]
221
- contri_df = excel["CONTRIBUTION MMM"]
222
-
223
- # Check if the panel is not None
224
- if panel is not None and panel != "Total Market":
225
- raw_df = raw_df[raw_df["Panel"] == panel].drop(columns=["Panel"])
226
- spend_df = spend_df[spend_df["Panel"] == panel].drop(columns=["Panel"])
227
- contri_df = contri_df[contri_df["Panel"] == panel].drop(columns=["Panel"])
228
- elif panel == "Total Market":
229
- raw_df = panel_level(raw_df, date_column="Date")
230
- spend_df = panel_level(spend_df, date_column="Week")
231
- contri_df = panel_level(contri_df, date_column="Date")
232
-
233
- # Revenue_df = excel['Revenue']
234
-
235
- ## remove sesonalities, indices etc ...
236
- exclude_columns = [
237
- "Date",
238
- "Region",
239
- "Controls_Grammarly_Index_SeasonalAVG",
240
- "Controls_Quillbot_Index",
241
- "Daily_Positive_Outliers",
242
- "External_RemoteClass_Index",
243
- "Intervals ON 20190520-20190805 | 20200518-20200803 | 20210517-20210802",
244
- "Intervals ON 20190826-20191209 | 20200824-20201207 | 20210823-20211206",
245
- "Intervals ON 20201005-20201019",
246
- "Promotion_PercentOff",
247
- "Promotion_TimeBased",
248
- "Seasonality_Indicator_Chirstmas",
249
- "Seasonality_Indicator_NewYears_Days",
250
- "Seasonality_Indicator_Thanksgiving",
251
- "Trend 20200302 / 20200803",
252
- ]
253
- raw_df["Date"] = pd.to_datetime(raw_df["Date"])
254
- contri_df["Date"] = pd.to_datetime(contri_df["Date"])
255
- input_df = raw_df.sort_values(by="Date")
256
- output_df = contri_df.sort_values(by="Date")
257
- spend_df["Week"] = pd.to_datetime(
258
- spend_df["Week"], format="%Y-%m-%d", errors="coerce"
259
- )
260
- spend_df.sort_values(by="Week", inplace=True)
261
-
262
- # spend_df['Week'] = pd.to_datetime(spend_df['Week'], errors='coerce')
263
- # spend_df = spend_df.sort_values(by='Week')
264
-
265
- channel_list = [col for col in input_df.columns if col not in exclude_columns]
266
- channel_list = list(set(channel_list) - set(["fb_level_achieved_tier_1", "ga_app"]))
267
-
268
- response_curves = {}
269
- mapes = {}
270
- rmses = {}
271
- upper_limits = {}
272
- powers = {}
273
- r2 = {}
274
- conv_rates = {}
275
- output_cols = []
276
- channels = {}
277
- sales = None
278
- dates = input_df.Date.values
279
- actual_output_dic = {}
280
- actual_input_dic = {}
281
-
282
- for inp_col in channel_list:
283
- # st.write(inp_col)
284
- spends = input_df[inp_col].values
285
- x = spends.copy()
286
- # upper limit for penalty
287
- upper_limits[inp_col] = 2 * x.max()
288
-
289
- # contribution
290
- out_col = [_col for _col in output_df.columns if _col.startswith(inp_col)][0]
291
- y = output_df[out_col].values.copy()
292
- actual_output_dic[inp_col] = y.copy()
293
- actual_input_dic[inp_col] = x.copy()
294
- ##output cols aggregation
295
- output_cols.append(out_col)
296
-
297
- ## scale the input
298
- power = np.ceil(np.log(x.max()) / np.log(10)) - 3
299
- if power >= 0:
300
- x = x / 10**power
301
-
302
- x = x.astype("float64")
303
- y = y.astype("float64")
304
- # print('#printing yyyyyyyyy')
305
- # print(inp_col)
306
- # print(x.max())
307
- # print(y.max())
308
- bounds = ((0, 0, 0, 0), (3 * y.max(), 1000, 1, x.max()))
309
-
310
- # bounds = ((y.max(), 3*y.max()),(0,1000),(0,1),(0,x.max()))
311
- params, _ = curve_fit(
312
- s_curve,
313
- x,
314
- y,
315
- p0=(2 * y.max(), 0.01, 1e-5, x.max()),
316
- bounds=bounds,
317
- maxfev=int(1e5),
318
- )
319
- mape = (100 * abs(1 - s_curve(x, *params) / y.clip(min=1))).mean()
320
- rmse = np.sqrt(((y - s_curve(x, *params)) ** 2).mean())
321
- r2_ = r2_score(y, s_curve(x, *params))
322
-
323
- response_curves[inp_col] = {
324
- "K": params[0],
325
- "b": params[1],
326
- "a": params[2],
327
- "x0": params[3],
328
- }
329
-
330
- updated_rcs_key = f"{metrics}#@{panel}#@{inp_col}"
331
- if updated_rcs is not None and updated_rcs_key in list(updated_rcs.keys()):
332
- response_curves[inp_col] = updated_rcs[updated_rcs_key]
333
-
334
- mapes[inp_col] = mape
335
- rmses[inp_col] = rmse
336
- r2[inp_col] = r2_
337
- powers[inp_col] = power
338
-
339
- ## conversion rates
340
- spend_col = [
341
- _col
342
- for _col in spend_df.columns
343
- if _col.startswith(inp_col.rsplit("_", 1)[0])
344
- ][0]
345
-
346
- # print('#printing spendssss')
347
- # print(spend_col)
348
- conv = (
349
- spend_df.set_index("Week")[spend_col]
350
- / input_df.set_index("Date")[inp_col].clip(lower=1)
351
- ).reset_index()
352
- conv.rename(columns={"index": "Week"}, inplace=True)
353
- conv["year"] = conv.Week.dt.year
354
- conv_rates[inp_col] = list(conv.drop("Week", axis=1).mean().to_dict().values())[
355
- 0
356
- ]
357
- ##print('Before',conv_rates[inp_col])
358
- # conv_rates[inp_col] = uopx_conv_rates[inp_col]
359
- ##print('After',(conv_rates[inp_col]))
360
-
361
- channel = Channel(
362
- name=inp_col,
363
- dates=dates,
364
- spends=spends,
365
- # conversion_rate = np.mean(list(conv_rates[inp_col].values())),
366
- conversion_rate=conv_rates[inp_col],
367
- response_curve_type="s-curve",
368
- response_curve_params={
369
- "K": params[0],
370
- "b": params[1],
371
- "a": params[2],
372
- "x0": params[3],
373
- },
374
- bounds=np.array([-10, 10]),
375
- )
376
- channels[inp_col] = channel
377
- if sales is None:
378
- sales = channel.actual_sales
379
- else:
380
- sales += channel.actual_sales
381
- other_contributions = (
382
- output_df.drop([*output_cols], axis=1).sum(axis=1, numeric_only=True).values
383
- )
384
- correction = output_df.drop("Date", axis=1).sum(axis=1).values - (
385
- sales + other_contributions
386
- )
387
- scenario = Scenario(
388
- name="default",
389
- channels=channels,
390
- constant=other_contributions,
391
- correction=correction,
392
- )
393
- ## setting session variables
394
- st.session_state["initialized"] = True
395
- st.session_state["actual_df"] = input_df
396
- st.session_state["raw_df"] = raw_df
397
- st.session_state["contri_df"] = output_df
398
- default_scenario_dict = class_to_dict(scenario)
399
- st.session_state["default_scenario_dict"] = default_scenario_dict
400
- st.session_state["scenario"] = scenario
401
- st.session_state["channels_list"] = channel_list
402
- st.session_state["optimization_channels"] = {
403
- channel_name: False for channel_name in channel_list
404
- }
405
- st.session_state["rcs"] = response_curves
406
-
407
- st.session_state["powers"] = powers
408
- st.session_state["actual_contribution_df"] = pd.DataFrame(actual_output_dic)
409
- st.session_state["actual_input_df"] = pd.DataFrame(actual_input_dic)
410
-
411
- for channel in channels.values():
412
- st.session_state[channel.name] = numerize(
413
- channel.actual_total_spends * channel.conversion_rate, 1
414
- )
415
-
416
- st.session_state["xlsx_buffer"] = io.BytesIO()
417
-
418
- if Path("../saved_scenarios.pkl").exists():
419
- with open("../saved_scenarios.pkl", "rb") as f:
420
- st.session_state["saved_scenarios"] = pickle.load(f)
421
- else:
422
- st.session_state["saved_scenarios"] = OrderedDict()
423
-
424
- # st.session_state["total_spends_change"] = 0
425
- st.session_state["optimization_channels"] = {
426
- channel_name: False for channel_name in channel_list
427
- }
428
- st.session_state["disable_download_button"] = True
429
-
430
-
431
- # def initialize_data():
432
- # # fetch data from excel
433
- # output = pd.read_excel('data.xlsx',sheet_name=None)
434
- # raw_df = output['RAW DATA MMM']
435
- # contribution_df = output['CONTRIBUTION MMM']
436
- # Revenue_df = output['Revenue']
437
-
438
- # ## channels to be shows
439
- # channel_list = []
440
- # for col in raw_df.columns:
441
- # if 'click' in col.lower() or 'spend' in col.lower() or 'imp' in col.lower():
442
- # ##print(col)
443
- # channel_list.append(col)
444
- # else:
445
- # pass
446
-
447
- # ## NOTE : Considered only Desktop spends for all calculations
448
- # acutal_df = raw_df[raw_df.Region == 'Desktop'].copy()
449
- # ## NOTE : Considered one year of data
450
- # acutal_df = acutal_df[acutal_df.Date>'2020-12-31']
451
- # actual_df = acutal_df.drop('Region',axis=1).sort_values(by='Date')[[*channel_list,'Date']]
452
-
453
- # ##load response curves
454
- # with open('./grammarly_response_curves.json','r') as f:
455
- # response_curves = json.load(f)
456
-
457
- # ## create channel dict for scenario creation
458
- # dates = actual_df.Date.values
459
- # channels = {}
460
- # rcs = {}
461
- # constant = 0.
462
- # for i,info_dict in enumerate(response_curves):
463
- # name = info_dict.get('name')
464
- # response_curve_type = info_dict.get('response_curve')
465
- # response_curve_params = info_dict.get('params')
466
- # rcs[name] = response_curve_params
467
- # if name != 'constant':
468
- # spends = actual_df[name].values
469
- # channel = Channel(name=name,dates=dates,
470
- # spends=spends,
471
- # response_curve_type=response_curve_type,
472
- # response_curve_params=response_curve_params,
473
- # bounds=np.array([-30,30]))
474
-
475
- # channels[name] = channel
476
- # else:
477
- # constant = info_dict.get('value',0.) * len(dates)
478
-
479
- # ## create scenario
480
- # scenario = Scenario(name='default', channels=channels, constant=constant)
481
- # default_scenario_dict = class_to_dict(scenario)
482
-
483
-
484
- # ## setting session variables
485
- # st.session_state['initialized'] = True
486
- # st.session_state['actual_df'] = actual_df
487
- # st.session_state['raw_df'] = raw_df
488
- # st.session_state['default_scenario_dict'] = default_scenario_dict
489
- # st.session_state['scenario'] = scenario
490
- # st.session_state['channels_list'] = channel_list
491
- # st.session_state['optimization_channels'] = {channel_name : False for channel_name in channel_list}
492
- # st.session_state['rcs'] = rcs
493
- # for channel in channels.values():
494
- # if channel.name not in st.session_state:
495
- # st.session_state[channel.name] = float(channel.actual_total_spends)
496
-
497
- # if 'xlsx_buffer' not in st.session_state:
498
- # st.session_state['xlsx_buffer'] = io.BytesIO()
499
-
500
- # ## for saving scenarios
501
- # if 'saved_scenarios' not in st.session_state:
502
- # if Path('../saved_scenarios.pkl').exists():
503
- # with open('../saved_scenarios.pkl','rb') as f:
504
- # st.session_state['saved_scenarios'] = pickle.load(f)
505
-
506
- # else:
507
- # st.session_state['saved_scenarios'] = OrderedDict()
508
-
509
- # if 'total_spends_change' not in st.session_state:
510
- # st.session_state['total_spends_change'] = 0
511
-
512
- # if 'optimization_channels' not in st.session_state:
513
- # st.session_state['optimization_channels'] = {channel_name : False for channel_name in channel_list}
514
-
515
- # if 'disable_download_button' not in st.session_state:
516
- # st.session_state['disable_download_button'] = True
517
-
518
-
519
- def create_channel_summary(scenario):
520
-
521
- # Provided data
522
- data = {
523
- "Channel": [
524
- "Paid Search",
525
- "Ga will cid baixo risco",
526
- "Digital tactic others",
527
- "Fb la tier 1",
528
- "Fb la tier 2",
529
- "Paid social others",
530
- "Programmatic",
531
- "Kwai",
532
- "Indicacao",
533
- "Infleux",
534
- "Influencer",
535
- ],
536
- "Spends": [
537
- "$ 11.3K",
538
- "$ 155.2K",
539
- "$ 50.7K",
540
- "$ 125.4K",
541
- "$ 125.2K",
542
- "$ 105K",
543
- "$ 3.3M",
544
- "$ 47.5K",
545
- "$ 55.9K",
546
- "$ 632.3K",
547
- "$ 48.3K",
548
- ],
549
- "Revenue": [
550
- "558.0K",
551
- "3.5M",
552
- "5.2M",
553
- "3.1M",
554
- "3.1M",
555
- "2.1M",
556
- "20.8M",
557
- "1.6M",
558
- "728.4K",
559
- "22.9M",
560
- "4.8M",
561
- ],
562
- }
563
-
564
- # Create DataFrame
565
- df = pd.DataFrame(data)
566
-
567
- # Convert currency strings to numeric values
568
- df["Spends"] = (
569
- df["Spends"]
570
- .replace({"\$": "", "K": "*1e3", "M": "*1e6"}, regex=True)
571
- .map(pd.eval)
572
- .astype(int)
573
- )
574
- df["Revenue"] = (
575
- df["Revenue"]
576
- .replace({"\$": "", "K": "*1e3", "M": "*1e6"}, regex=True)
577
- .map(pd.eval)
578
- .astype(int)
579
- )
580
-
581
- # Calculate ROI
582
- df["ROI"] = (df["Revenue"] - df["Spends"]) / df["Spends"]
583
-
584
- # Format columns
585
- format_currency = lambda x: f"${x:,.1f}"
586
- format_roi = lambda x: f"{x:.1f}"
587
-
588
- df["Spends"] = [
589
- "$ 11.3K",
590
- "$ 155.2K",
591
- "$ 50.7K",
592
- "$ 125.4K",
593
- "$ 125.2K",
594
- "$ 105K",
595
- "$ 3.3M",
596
- "$ 47.5K",
597
- "$ 55.9K",
598
- "$ 632.3K",
599
- "$ 48.3K",
600
- ]
601
- df["Revenue"] = [
602
- "$ 536.3K",
603
- "$ 3.4M",
604
- "$ 5M",
605
- "$ 3M",
606
- "$ 3M",
607
- "$ 2M",
608
- "$ 20M",
609
- "$ 1.5M",
610
- "$ 7.1M",
611
- "$ 22M",
612
- "$ 4.6M",
613
- ]
614
- df["ROI"] = df["ROI"].apply(format_roi)
615
-
616
- return df
617
-
618
-
619
- # @st.cache(allow_output_mutation=True)
620
- # def create_contribution_pie(scenario):
621
- # #c1f7dc
622
- # colors_map = {col:color for col,color in zip(st.session_state['channels_list'],plotly.colors.n_colors(plotly.colors.hex_to_rgb('#BE6468'), plotly.colors.hex_to_rgb('#E7B8B7'),23))}
623
- # total_contribution_fig = make_subplots(rows=1, cols=2,subplot_titles=['Spends','Revenue'],specs=[[{"type": "pie"}, {"type": "pie"}]])
624
- # total_contribution_fig.add_trace(
625
- # go.Pie(labels=[channel_name_formating(channel_name) for channel_name in st.session_state['channels_list']] + ['Non Media'],
626
- # values= [round(scenario.channels[channel_name].actual_total_spends * scenario.channels[channel_name].conversion_rate,1) for channel_name in st.session_state['channels_list']] + [0],
627
- # marker=dict(colors = [plotly.colors.label_rgb(colors_map[channel_name]) for channel_name in st.session_state['channels_list']] + ['#F0F0F0']),
628
- # hole=0.3),
629
- # row=1, col=1)
630
-
631
- # total_contribution_fig.add_trace(
632
- # go.Pie(labels=[channel_name_formating(channel_name) for channel_name in st.session_state['channels_list']] + ['Non Media'],
633
- # values= [scenario.channels[channel_name].actual_total_sales for channel_name in st.session_state['channels_list']] + [scenario.correction.sum() + scenario.constant.sum()],
634
- # hole=0.3),
635
- # row=1, col=2)
636
-
637
- # total_contribution_fig.update_traces(textposition='inside',texttemplate='%{percent:.1%}')
638
- # total_contribution_fig.update_layout(uniformtext_minsize=12,title='Channel contribution', uniformtext_mode='hide')
639
- # return total_contribution_fig
640
-
641
- # @st.cache(allow_output_mutation=True)
642
-
643
- # def create_contribuion_stacked_plot(scenario):
644
- # weekly_contribution_fig = make_subplots(rows=1, cols=2,subplot_titles=['Spends','Revenue'],specs=[[{"type": "bar"}, {"type": "bar"}]])
645
- # raw_df = st.session_state['raw_df']
646
- # df = raw_df.sort_values(by='Date')
647
- # x = df.Date
648
- # weekly_spends_data = []
649
- # weekly_sales_data = []
650
- # for channel_name in st.session_state['channels_list']:
651
- # weekly_spends_data.append((go.Bar(x=x,
652
- # y=scenario.channels[channel_name].actual_spends * scenario.channels[channel_name].conversion_rate,
653
- # name=channel_name_formating(channel_name),
654
- # hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}",
655
- # legendgroup=channel_name)))
656
- # weekly_sales_data.append((go.Bar(x=x,
657
- # y=scenario.channels[channel_name].actual_sales,
658
- # name=channel_name_formating(channel_name),
659
- # hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
660
- # legendgroup=channel_name, showlegend=False)))
661
- # for _d in weekly_spends_data:
662
- # weekly_contribution_fig.add_trace(_d, row=1, col=1)
663
- # for _d in weekly_sales_data:
664
- # weekly_contribution_fig.add_trace(_d, row=1, col=2)
665
- # weekly_contribution_fig.add_trace(go.Bar(x=x,
666
- # y=scenario.constant + scenario.correction,
667
- # name='Non Media',
668
- # hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}"), row=1, col=2)
669
- # weekly_contribution_fig.update_layout(barmode='stack', title='Channel contribuion by week', xaxis_title='Date')
670
- # weekly_contribution_fig.update_xaxes(showgrid=False)
671
- # weekly_contribution_fig.update_yaxes(showgrid=False)
672
- # return weekly_contribution_fig
673
-
674
- # @st.cache(allow_output_mutation=True)
675
- # def create_channel_spends_sales_plot(channel):
676
- # if channel is not None:
677
- # x = channel.dates
678
- # _spends = channel.actual_spends * channel.conversion_rate
679
- # _sales = channel.actual_sales
680
- # channel_sales_spends_fig = make_subplots(specs=[[{"secondary_y": True}]])
681
- # channel_sales_spends_fig.add_trace(go.Bar(x=x, y=_sales,marker_color='#c1f7dc',name='Revenue', hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}"), secondary_y = False)
682
- # channel_sales_spends_fig.add_trace(go.Scatter(x=x, y=_spends,line=dict(color='#005b96'),name='Spends',hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}"), secondary_y = True)
683
- # channel_sales_spends_fig.update_layout(xaxis_title='Date',yaxis_title='Revenue',yaxis2_title='Spends ($)',title='Channel spends and Revenue week wise')
684
- # channel_sales_spends_fig.update_xaxes(showgrid=False)
685
- # channel_sales_spends_fig.update_yaxes(showgrid=False)
686
- # else:
687
- # raw_df = st.session_state['raw_df']
688
- # df = raw_df.sort_values(by='Date')
689
- # x = df.Date
690
- # scenario = class_from_dict(st.session_state['default_scenario_dict'])
691
- # _sales = scenario.constant + scenario.correction
692
- # channel_sales_spends_fig = make_subplots(specs=[[{"secondary_y": True}]])
693
- # channel_sales_spends_fig.add_trace(go.Bar(x=x, y=_sales,marker_color='#c1f7dc',name='Revenue', hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}"), secondary_y = False)
694
- # # channel_sales_spends_fig.add_trace(go.Scatter(x=x, y=_spends,line=dict(color='#15C39A'),name='Spends',hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}"), secondary_y = True)
695
- # channel_sales_spends_fig.update_layout(xaxis_title='Date',yaxis_title='Revenue',yaxis2_title='Spends ($)',title='Channel spends and Revenue week wise')
696
- # channel_sales_spends_fig.update_xaxes(showgrid=False)
697
- # channel_sales_spends_fig.update_yaxes(showgrid=False)
698
- # return channel_sales_spends_fig
699
-
700
-
701
- # Define a shared color palette
702
-
703
-
704
- def create_contribution_pie():
705
- color_palette = [
706
- "#F3F3F0",
707
- "#5E7D7E",
708
- "#2FA1FF",
709
- "#00EDED",
710
- "#00EAE4",
711
- "#304550",
712
- "#EDEBEB",
713
- "#7FBEFD",
714
- "#003059",
715
- "#A2F3F3",
716
- "#E1D6E2",
717
- "#B6B6B6",
718
- ]
719
- total_contribution_fig = make_subplots(
720
- rows=1,
721
- cols=2,
722
- subplot_titles=["Spends", "Revenue"],
723
- specs=[[{"type": "pie"}, {"type": "pie"}]],
724
- )
725
-
726
- channels_list = [
727
- "Paid Search",
728
- "Ga will cid baixo risco",
729
- "Digital tactic others",
730
- "Fb la tier 1",
731
- "Fb la tier 2",
732
- "Paid social others",
733
- "Programmatic",
734
- "Kwai",
735
- "Indicacao",
736
- "Infleux",
737
- "Influencer",
738
- "Non Media",
739
- ]
740
-
741
- # Assign colors from the limited palette to channels
742
- colors_map = {
743
- col: color_palette[i % len(color_palette)]
744
- for i, col in enumerate(channels_list)
745
- }
746
- colors_map["Non Media"] = color_palette[
747
- 5
748
- ] # Assign fixed green color for 'Non Media'
749
-
750
- # Hardcoded values for Spends and Revenue
751
- spends_values = [0.5, 3.36, 1.1, 2.7, 2.7, 2.27, 70.6, 1, 1, 13.7, 1, 0]
752
- revenue_values = [1, 4, 5, 3, 3, 2, 50.8, 1.5, 0.7, 13, 0, 16]
753
-
754
- # Add trace for Spends pie chart
755
- total_contribution_fig.add_trace(
756
- go.Pie(
757
- labels=[channel_name for channel_name in channels_list],
758
- values=spends_values,
759
- marker=dict(
760
- colors=[colors_map[channel_name] for channel_name in channels_list]
761
- ),
762
- hole=0.3,
763
- ),
764
- row=1,
765
- col=1,
766
- )
767
-
768
- # Add trace for Revenue pie chart
769
- total_contribution_fig.add_trace(
770
- go.Pie(
771
- labels=[channel_name for channel_name in channels_list],
772
- values=revenue_values,
773
- marker=dict(
774
- colors=[colors_map[channel_name] for channel_name in channels_list]
775
- ),
776
- hole=0.3,
777
- ),
778
- row=1,
779
- col=2,
780
- )
781
-
782
- total_contribution_fig.update_traces(
783
- textposition="inside", texttemplate="%{percent:.1%}"
784
- )
785
- total_contribution_fig.update_layout(
786
- uniformtext_minsize=12, title="Channel contribution", uniformtext_mode="hide"
787
- )
788
- return total_contribution_fig
789
-
790
-
791
- def create_contribuion_stacked_plot(scenario):
792
- weekly_contribution_fig = make_subplots(
793
- rows=1,
794
- cols=2,
795
- subplot_titles=["Spends", "Revenue"],
796
- specs=[[{"type": "bar"}, {"type": "bar"}]],
797
- )
798
- raw_df = st.session_state["raw_df"]
799
- df = raw_df.sort_values(by="Date")
800
- x = df.Date
801
- weekly_spends_data = []
802
- weekly_sales_data = []
803
-
804
- for i, channel_name in enumerate(st.session_state["channels_list"]):
805
- color = color_palette[i % len(color_palette)]
806
-
807
- weekly_spends_data.append(
808
- go.Bar(
809
- x=x,
810
- y=scenario.channels[channel_name].actual_spends
811
- * scenario.channels[channel_name].conversion_rate,
812
- name=channel_name_formating(channel_name),
813
- hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}",
814
- legendgroup=channel_name,
815
- marker_color=color,
816
- )
817
- )
818
-
819
- weekly_sales_data.append(
820
- go.Bar(
821
- x=x,
822
- y=scenario.channels[channel_name].actual_sales,
823
- name=channel_name_formating(channel_name),
824
- hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
825
- legendgroup=channel_name,
826
- showlegend=False,
827
- marker_color=color,
828
- )
829
- )
830
-
831
- for _d in weekly_spends_data:
832
- weekly_contribution_fig.add_trace(_d, row=1, col=1)
833
- for _d in weekly_sales_data:
834
- weekly_contribution_fig.add_trace(_d, row=1, col=2)
835
-
836
- weekly_contribution_fig.add_trace(
837
- go.Bar(
838
- x=x,
839
- y=scenario.constant + scenario.correction,
840
- name="Non Media",
841
- hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
842
- marker_color=color_palette[-1],
843
- ),
844
- row=1,
845
- col=2,
846
- )
847
-
848
- weekly_contribution_fig.update_layout(
849
- barmode="stack", title="Channel contribution by week", xaxis_title="Date"
850
- )
851
- weekly_contribution_fig.update_xaxes(showgrid=False)
852
- weekly_contribution_fig.update_yaxes(showgrid=False)
853
- return weekly_contribution_fig
854
-
855
-
856
- def create_channel_spends_sales_plot(channel):
857
- if channel is not None:
858
- x = channel.dates
859
- _spends = channel.actual_spends * channel.conversion_rate
860
- _sales = channel.actual_sales
861
- channel_sales_spends_fig = make_subplots(specs=[[{"secondary_y": True}]])
862
- channel_sales_spends_fig.add_trace(
863
- go.Bar(
864
- x=x,
865
- y=_sales,
866
- marker_color=color_palette[
867
- 3
868
- ], # You can choose a color from the palette
869
- name="Revenue",
870
- hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
871
- ),
872
- secondary_y=False,
873
- )
874
-
875
- channel_sales_spends_fig.add_trace(
876
- go.Scatter(
877
- x=x,
878
- y=_spends,
879
- line=dict(
880
- color=color_palette[2]
881
- ), # You can choose another color from the palette
882
- name="Spends",
883
- hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}",
884
- ),
885
- secondary_y=True,
886
- )
887
-
888
- channel_sales_spends_fig.update_layout(
889
- xaxis_title="Date",
890
- yaxis_title="Revenue",
891
- yaxis2_title="Spends ($)",
892
- title="Channel spends and Revenue week-wise",
893
- )
894
- channel_sales_spends_fig.update_xaxes(showgrid=False)
895
- channel_sales_spends_fig.update_yaxes(showgrid=False)
896
- else:
897
- raw_df = st.session_state["raw_df"]
898
- df = raw_df.sort_values(by="Date")
899
- x = df.Date
900
- scenario = class_from_dict(st.session_state["default_scenario_dict"])
901
- _sales = scenario.constant + scenario.correction
902
- channel_sales_spends_fig = make_subplots(specs=[[{"secondary_y": True}]])
903
- channel_sales_spends_fig.add_trace(
904
- go.Bar(
905
- x=x,
906
- y=_sales,
907
- marker_color=color_palette[
908
- 0
909
- ], # You can choose a color from the palette
910
- name="Revenue",
911
- hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
912
- ),
913
- secondary_y=False,
914
- )
915
-
916
- channel_sales_spends_fig.update_layout(
917
- xaxis_title="Date",
918
- yaxis_title="Revenue",
919
- yaxis2_title="Spends ($)",
920
- title="Channel spends and Revenue week-wise",
921
- )
922
- channel_sales_spends_fig.update_xaxes(showgrid=False)
923
- channel_sales_spends_fig.update_yaxes(showgrid=False)
924
-
925
- return channel_sales_spends_fig
926
-
927
-
928
- def format_numbers(value, n_decimals=1, include_indicator=True):
929
- if include_indicator:
930
- return f"{CURRENCY_INDICATOR} {numerize(value,n_decimals)}"
931
- else:
932
- return f"{numerize(value,n_decimals)}"
933
-
934
-
935
- def decimal_formater(num_string, n_decimals=1):
936
- parts = num_string.split(".")
937
- if len(parts) == 1:
938
- return num_string + "." + "0" * n_decimals
939
- else:
940
- to_be_padded = n_decimals - len(parts[-1])
941
- if to_be_padded > 0:
942
- return num_string + "0" * to_be_padded
943
- else:
944
- return num_string
945
-
946
-
947
- def channel_name_formating(channel_name):
948
- name_mod = channel_name.replace("_", " ")
949
- if name_mod.lower().endswith(" imp"):
950
- name_mod = name_mod.replace("Imp", "Spend")
951
- elif name_mod.lower().endswith(" clicks"):
952
- name_mod = name_mod.replace("Clicks", "Spend")
953
- return name_mod
954
-
955
-
956
- def send_email(email, message):
957
- s = smtplib.SMTP("smtp.gmail.com", 587)
958
- s.starttls()
959
- s.login("[email protected]", "jgydhpfusuremcol")
960
- s.sendmail("[email protected]", email, message)
961
- s.quit()
962
-
963
-
964
- if __name__ == "__main__":
965
- initialize_data()
 
1
+ from numerize.numerize import numerize
2
+ import streamlit as st
3
+ import pandas as pd
4
+ import json
5
+ from classes import Channel, Scenario
6
+ import numpy as np
7
+ from plotly.subplots import make_subplots
8
+ import plotly.graph_objects as go
9
+ from classes import class_to_dict
10
+ from collections import OrderedDict
11
+ import io
12
+ import plotly
13
+ from pathlib import Path
14
+ import pickle
15
+ import yaml
16
+ from yaml import SafeLoader
17
+ from streamlit.components.v1 import html
18
+ import smtplib
19
+ from scipy.optimize import curve_fit
20
+ from sklearn.metrics import r2_score
21
+ from classes import class_from_dict
22
+ import os
23
+ import base64
24
+
25
+
26
+ color_palette = [
27
+ "#F3F3F0",
28
+ "#5E7D7E",
29
+ "#2FA1FF",
30
+ "#00EDED",
31
+ "#00EAE4",
32
+ "#304550",
33
+ "#EDEBEB",
34
+ "#7FBEFD",
35
+ "#003059",
36
+ "#A2F3F3",
37
+ "#E1D6E2",
38
+ "#B6B6B6",
39
+ ]
40
+
41
+
42
+ CURRENCY_INDICATOR = "$"
43
+
44
+ import streamlit_authenticator as stauth
45
+
46
+
47
+ def load_authenticator():
48
+ with open("config.yaml") as file:
49
+ config = yaml.load(file, Loader=SafeLoader)
50
+ st.session_state["config"] = config
51
+ authenticator = stauth.Authenticate(
52
+ credentials=config["credentials"],
53
+ cookie_name=config["cookie"]["name"],
54
+ key=config["cookie"]["key"],
55
+ cookie_expiry_days=config["cookie"]["expiry_days"],
56
+ preauthorized=config["preauthorized"],
57
+ )
58
+ st.session_state["authenticator"] = authenticator
59
+ return authenticator
60
+
61
+
62
+ # Authentication
63
+ def authentication():
64
+ with open("config.yaml") as file:
65
+ config = yaml.load(file, Loader=SafeLoader)
66
+
67
+ authenticator = stauth.Authenticate(
68
+ config["credentials"],
69
+ config["cookie"]["name"],
70
+ config["cookie"]["key"],
71
+ config["cookie"]["expiry_days"],
72
+ config["preauthorized"],
73
+ )
74
+
75
+ name, authentication_status, username = authenticator.login("Login", "main")
76
+ return authenticator, name, authentication_status, username
77
+
78
+
79
+ def nav_page(page_name, timeout_secs=3):
80
+ nav_script = """
81
+ <script type="text/javascript">
82
+ function attempt_nav_page(page_name, start_time, timeout_secs) {
83
+ var links = window.parent.document.getElementsByTagName("a");
84
+ for (var i = 0; i < links.length; i++) {
85
+ if (links[i].href.toLowerCase().endsWith("/" + page_name.toLowerCase())) {
86
+ links[i].click();
87
+ return;
88
+ }
89
+ }
90
+ var elasped = new Date() - start_time;
91
+ if (elasped < timeout_secs * 1000) {
92
+ setTimeout(attempt_nav_page, 100, page_name, start_time, timeout_secs);
93
+ } else {
94
+ alert("Unable to navigate to page '" + page_name + "' after " + timeout_secs + " second(s).");
95
+ }
96
+ }
97
+ window.addEventListener("load", function() {
98
+ attempt_nav_page("%s", new Date(), %d);
99
+ });
100
+ </script>
101
+ """ % (
102
+ page_name,
103
+ timeout_secs,
104
+ )
105
+ html(nav_script)
106
+
107
+
108
+ # def load_local_css(file_name):
109
+ # with open(file_name) as f:
110
+ # st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
111
+
112
+
113
+ # def set_header():
114
+ # return st.markdown(f"""<div class='main-header'>
115
+ # <h1>MMM LiME</h1>
116
+ # <img src="https://assets-global.website-files.com/64c8fffb0e95cbc525815b79/64df84637f83a891c1473c51_Vector%20(Stroke).svg ">
117
+ # </div>""", unsafe_allow_html=True)
118
+
119
+ path = os.path.dirname(__file__)
120
+
121
+ file_ = open(f"{path}/ALDI_2017.png", "rb")
122
+
123
+ contents = file_.read()
124
+
125
+ data_url = base64.b64encode(contents).decode("utf-8")
126
+
127
+ file_.close()
128
+
129
+
130
+ DATA_PATH = "./data"
131
+
132
+ IMAGES_PATH = "./data/images_224_224"
133
+
134
+
135
+ def load_local_css(file_name):
136
+
137
+ with open(file_name) as f:
138
+
139
+ st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
140
+
141
+
142
+ # def set_header():
143
+
144
+ # return st.markdown(f"""<div class='main-header'>
145
+
146
+ # <h1>H & M Recommendations</h1>
147
+
148
+ # <img src="data:image;base64,{data_url}", alt="Logo">
149
+
150
+ # </div>""", unsafe_allow_html=True)
151
+ path1 = os.path.dirname(__file__)
152
+
153
+ file_1 = open(f"{path}/ALDI_2017.png", "rb")
154
+
155
+ contents1 = file_1.read()
156
+
157
+ data_url1 = base64.b64encode(contents1).decode("utf-8")
158
+
159
+ file_1.close()
160
+
161
+
162
+ DATA_PATH1 = "./data"
163
+
164
+ IMAGES_PATH1 = "./data/images_224_224"
165
+
166
+
167
+ def set_header():
168
+ return st.markdown(
169
+ f"""<div class='main-header'>
170
+ <!-- <h1></h1> -->
171
+ <div >
172
+ <img class='blend-logo' src="data:image;base64,{data_url1}", alt="Logo">
173
+ </div>""",
174
+ unsafe_allow_html=True,
175
+ )
176
+
177
+
178
+ # def set_header():
179
+ # logo_path = "./path/to/your/local/LIME_logo.png" # Replace with the actual file path
180
+ # text = "LiME"
181
+ # return st.markdown(f"""<div class='main-header'>
182
+ # <img src="data:image/png;base64,{data_url}" alt="Logo" style="float: left; margin-right: 10px; width: 100px; height: auto;">
183
+ # <h1>{text}</h1>
184
+ # </div>""", unsafe_allow_html=True)
185
+
186
+
187
+ def s_curve(x, K, b, a, x0):
188
+ return K / (1 + b * np.exp(-a * (x - x0)))
189
+
190
+
191
+ def panel_level(input_df, date_column="Date"):
192
+ # Ensure 'Date' is set as the index
193
+ if date_column not in input_df.index.names:
194
+ input_df = input_df.set_index(date_column)
195
+
196
+ # Select numeric columns only (excluding 'Date' since it's now the index)
197
+ numeric_columns_df = input_df.select_dtypes(include="number")
198
+
199
+ # Group by 'Date' (which is the index) and sum the numeric columns
200
+ aggregated_df = numeric_columns_df.groupby(input_df.index).sum()
201
+
202
+ # Reset index if you want 'Date' back as a column
203
+ aggregated_df = aggregated_df.reset_index()
204
+
205
+ return aggregated_df
206
+
207
+
208
+ def initialize_data(
209
+ panel=None, target_file="Overview_data_test_panel@#revenue.xlsx", updated_rcs=None, metrics=None
210
+ ):
211
+ # uopx_conv_rates = {'streaming_impressions' : 0.007,'digital_impressions' : 0.007,'search_clicks' : 0.00719,'tv_impressions' : 0.000173,
212
+ # "digital_clicks":0.005,"streaming_clicks":0.004,'streaming_spends':1,"tv_spends":1,"search_spends":1,
213
+ # "digital_spends":1}
214
+ # print('State initialized')
215
+
216
+ excel = pd.read_excel(target_file, sheet_name=None)
217
+
218
+ # Extract dataframes for raw data, spend input, and contribution MMM
219
+ raw_df = excel["RAW DATA MMM"]
220
+ spend_df = excel["SPEND INPUT"]
221
+ contri_df = excel["CONTRIBUTION MMM"]
222
+
223
+ # Check if the panel is not None
224
+ if panel is not None and panel != "Total Market":
225
+ raw_df = raw_df[raw_df["Panel"] == panel].drop(columns=["Panel"])
226
+ spend_df = spend_df[spend_df["Panel"] == panel].drop(columns=["Panel"])
227
+ contri_df = contri_df[contri_df["Panel"] == panel].drop(columns=["Panel"])
228
+ elif panel == "Total Market":
229
+ raw_df = panel_level(raw_df, date_column="Date")
230
+ spend_df = panel_level(spend_df, date_column="Week")
231
+ contri_df = panel_level(contri_df, date_column="Date")
232
+
233
+ # Revenue_df = excel['Revenue']
234
+
235
+ ## remove sesonalities, indices etc ...
236
+ exclude_columns = [
237
+ "Date",
238
+ "Region",
239
+ "Controls_Grammarly_Index_SeasonalAVG",
240
+ "Controls_Quillbot_Index",
241
+ "Daily_Positive_Outliers",
242
+ "External_RemoteClass_Index",
243
+ "Intervals ON 20190520-20190805 | 20200518-20200803 | 20210517-20210802",
244
+ "Intervals ON 20190826-20191209 | 20200824-20201207 | 20210823-20211206",
245
+ "Intervals ON 20201005-20201019",
246
+ "Promotion_PercentOff",
247
+ "Promotion_TimeBased",
248
+ "Seasonality_Indicator_Chirstmas",
249
+ "Seasonality_Indicator_NewYears_Days",
250
+ "Seasonality_Indicator_Thanksgiving",
251
+ "Trend 20200302 / 20200803",
252
+ ]
253
+ raw_df["Date"] = pd.to_datetime(raw_df["Date"])
254
+ contri_df["Date"] = pd.to_datetime(contri_df["Date"])
255
+ input_df = raw_df.sort_values(by="Date")
256
+ output_df = contri_df.sort_values(by="Date")
257
+ spend_df["Week"] = pd.to_datetime(
258
+ spend_df["Week"], format="%Y-%m-%d", errors="coerce"
259
+ )
260
+ spend_df.sort_values(by="Week", inplace=True)
261
+
262
+ # spend_df['Week'] = pd.to_datetime(spend_df['Week'], errors='coerce')
263
+ # spend_df = spend_df.sort_values(by='Week')
264
+
265
+ channel_list = [col for col in input_df.columns if col not in exclude_columns]
266
+ channel_list = list(set(channel_list) - set(["fb_level_achieved_tier_1", "ga_app"]))
267
+
268
+ response_curves = {}
269
+ mapes = {}
270
+ rmses = {}
271
+ upper_limits = {}
272
+ powers = {}
273
+ r2 = {}
274
+ conv_rates = {}
275
+ output_cols = []
276
+ channels = {}
277
+ sales = None
278
+ dates = input_df.Date.values
279
+ actual_output_dic = {}
280
+ actual_input_dic = {}
281
+
282
+ for inp_col in channel_list:
283
+ # st.write(inp_col)
284
+ spends = input_df[inp_col].values
285
+ x = spends.copy()
286
+ # upper limit for penalty
287
+ upper_limits[inp_col] = 2 * x.max()
288
+
289
+ # contribution
290
+ out_col = [_col for _col in output_df.columns if _col.startswith(inp_col)][0]
291
+ y = output_df[out_col].values.copy()
292
+ actual_output_dic[inp_col] = y.copy()
293
+ actual_input_dic[inp_col] = x.copy()
294
+ ##output cols aggregation
295
+ output_cols.append(out_col)
296
+
297
+ ## scale the input
298
+ power = np.ceil(np.log(x.max()) / np.log(10)) - 3
299
+ if power >= 0:
300
+ x = x / 10**power
301
+
302
+ x = x.astype("float64")
303
+ y = y.astype("float64")
304
+ # print('#printing yyyyyyyyy')
305
+ # print(inp_col)
306
+ # print(x.max())
307
+ # print(y.max())
308
+ bounds = ((0, 0, 0, 0), (3 * y.max(), 1000, 1, x.max()))
309
+
310
+ # bounds = ((y.max(), 3*y.max()),(0,1000),(0,1),(0,x.max()))
311
+ params, _ = curve_fit(
312
+ s_curve,
313
+ x,
314
+ y,
315
+ p0=(2 * y.max(), 0.01, 1e-5, x.max()),
316
+ bounds=bounds,
317
+ maxfev=int(1e5),
318
+ )
319
+ mape = (100 * abs(1 - s_curve(x, *params) / y.clip(min=1))).mean()
320
+ rmse = np.sqrt(((y - s_curve(x, *params)) ** 2).mean())
321
+ r2_ = r2_score(y, s_curve(x, *params))
322
+
323
+ response_curves[inp_col] = {
324
+ "K": params[0],
325
+ "b": params[1],
326
+ "a": params[2],
327
+ "x0": params[3],
328
+ }
329
+
330
+ updated_rcs_key = f"{metrics}#@{panel}#@{inp_col}"
331
+ if updated_rcs is not None and updated_rcs_key in list(updated_rcs.keys()):
332
+ response_curves[inp_col] = updated_rcs[updated_rcs_key]
333
+
334
+ mapes[inp_col] = mape
335
+ rmses[inp_col] = rmse
336
+ r2[inp_col] = r2_
337
+ powers[inp_col] = power
338
+
339
+ ## conversion rates
340
+ spend_col = [
341
+ _col
342
+ for _col in spend_df.columns
343
+ if _col.startswith(inp_col.rsplit("_", 1)[0])
344
+ ][0]
345
+
346
+ # print('#printing spendssss')
347
+ # print(spend_col)
348
+ conv = (
349
+ spend_df.set_index("Week")[spend_col]
350
+ / input_df.set_index("Date")[inp_col].clip(lower=1)
351
+ ).reset_index()
352
+ conv.rename(columns={"index": "Week"}, inplace=True)
353
+ conv["year"] = conv.Week.dt.year
354
+ conv_rates[inp_col] = list(conv.drop("Week", axis=1).mean().to_dict().values())[
355
+ 0
356
+ ]
357
+ ##print('Before',conv_rates[inp_col])
358
+ # conv_rates[inp_col] = uopx_conv_rates[inp_col]
359
+ ##print('After',(conv_rates[inp_col]))
360
+
361
+ channel = Channel(
362
+ name=inp_col,
363
+ dates=dates,
364
+ spends=spends,
365
+ # conversion_rate = np.mean(list(conv_rates[inp_col].values())),
366
+ conversion_rate=conv_rates[inp_col],
367
+ response_curve_type="s-curve",
368
+ response_curve_params={
369
+ "K": params[0],
370
+ "b": params[1],
371
+ "a": params[2],
372
+ "x0": params[3],
373
+ },
374
+ bounds=np.array([-10, 10]),
375
+ )
376
+ channels[inp_col] = channel
377
+ if sales is None:
378
+ sales = channel.actual_sales
379
+ else:
380
+ sales += channel.actual_sales
381
+ other_contributions = (
382
+ output_df.drop([*output_cols], axis=1).sum(axis=1, numeric_only=True).values
383
+ )
384
+ correction = output_df.drop("Date", axis=1).sum(axis=1).values - (
385
+ sales + other_contributions
386
+ )
387
+ scenario = Scenario(
388
+ name="default",
389
+ channels=channels,
390
+ constant=other_contributions,
391
+ correction=correction,
392
+ )
393
+ ## setting session variables
394
+ st.session_state["initialized"] = True
395
+ st.session_state["actual_df"] = input_df
396
+ st.session_state["raw_df"] = raw_df
397
+ st.session_state["contri_df"] = output_df
398
+ default_scenario_dict = class_to_dict(scenario)
399
+ st.session_state["default_scenario_dict"] = default_scenario_dict
400
+ st.session_state["scenario"] = scenario
401
+ st.session_state["channels_list"] = channel_list
402
+ st.session_state["optimization_channels"] = {
403
+ channel_name: False for channel_name in channel_list
404
+ }
405
+ st.session_state["rcs"] = response_curves
406
+
407
+ st.session_state["powers"] = powers
408
+ st.session_state["actual_contribution_df"] = pd.DataFrame(actual_output_dic)
409
+ st.session_state["actual_input_df"] = pd.DataFrame(actual_input_dic)
410
+
411
+ for channel in channels.values():
412
+ st.session_state[channel.name] = numerize(
413
+ channel.actual_total_spends * channel.conversion_rate, 1
414
+ )
415
+
416
+ st.session_state["xlsx_buffer"] = io.BytesIO()
417
+
418
+ if Path("../saved_scenarios.pkl").exists():
419
+ with open("../saved_scenarios.pkl", "rb") as f:
420
+ st.session_state["saved_scenarios"] = pickle.load(f)
421
+ else:
422
+ st.session_state["saved_scenarios"] = OrderedDict()
423
+
424
+ # st.session_state["total_spends_change"] = 0
425
+ st.session_state["optimization_channels"] = {
426
+ channel_name: False for channel_name in channel_list
427
+ }
428
+ st.session_state["disable_download_button"] = True
429
+
430
+
431
+ # def initialize_data():
432
+ # # fetch data from excel
433
+ # output = pd.read_excel('data.xlsx',sheet_name=None)
434
+ # raw_df = output['RAW DATA MMM']
435
+ # contribution_df = output['CONTRIBUTION MMM']
436
+ # Revenue_df = output['Revenue']
437
+
438
+ # ## channels to be shows
439
+ # channel_list = []
440
+ # for col in raw_df.columns:
441
+ # if 'click' in col.lower() or 'spend' in col.lower() or 'imp' in col.lower():
442
+ # ##print(col)
443
+ # channel_list.append(col)
444
+ # else:
445
+ # pass
446
+
447
+ # ## NOTE : Considered only Desktop spends for all calculations
448
+ # acutal_df = raw_df[raw_df.Region == 'Desktop'].copy()
449
+ # ## NOTE : Considered one year of data
450
+ # acutal_df = acutal_df[acutal_df.Date>'2020-12-31']
451
+ # actual_df = acutal_df.drop('Region',axis=1).sort_values(by='Date')[[*channel_list,'Date']]
452
+
453
+ # ##load response curves
454
+ # with open('./grammarly_response_curves.json','r') as f:
455
+ # response_curves = json.load(f)
456
+
457
+ # ## create channel dict for scenario creation
458
+ # dates = actual_df.Date.values
459
+ # channels = {}
460
+ # rcs = {}
461
+ # constant = 0.
462
+ # for i,info_dict in enumerate(response_curves):
463
+ # name = info_dict.get('name')
464
+ # response_curve_type = info_dict.get('response_curve')
465
+ # response_curve_params = info_dict.get('params')
466
+ # rcs[name] = response_curve_params
467
+ # if name != 'constant':
468
+ # spends = actual_df[name].values
469
+ # channel = Channel(name=name,dates=dates,
470
+ # spends=spends,
471
+ # response_curve_type=response_curve_type,
472
+ # response_curve_params=response_curve_params,
473
+ # bounds=np.array([-30,30]))
474
+
475
+ # channels[name] = channel
476
+ # else:
477
+ # constant = info_dict.get('value',0.) * len(dates)
478
+
479
+ # ## create scenario
480
+ # scenario = Scenario(name='default', channels=channels, constant=constant)
481
+ # default_scenario_dict = class_to_dict(scenario)
482
+
483
+
484
+ # ## setting session variables
485
+ # st.session_state['initialized'] = True
486
+ # st.session_state['actual_df'] = actual_df
487
+ # st.session_state['raw_df'] = raw_df
488
+ # st.session_state['default_scenario_dict'] = default_scenario_dict
489
+ # st.session_state['scenario'] = scenario
490
+ # st.session_state['channels_list'] = channel_list
491
+ # st.session_state['optimization_channels'] = {channel_name : False for channel_name in channel_list}
492
+ # st.session_state['rcs'] = rcs
493
+ # for channel in channels.values():
494
+ # if channel.name not in st.session_state:
495
+ # st.session_state[channel.name] = float(channel.actual_total_spends)
496
+
497
+ # if 'xlsx_buffer' not in st.session_state:
498
+ # st.session_state['xlsx_buffer'] = io.BytesIO()
499
+
500
+ # ## for saving scenarios
501
+ # if 'saved_scenarios' not in st.session_state:
502
+ # if Path('../saved_scenarios.pkl').exists():
503
+ # with open('../saved_scenarios.pkl','rb') as f:
504
+ # st.session_state['saved_scenarios'] = pickle.load(f)
505
+
506
+ # else:
507
+ # st.session_state['saved_scenarios'] = OrderedDict()
508
+
509
+ # if 'total_spends_change' not in st.session_state:
510
+ # st.session_state['total_spends_change'] = 0
511
+
512
+ # if 'optimization_channels' not in st.session_state:
513
+ # st.session_state['optimization_channels'] = {channel_name : False for channel_name in channel_list}
514
+
515
+ # if 'disable_download_button' not in st.session_state:
516
+ # st.session_state['disable_download_button'] = True
517
+
518
+
519
+ def create_channel_summary(scenario):
520
+
521
+ # Provided data
522
+ data = {
523
+ "Channel": [
524
+ "Paid Search",
525
+ "Ga will cid baixo risco",
526
+ "Digital tactic others",
527
+ "Fb la tier 1",
528
+ "Fb la tier 2",
529
+ "Paid social others",
530
+ "Programmatic",
531
+ "Kwai",
532
+ "Indicacao",
533
+ "Infleux",
534
+ "Influencer",
535
+ ],
536
+ "Spends": [
537
+ "$ 11.3K",
538
+ "$ 155.2K",
539
+ "$ 50.7K",
540
+ "$ 125.4K",
541
+ "$ 125.2K",
542
+ "$ 105K",
543
+ "$ 3.3M",
544
+ "$ 47.5K",
545
+ "$ 55.9K",
546
+ "$ 632.3K",
547
+ "$ 48.3K",
548
+ ],
549
+ "Revenue": [
550
+ "558.0K",
551
+ "3.5M",
552
+ "5.2M",
553
+ "3.1M",
554
+ "3.1M",
555
+ "2.1M",
556
+ "20.8M",
557
+ "1.6M",
558
+ "728.4K",
559
+ "22.9M",
560
+ "4.8M",
561
+ ],
562
+ }
563
+
564
+ # Create DataFrame
565
+ df = pd.DataFrame(data)
566
+
567
+ # Convert currency strings to numeric values
568
+ df["Spends"] = (
569
+ df["Spends"]
570
+ .replace({"\$": "", "K": "*1e3", "M": "*1e6"}, regex=True)
571
+ .map(pd.eval)
572
+ .astype(int)
573
+ )
574
+ df["Revenue"] = (
575
+ df["Revenue"]
576
+ .replace({"\$": "", "K": "*1e3", "M": "*1e6"}, regex=True)
577
+ .map(pd.eval)
578
+ .astype(int)
579
+ )
580
+
581
+ # Calculate ROI
582
+ df["ROI"] = (df["Revenue"] - df["Spends"]) / df["Spends"]
583
+
584
+ # Format columns
585
+ format_currency = lambda x: f"${x:,.1f}"
586
+ format_roi = lambda x: f"{x:.1f}"
587
+
588
+ df["Spends"] = [
589
+ "$ 11.3K",
590
+ "$ 155.2K",
591
+ "$ 50.7K",
592
+ "$ 125.4K",
593
+ "$ 125.2K",
594
+ "$ 105K",
595
+ "$ 3.3M",
596
+ "$ 47.5K",
597
+ "$ 55.9K",
598
+ "$ 632.3K",
599
+ "$ 48.3K",
600
+ ]
601
+ df["Revenue"] = [
602
+ "$ 536.3K",
603
+ "$ 3.4M",
604
+ "$ 5M",
605
+ "$ 3M",
606
+ "$ 3M",
607
+ "$ 2M",
608
+ "$ 20M",
609
+ "$ 1.5M",
610
+ "$ 7.1M",
611
+ "$ 22M",
612
+ "$ 4.6M",
613
+ ]
614
+ df["ROI"] = df["ROI"].apply(format_roi)
615
+
616
+ return df
617
+
618
+
619
+ # @st.cache(allow_output_mutation=True)
620
+ # def create_contribution_pie(scenario):
621
+ # #c1f7dc
622
+ # colors_map = {col:color for col,color in zip(st.session_state['channels_list'],plotly.colors.n_colors(plotly.colors.hex_to_rgb('#BE6468'), plotly.colors.hex_to_rgb('#E7B8B7'),23))}
623
+ # total_contribution_fig = make_subplots(rows=1, cols=2,subplot_titles=['Spends','Revenue'],specs=[[{"type": "pie"}, {"type": "pie"}]])
624
+ # total_contribution_fig.add_trace(
625
+ # go.Pie(labels=[channel_name_formating(channel_name) for channel_name in st.session_state['channels_list']] + ['Non Media'],
626
+ # values= [round(scenario.channels[channel_name].actual_total_spends * scenario.channels[channel_name].conversion_rate,1) for channel_name in st.session_state['channels_list']] + [0],
627
+ # marker=dict(colors = [plotly.colors.label_rgb(colors_map[channel_name]) for channel_name in st.session_state['channels_list']] + ['#F0F0F0']),
628
+ # hole=0.3),
629
+ # row=1, col=1)
630
+
631
+ # total_contribution_fig.add_trace(
632
+ # go.Pie(labels=[channel_name_formating(channel_name) for channel_name in st.session_state['channels_list']] + ['Non Media'],
633
+ # values= [scenario.channels[channel_name].actual_total_sales for channel_name in st.session_state['channels_list']] + [scenario.correction.sum() + scenario.constant.sum()],
634
+ # hole=0.3),
635
+ # row=1, col=2)
636
+
637
+ # total_contribution_fig.update_traces(textposition='inside',texttemplate='%{percent:.1%}')
638
+ # total_contribution_fig.update_layout(uniformtext_minsize=12,title='Channel contribution', uniformtext_mode='hide')
639
+ # return total_contribution_fig
640
+
641
+ # @st.cache(allow_output_mutation=True)
642
+
643
+ # def create_contribuion_stacked_plot(scenario):
644
+ # weekly_contribution_fig = make_subplots(rows=1, cols=2,subplot_titles=['Spends','Revenue'],specs=[[{"type": "bar"}, {"type": "bar"}]])
645
+ # raw_df = st.session_state['raw_df']
646
+ # df = raw_df.sort_values(by='Date')
647
+ # x = df.Date
648
+ # weekly_spends_data = []
649
+ # weekly_sales_data = []
650
+ # for channel_name in st.session_state['channels_list']:
651
+ # weekly_spends_data.append((go.Bar(x=x,
652
+ # y=scenario.channels[channel_name].actual_spends * scenario.channels[channel_name].conversion_rate,
653
+ # name=channel_name_formating(channel_name),
654
+ # hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}",
655
+ # legendgroup=channel_name)))
656
+ # weekly_sales_data.append((go.Bar(x=x,
657
+ # y=scenario.channels[channel_name].actual_sales,
658
+ # name=channel_name_formating(channel_name),
659
+ # hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
660
+ # legendgroup=channel_name, showlegend=False)))
661
+ # for _d in weekly_spends_data:
662
+ # weekly_contribution_fig.add_trace(_d, row=1, col=1)
663
+ # for _d in weekly_sales_data:
664
+ # weekly_contribution_fig.add_trace(_d, row=1, col=2)
665
+ # weekly_contribution_fig.add_trace(go.Bar(x=x,
666
+ # y=scenario.constant + scenario.correction,
667
+ # name='Non Media',
668
+ # hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}"), row=1, col=2)
669
+ # weekly_contribution_fig.update_layout(barmode='stack', title='Channel contribuion by week', xaxis_title='Date')
670
+ # weekly_contribution_fig.update_xaxes(showgrid=False)
671
+ # weekly_contribution_fig.update_yaxes(showgrid=False)
672
+ # return weekly_contribution_fig
673
+
674
+ # @st.cache(allow_output_mutation=True)
675
+ # def create_channel_spends_sales_plot(channel):
676
+ # if channel is not None:
677
+ # x = channel.dates
678
+ # _spends = channel.actual_spends * channel.conversion_rate
679
+ # _sales = channel.actual_sales
680
+ # channel_sales_spends_fig = make_subplots(specs=[[{"secondary_y": True}]])
681
+ # channel_sales_spends_fig.add_trace(go.Bar(x=x, y=_sales,marker_color='#c1f7dc',name='Revenue', hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}"), secondary_y = False)
682
+ # channel_sales_spends_fig.add_trace(go.Scatter(x=x, y=_spends,line=dict(color='#005b96'),name='Spends',hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}"), secondary_y = True)
683
+ # channel_sales_spends_fig.update_layout(xaxis_title='Date',yaxis_title='Revenue',yaxis2_title='Spends ($)',title='Channel spends and Revenue week wise')
684
+ # channel_sales_spends_fig.update_xaxes(showgrid=False)
685
+ # channel_sales_spends_fig.update_yaxes(showgrid=False)
686
+ # else:
687
+ # raw_df = st.session_state['raw_df']
688
+ # df = raw_df.sort_values(by='Date')
689
+ # x = df.Date
690
+ # scenario = class_from_dict(st.session_state['default_scenario_dict'])
691
+ # _sales = scenario.constant + scenario.correction
692
+ # channel_sales_spends_fig = make_subplots(specs=[[{"secondary_y": True}]])
693
+ # channel_sales_spends_fig.add_trace(go.Bar(x=x, y=_sales,marker_color='#c1f7dc',name='Revenue', hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}"), secondary_y = False)
694
+ # # channel_sales_spends_fig.add_trace(go.Scatter(x=x, y=_spends,line=dict(color='#15C39A'),name='Spends',hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}"), secondary_y = True)
695
+ # channel_sales_spends_fig.update_layout(xaxis_title='Date',yaxis_title='Revenue',yaxis2_title='Spends ($)',title='Channel spends and Revenue week wise')
696
+ # channel_sales_spends_fig.update_xaxes(showgrid=False)
697
+ # channel_sales_spends_fig.update_yaxes(showgrid=False)
698
+ # return channel_sales_spends_fig
699
+
700
+
701
+ # Define a shared color palette
702
+
703
+
704
+ def create_contribution_pie():
705
+ color_palette = [
706
+ "#F3F3F0",
707
+ "#5E7D7E",
708
+ "#2FA1FF",
709
+ "#00EDED",
710
+ "#00EAE4",
711
+ "#304550",
712
+ "#EDEBEB",
713
+ "#7FBEFD",
714
+ "#003059",
715
+ "#A2F3F3",
716
+ "#E1D6E2",
717
+ "#B6B6B6",
718
+ ]
719
+ total_contribution_fig = make_subplots(
720
+ rows=1,
721
+ cols=2,
722
+ subplot_titles=["Spends", "Revenue"],
723
+ specs=[[{"type": "pie"}, {"type": "pie"}]],
724
+ )
725
+
726
+ channels_list = [
727
+ "Paid Search",
728
+ "Ga will cid baixo risco",
729
+ "Digital tactic others",
730
+ "Fb la tier 1",
731
+ "Fb la tier 2",
732
+ "Paid social others",
733
+ "Programmatic",
734
+ "Kwai",
735
+ "Indicacao",
736
+ "Infleux",
737
+ "Influencer",
738
+ "Non Media",
739
+ ]
740
+
741
+ # Assign colors from the limited palette to channels
742
+ colors_map = {
743
+ col: color_palette[i % len(color_palette)]
744
+ for i, col in enumerate(channels_list)
745
+ }
746
+ colors_map["Non Media"] = color_palette[
747
+ 5
748
+ ] # Assign fixed green color for 'Non Media'
749
+
750
+ # Hardcoded values for Spends and Revenue
751
+ spends_values = [0.5, 3.36, 1.1, 2.7, 2.7, 2.27, 70.6, 1, 1, 13.7, 1, 0]
752
+ revenue_values = [1, 4, 5, 3, 3, 2, 50.8, 1.5, 0.7, 13, 0, 16]
753
+
754
+ # Add trace for Spends pie chart
755
+ total_contribution_fig.add_trace(
756
+ go.Pie(
757
+ labels=[channel_name for channel_name in channels_list],
758
+ values=spends_values,
759
+ marker=dict(
760
+ colors=[colors_map[channel_name] for channel_name in channels_list]
761
+ ),
762
+ hole=0.3,
763
+ ),
764
+ row=1,
765
+ col=1,
766
+ )
767
+
768
+ # Add trace for Revenue pie chart
769
+ total_contribution_fig.add_trace(
770
+ go.Pie(
771
+ labels=[channel_name for channel_name in channels_list],
772
+ values=revenue_values,
773
+ marker=dict(
774
+ colors=[colors_map[channel_name] for channel_name in channels_list]
775
+ ),
776
+ hole=0.3,
777
+ ),
778
+ row=1,
779
+ col=2,
780
+ )
781
+
782
+ total_contribution_fig.update_traces(
783
+ textposition="inside", texttemplate="%{percent:.1%}"
784
+ )
785
+ total_contribution_fig.update_layout(
786
+ uniformtext_minsize=12, title="Channel contribution", uniformtext_mode="hide"
787
+ )
788
+ return total_contribution_fig
789
+
790
+
791
+ def create_contribuion_stacked_plot(scenario):
792
+ weekly_contribution_fig = make_subplots(
793
+ rows=1,
794
+ cols=2,
795
+ subplot_titles=["Spends", "Revenue"],
796
+ specs=[[{"type": "bar"}, {"type": "bar"}]],
797
+ )
798
+ raw_df = st.session_state["raw_df"]
799
+ df = raw_df.sort_values(by="Date")
800
+ x = df.Date
801
+ weekly_spends_data = []
802
+ weekly_sales_data = []
803
+
804
+ for i, channel_name in enumerate(st.session_state["channels_list"]):
805
+ color = color_palette[i % len(color_palette)]
806
+
807
+ weekly_spends_data.append(
808
+ go.Bar(
809
+ x=x,
810
+ y=scenario.channels[channel_name].actual_spends
811
+ * scenario.channels[channel_name].conversion_rate,
812
+ name=channel_name_formating(channel_name),
813
+ hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}",
814
+ legendgroup=channel_name,
815
+ marker_color=color,
816
+ )
817
+ )
818
+
819
+ weekly_sales_data.append(
820
+ go.Bar(
821
+ x=x,
822
+ y=scenario.channels[channel_name].actual_sales,
823
+ name=channel_name_formating(channel_name),
824
+ hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
825
+ legendgroup=channel_name,
826
+ showlegend=False,
827
+ marker_color=color,
828
+ )
829
+ )
830
+
831
+ for _d in weekly_spends_data:
832
+ weekly_contribution_fig.add_trace(_d, row=1, col=1)
833
+ for _d in weekly_sales_data:
834
+ weekly_contribution_fig.add_trace(_d, row=1, col=2)
835
+
836
+ weekly_contribution_fig.add_trace(
837
+ go.Bar(
838
+ x=x,
839
+ y=scenario.constant + scenario.correction,
840
+ name="Non Media",
841
+ hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
842
+ marker_color=color_palette[-1],
843
+ ),
844
+ row=1,
845
+ col=2,
846
+ )
847
+
848
+ weekly_contribution_fig.update_layout(
849
+ barmode="stack", title="Channel contribution by week", xaxis_title="Date"
850
+ )
851
+ weekly_contribution_fig.update_xaxes(showgrid=False)
852
+ weekly_contribution_fig.update_yaxes(showgrid=False)
853
+ return weekly_contribution_fig
854
+
855
+
856
+ def create_channel_spends_sales_plot(channel):
857
+ if channel is not None:
858
+ x = channel.dates
859
+ _spends = channel.actual_spends * channel.conversion_rate
860
+ _sales = channel.actual_sales
861
+ channel_sales_spends_fig = make_subplots(specs=[[{"secondary_y": True}]])
862
+ channel_sales_spends_fig.add_trace(
863
+ go.Bar(
864
+ x=x,
865
+ y=_sales,
866
+ marker_color=color_palette[
867
+ 3
868
+ ], # You can choose a color from the palette
869
+ name="Revenue",
870
+ hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
871
+ ),
872
+ secondary_y=False,
873
+ )
874
+
875
+ channel_sales_spends_fig.add_trace(
876
+ go.Scatter(
877
+ x=x,
878
+ y=_spends,
879
+ line=dict(
880
+ color=color_palette[2]
881
+ ), # You can choose another color from the palette
882
+ name="Spends",
883
+ hovertemplate="Date:%{x}<br>Spend:%{y:$.2s}",
884
+ ),
885
+ secondary_y=True,
886
+ )
887
+
888
+ channel_sales_spends_fig.update_layout(
889
+ xaxis_title="Date",
890
+ yaxis_title="Revenue",
891
+ yaxis2_title="Spends ($)",
892
+ title="Channel spends and Revenue week-wise",
893
+ )
894
+ channel_sales_spends_fig.update_xaxes(showgrid=False)
895
+ channel_sales_spends_fig.update_yaxes(showgrid=False)
896
+ else:
897
+ raw_df = st.session_state["raw_df"]
898
+ df = raw_df.sort_values(by="Date")
899
+ x = df.Date
900
+ scenario = class_from_dict(st.session_state["default_scenario_dict"])
901
+ _sales = scenario.constant + scenario.correction
902
+ channel_sales_spends_fig = make_subplots(specs=[[{"secondary_y": True}]])
903
+ channel_sales_spends_fig.add_trace(
904
+ go.Bar(
905
+ x=x,
906
+ y=_sales,
907
+ marker_color=color_palette[
908
+ 0
909
+ ], # You can choose a color from the palette
910
+ name="Revenue",
911
+ hovertemplate="Date:%{x}<br>Revenue:%{y:$.2s}",
912
+ ),
913
+ secondary_y=False,
914
+ )
915
+
916
+ channel_sales_spends_fig.update_layout(
917
+ xaxis_title="Date",
918
+ yaxis_title="Revenue",
919
+ yaxis2_title="Spends ($)",
920
+ title="Channel spends and Revenue week-wise",
921
+ )
922
+ channel_sales_spends_fig.update_xaxes(showgrid=False)
923
+ channel_sales_spends_fig.update_yaxes(showgrid=False)
924
+
925
+ return channel_sales_spends_fig
926
+
927
+
928
+ def format_numbers(value, n_decimals=1, include_indicator=True):
929
+ if include_indicator:
930
+ return f"{CURRENCY_INDICATOR} {numerize(value,n_decimals)}"
931
+ else:
932
+ return f"{numerize(value,n_decimals)}"
933
+
934
+
935
+ def decimal_formater(num_string, n_decimals=1):
936
+ parts = num_string.split(".")
937
+ if len(parts) == 1:
938
+ return num_string + "." + "0" * n_decimals
939
+ else:
940
+ to_be_padded = n_decimals - len(parts[-1])
941
+ if to_be_padded > 0:
942
+ return num_string + "0" * to_be_padded
943
+ else:
944
+ return num_string
945
+
946
+
947
+ def channel_name_formating(channel_name):
948
+ name_mod = channel_name.replace("_", " ")
949
+ if name_mod.lower().endswith(" imp"):
950
+ name_mod = name_mod.replace("Imp", "Spend")
951
+ elif name_mod.lower().endswith(" clicks"):
952
+ name_mod = name_mod.replace("Clicks", "Spend")
953
+ return name_mod
954
+
955
+
956
+ def send_email(email, message):
957
+ s = smtplib.SMTP("smtp.gmail.com", 587)
958
+ s.starttls()
959
+ s.login("[email protected]", "jgydhpfusuremcol")
960
+ s.sendmail("[email protected]", email, message)
961
+ s.quit()
962
+
963
+
964
+ if __name__ == "__main__":
965
+ initialize_data()