itheenigma commited on
Commit
3bd8465
·
1 Parent(s): ab5f5e1

upload meta data and app files

Browse files
ecom_cvr_aov_by_month.csv ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Country,CVR,AOV,PC,DATE
2
+ US,1.19%,671,28%,2022-08-01
3
+ DE,2.23%,449,28%,2022-08-01
4
+ NL,2.60%,400,28%,2022-08-01
5
+ AU,2.08%,632,28%,2022-08-01
6
+ SE,2.69%,419,28%,2022-08-01
7
+ FR,1.53%,434,28%,2022-08-01
8
+ JP,1.22%,410,28%,2022-08-01
9
+ BE,2.29%,475,28%,2022-08-01
10
+ IT,1.02%,469,28%,2022-08-01
11
+ CH,1.96%,684,28%,2022-08-01
12
+ AT,2.30%,545,28%,2022-08-01
13
+ NO,2.22%,684,28%,2022-08-01
14
+ DE,2.60%,517,28%,2022-09-01
15
+ NL,2.18%,472,28%,2022-09-01
16
+ US,2.56%,753,28%,2022-09-01
17
+ SE,2.12%,585,28%,2022-09-01
18
+ BE,2.15%,529,28%,2022-09-01
19
+ FR,1.82%,514,28%,2022-09-01
20
+ AU,1.83%,703,28%,2022-09-01
21
+ JP,0.91%,448,28%,2022-09-01
22
+ IT,1.27%,519,28%,2022-09-01
23
+ CH,2.03%,825,28%,2022-09-01
24
+ AT,1.99%,693,28%,2022-09-01
25
+ NO,2.39%,735,28%,2022-09-01
26
+ DE,2.81%,571,28%,2022-10-01
27
+ NL,1.93%,501,28%,2022-10-01
28
+ US,2.31%,900,28%,2022-10-01
29
+ SE,2.70%,453,28%,2022-10-01
30
+ BE,1.56%,789,28%,2022-10-01
31
+ AU,2.23%,774,28%,2022-10-01
32
+ IT,1.35%,536,28%,2022-10-01
33
+ FR,2.09%,566,28%,2022-10-01
34
+ AT,2.33%,761,28%,2022-10-01
35
+ CH,2.44%,966,28%,2022-10-01
36
+ JP,1.09%,438,28%,2022-10-01
37
+ NO,3.68%,567,28%,2022-10-01
38
+ DE,4.99%,460,28%,2022-11-01
39
+ NL,3.92%,378,28%,2022-11-01
40
+ US,4.09%,688,28%,2022-11-01
41
+ SE,3.93%,431,28%,2022-11-01
42
+ FR,3.49%,462,28%,2022-11-01
43
+ BE,3.90%,516,28%,2022-11-01
44
+ IT,2.81%,462,28%,2022-11-01
45
+ CH,3.79%,727,28%,2022-11-01
46
+ AU,4.23%,525,28%,2022-11-01
47
+ AT,5.05%,573,28%,2022-11-01
48
+ NO,4.24%,549,28%,2022-11-01
49
+ JP,1.56%,435,28%,2022-11-01
50
+ DE,3.70%,458,28%,2022-12-01
51
+ US,4.11%,649,28%,2022-12-01
52
+ NL,2.86%,436,28%,2022-12-01
53
+ FR,2.91%,478,28%,2022-12-01
54
+ BE,3.07%,502,28%,2022-12-01
55
+ SE,2.85%,461,28%,2022-12-01
56
+ IT,1.87%,500,28%,2022-12-01
57
+ CH,3.37%,698,28%,2022-12-01
58
+ AT,3.27%,553,28%,2022-12-01
59
+ AU,1.84%,585,28%,2022-12-01
60
+ JP,1.21%,521,28%,2022-12-01
61
+ NO,2.57%,526,28%,2022-12-01
62
+ DE,3.05%,495,28%,2023-01-01
63
+ NL,2.71%,448,28%,2023-01-01
64
+ US,3.05%,649,28%,2023-01-01
65
+ SE,1.95%,449,28%,2023-01-01
66
+ BE,2.72%,546,28%,2023-01-01
67
+ FR,2.12%,533,28%,2023-01-01
68
+ IT,1.37%,491,28%,2023-01-01
69
+ CH,3.03%,629,28%,2023-01-01
70
+ JP,1.02%,581,28%,2023-01-01
71
+ AT,2.81%,543,28%,2023-01-01
72
+ AU,2.36%,602,28%,2023-01-01
73
+ NO,1.21%,550,28%,2023-01-01
74
+ DE,3.42%,505,28%,2023-02-01
75
+ NL,3.09%,446,28%,2023-02-01
76
+ US,3.07%,664,28%,2023-02-01
77
+ SE,2.27%,487,28%,2023-02-01
78
+ IT,1.75%,504,28%,2023-02-01
79
+ FR,1.98%,502,28%,2023-02-01
80
+ BE,2.97%,504,28%,2023-02-01
81
+ CH,3.54%,708,28%,2023-02-01
82
+ JP,0.84%,605,28%,2023-02-01
83
+ AT,3.65%,582,28%,2023-02-01
84
+ AU,2.61%,586,28%,2023-02-01
85
+ NO,1.92%,532,28%,2023-02-01
86
+ DE,3.59%,513,28%,2023-03-01
87
+ NL,2.71%,445,28%,2023-03-01
88
+ US,3.05%,669,28%,2023-03-01
89
+ SE,2.49%,468,28%,2023-03-01
90
+ IT,1.61%,497,28%,2023-03-01
91
+ FR,1.86%,535,28%,2023-03-01
92
+ BE,3.05%,549,28%,2023-03-01
93
+ AU,2.26%,581,28%,2023-03-01
94
+ CH,3.68%,725,28%,2023-03-01
95
+ AT,4.26%,602,28%,2023-03-01
96
+ JP,0.97%,567,28%,2023-03-01
97
+ NO,1.37%,634,28%,2023-03-01
98
+ DE,3.08%,504,28%,2023-04-01
99
+ NL,2.80%,484,28%,2023-04-01
100
+ US,2.62%,722,28%,2023-04-01
101
+ SE,1.91%,523,28%,2023-04-01
102
+ FR,1.90%,567,28%,2023-04-01
103
+ IT,1.33%,534,28%,2023-04-01
104
+ BE,2.71%,602,28%,2023-04-01
105
+ AU,2.35%,631,28%,2023-04-01
106
+ AT,2.97%,585,28%,2023-04-01
107
+ CH,2.87%,742,28%,2023-04-01
108
+ JP,0.82%,707,28%,2023-04-01
109
+ NO,1.41%,644,28%,2023-04-01
110
+ DE,3.07%,471,28%,2023-05-01
111
+ NL,2.47%,469,28%,2023-05-01
112
+ US,2.71%,682,28%,2023-05-01
113
+ SE,1.98%,511,28%,2023-05-01
114
+ FR,1.77%,547,28%,2023-05-01
115
+ IT,1.42%,529,28%,2023-05-01
116
+ BE,3.00%,576,28%,2023-05-01
117
+ AU,1.97%,636,28%,2023-05-01
118
+ AT,2.81%,656,28%,2023-05-01
119
+ JP,0.93%,676,28%,2023-05-01
120
+ CH,2.40%,676,28%,2023-05-01
121
+ NO,1.80%,687,28%,2023-05-01
122
+ DE,2.52%,466,28%,2023-06-01
123
+ NL,2.62%,404,28%,2023-06-01
124
+ US,2.25%,622,28%,2023-06-01
125
+ SE,1.98%,453,28%,2023-06-01
126
+ FR,1.83%,524,28%,2023-06-01
127
+ BE,2.82%,521,28%,2023-06-01
128
+ IT,1.48%,519,28%,2023-06-01
129
+ AU,2.51%,576,28%,2023-06-01
130
+ JP,1.34%,575,28%,2023-06-01
131
+ AT,2.06%,569,28%,2023-06-01
132
+ CH,2.75%,761,28%,2023-06-01
133
+ NO,2.46%,623,28%,2023-06-01
134
+ DE,4.48%,432,28%,2023-07-01
135
+ NL,4.55%,397,28%,2023-07-01
136
+ US,4.11%,557,28%,2023-07-01
137
+ SE,2.82%,418,28%,2023-07-01
138
+ AU,4.53%,507,28%,2023-07-01
139
+ BE,4.12%,568,28%,2023-07-01
140
+ FR,3.48%,490,28%,2023-07-01
141
+ IT,2.55%,448,28%,2023-07-01
142
+ JP,2.32%,454,28%,2023-07-01
143
+ NO,3.07%,484,28%,2023-07-01
144
+ AT,3.98%,636,28%,2023-07-01
145
+ CH,3.37%,587,28%,2023-07-01
146
+ DE,3.03%,527,28%,2023-08-01
147
+ US,2.64%,704,28%,2023-08-01
148
+ AU,1.46%,582,28%,2023-08-01
149
+ NL,3.16%,475,28%,2023-08-01
150
+ SE,1.53%,520,28%,2023-08-01
151
+ FR,1.62%,620,28%,2023-08-01
152
+ IT,1.07%,551,28%,2023-08-01
153
+ BE,2.22%,550,28%,2023-08-01
154
+ AT,2.40%,593,28%,2023-08-01
155
+ CH,2.90%,740,28%,2023-08-01
156
+ NO,1.67%,641,28%,2023-08-01
157
+ JP,0.84%,642,28%,2023-08-01
hs_logo.png ADDED
mCPC_dashboard.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from PIL import Image
3
+ from marginal_cpc_calculator import *
4
+
5
+ st.set_page_config(
6
+ page_title="mCPC dashboard",
7
+ page_icon="socks",
8
+ layout="wide",
9
+ initial_sidebar_state="auto",
10
+ )
11
+
12
+ # dataframe operations first time
13
+ def impute_sub_channel(campaign):
14
+ if any([x in campaign for x in ['| Prospecting','| CPA','| Traffic','| CRM','Lead acquisition']]):
15
+ return 'TRAFFIC_DRIVING'
16
+ elif any([x in campaign for x in ['| Conversion','| Advantage+','| ASC+','Sales |','-Conversions']]):
17
+ return 'CONVERSION'
18
+ elif any([x in campaign for x in ['| Brand Awareness','| Reach']]):
19
+ return 'REACH'
20
+ else:
21
+ return 'OTHER'
22
+ df = pd.read_csv('meta_daily_ads_level_data/meta_ad_day_level_raw_data.csv.gz',compression='gzip',parse_dates=['Date'])
23
+ df.rename(columns={'Ad Set Name':'ad_set_name', 'Campaign Name':'campaign', 'Country':'country',
24
+ 'Date':'date', 'Amount Spent (SEK)':'cost', 'Clicks (all)':'clicks','Impressions':'impressions',
25
+ 'Purchases Conversion Value (SEK)':'platform_revenue','Ad Set Name':'ad_name',},inplace=True)
26
+ df.set_index('date',inplace=True)
27
+ df['cpc']=df['cost']/df['clicks']
28
+ df['platform']='meta'
29
+ df['sub_channel']=df.campaign.apply(impute_sub_channel)
30
+
31
+ # dashboard operations
32
+ def add_logo(logo_path, width, height):
33
+ """Read and return a resized logo"""
34
+ logo = Image.open(logo_path)
35
+ modified_logo = logo.resize((width, height))
36
+ return modified_logo
37
+
38
+ def get_date(input_min_date_month,input_min_date_year,input_max_date_month,input_max_date_year):
39
+ month_converter = {'Jun':'06','Jul':'07','Aug':'08','Sep':'09',
40
+ 'Oct':'10','Nov':'11','Dec':'12','Jan':'01','Feb':'02',
41
+ 'Mar':'03','Apr':'04','May':'05'}
42
+ min_date = input_min_date_year+'-'+month_converter[input_min_date_month]+'-01'
43
+ max_date = input_max_date_year+'-'+month_converter[input_max_date_month]+'-01'
44
+ return min_date,max_date
45
+
46
+ my_logo = add_logo(logo_path="hs_logo.png", width=180, height=60)
47
+ st.sidebar.image(my_logo)
48
+
49
+ st.title("mCPC & ad spend insights ✨")
50
+
51
+ # INLCUDE SECTION FOR INPUTS AND BUTTON TO GENERATE INSIGHTS
52
+ with st.sidebar:
53
+ st.write("Enter selections for mCPC spend profile")
54
+ input_country = st.selectbox('Country:',
55
+ ('','DE', 'US', 'UK','NL','SE','AU','CH','FR','BE'))
56
+ input_platform = st.selectbox('Platform:',
57
+ ('','meta'))
58
+ input_sub_channel = st.selectbox('Tactic / Sub channel:',
59
+ ('','CONVERSION','TRAFFIC_DRIVING','REACH'))
60
+ col1, col2 = st.columns(2)
61
+ with col1:
62
+ input_min_date_month = st.selectbox('From Month:',
63
+ ('','Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar','Apr','May'))
64
+ with col2:
65
+ input_min_date_year = st.selectbox('From Year:',
66
+ ('','2022','2023'))
67
+ col3, col4 = st.columns(2)
68
+ with col3:
69
+ input_max_date_month = st.selectbox('To Month:',
70
+ ('','Jun','Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar','Apr','May'))
71
+ with col4:
72
+ input_max_date_year = st.selectbox('To Year:',
73
+ ('','2022','2023'))
74
+ start = st.button("Generate mCPC curves", type='primary')
75
+ text_error_value = ''
76
+ if start:
77
+ for var,varname in [(input_country,'Country'),
78
+ (input_platform,'Platform'),
79
+ (input_min_date_month,'From Date'),
80
+ (input_min_date_year,'From Date'),
81
+ (input_max_date_month,'To Date'),
82
+ (input_max_date_year,'To Date')]:
83
+ if var=='':
84
+ text_error_value = f'{varname} cant be empty'
85
+ st.error(text_error_value)
86
+ break
87
+
88
+ # CHECKS TO ENSURE INPUTS ARE CLEAN BEFORE CALLING FUNCTIONS
89
+ if start:
90
+ if text_error_value=='':
91
+ min_date, max_date = get_date(input_min_date_month,
92
+ input_min_date_year,
93
+ input_max_date_month,
94
+ input_max_date_year)
95
+ st.markdown("""---""")
96
+ mid, mad = get_comparison_dates(min_date,max_date,period='previous_period')
97
+ st.subheader(f"Calculating for dates between {mid} & {mad}")
98
+ fig,output_msg = calculate_max_spend(df,mid,mad,
99
+ revenue_or_ebitda='revenue',
100
+ country=input_country,
101
+ platform=input_platform,
102
+ sub_channel=input_sub_channel,
103
+ pprint=True)
104
+ st.write(fig)
105
+ for l in output_msg:
106
+ st.write(l)
107
+ st.markdown("""---""")
108
+ mid, mad = get_comparison_dates(min_date,max_date,period='LY')
109
+ st.subheader(f"Calculating for dates between {mid} & {mad}")
110
+ fig,output_msg = calculate_max_spend(df,mid,mad,
111
+ revenue_or_ebitda='revenue',
112
+ country=input_country,
113
+ platform=input_platform,
114
+ sub_channel=input_sub_channel,
115
+ pprint=True)
116
+ st.write(fig)
117
+ for l in output_msg:
118
+ st.write(l)
119
+
120
+ else:
121
+ st.write(f'Text error value is {text_error_value}')
122
+ # DISPLAY INSIGHTS FOR PREVIOUS PERIOD AND LAST YEAR
123
+ # TEST FUNCTIONALITY OF APP
124
+ # DEPLOY TO HUGGINGFACE
125
+ # INCORPORATE GOOGLE
126
+ # INCORPORATE TIKTOK
127
+
128
+ st.markdown("<br><hr><center>Made with ❤️ for Happy Socks by <a href='mailto:vivekbharadwaj.ca@gmail.com?subject=mCPC dashboard queries&body=Please specify the issue you are facing with the dashboard.'><strong>Vivek</strong></a> ✨</center><hr>", unsafe_allow_html=True)
marginal_cpc_calculator.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from scipy.optimize import curve_fit
3
+ import matplotlib.pyplot as plt
4
+ import pandas as pd
5
+
6
+ def hello():
7
+ print ("world!")
8
+
9
+ def polynomial_func(x, a,b,c,d):
10
+ y = a * x - b * x**2 + c * x**3 + d
11
+ return y
12
+
13
+ def get_country_metrics(country,min_date='2023-07-01',max_date='2023-09-01'):
14
+ df_country = pd.read_csv('ecom_cvr_aov_by_month.csv',parse_dates=['DATE'])
15
+ df_country['CVR']=df_country['CVR'].apply(lambda x: x.replace("%","")).astype(float)/100
16
+ df_country['PC']=df_country['PC'].apply(lambda x: x.replace("%","")).astype(float)/100
17
+ if country in df_country.Country.unique():
18
+ return df_country[(df_country.Country==country) &
19
+ (df_country.DATE>=min_date) &
20
+ (df_country.DATE<max_date)].groupby('Country')[['CVR','AOV','PC']].mean().values[0]
21
+ else:
22
+ return df_country.loc[(df_country.DATE>=min_date) &
23
+ (df_country.DATE<max_date),['CVR','AOV','PC']].mean().values
24
+
25
+ def get_comparison_dates(min_date,max_date,period='previous_period'):
26
+ '''
27
+ period is either previous period or last year
28
+ '''
29
+ if period=='previous_period':
30
+ delta = (pd.to_datetime(max_date).to_period('M') - pd.to_datetime(min_date).to_period('M')).n
31
+ else:
32
+ delta = 12
33
+ mid,mad=(pd.DataFrame([pd.to_datetime(min_date), pd.to_datetime(max_date)]) - pd.DateOffset(months=delta)).values
34
+ return pd.to_datetime(mid[0]).strftime('%Y-%m-%d'),pd.to_datetime(mad[0]).strftime('%Y-%m-%d')
35
+
36
+
37
+ def calculate_max_spend(df,min_date,max_date,
38
+ revenue_or_ebitda='revenue',
39
+ country='DE',
40
+ platform='meta',
41
+ sub_channel='',
42
+ pprint=False):
43
+ # consider only ads with positive click outcomes and sort by cpc
44
+ if sub_channel=='':
45
+ df_temp = df[(df.clicks>0) & (df.country==country) &
46
+ (df.platform==platform) &
47
+ (df.index>min_date) & (df.index<max_date)
48
+ ].sort_values('cpc').reset_index()
49
+ else:
50
+ df_temp = df[(df.clicks>0) & (df.country==country) &
51
+ (df.platform==platform) &
52
+ (df.sub_channel==sub_channel) &
53
+ (df.index>min_date) & (df.index<max_date)
54
+ ].sort_values('cpc').reset_index()
55
+
56
+ # calculate cumulative spend and cumulative clicks
57
+ df_temp['cum_cost']=df_temp['cost'].cumsum()
58
+ df_temp['cum_clicks']=df_temp['clicks'].cumsum()
59
+ df_temp['cum_cpc']=df_temp['cum_cost']/df_temp['cum_clicks']
60
+
61
+ # calculate revenue and income
62
+ cvr,aov,pc = get_country_metrics(country,min_date,max_date)
63
+ df_temp['cum_revenue']=df_temp['cum_clicks']*cvr*aov
64
+ # at some stage, the ebitda becomes negative
65
+ if revenue_or_ebitda=='revenue':
66
+ df_temp['cum_income'] = df_temp['cum_revenue']-df_temp['cum_cost']
67
+ else:
68
+ df_temp['cum_income'] = df_temp['cum_revenue'] * pc - df_temp['cum_cost']
69
+
70
+ # calculate marginal cpc and marginal income
71
+ df_temp['marginal_cpc'] = (df_temp.cum_cost - df_temp.cum_cost.shift(1))/(df_temp.cum_clicks - df_temp.cum_clicks.shift(1))
72
+ df_temp['marginal_income'] = df_temp.cum_income - df_temp.cum_income.shift(1) - df_temp['marginal_cpc']
73
+
74
+ try:
75
+ params, cv = curve_fit(polynomial_func, df_temp['cum_cost'],df_temp['marginal_income'].fillna(0), p0=(1, 1,1,1))
76
+ a,b,c,d = params
77
+
78
+ # determine quality of the fit
79
+ squaredDiffs = np.square(df_temp['marginal_income'].fillna(0) - polynomial_func(df_temp['cum_cost'], a,b,c,d))
80
+ squaredDiffsFromMean = np.square(df_temp['marginal_income'].fillna(0) - np.mean(df_temp['marginal_income'].fillna(0)))
81
+ rSquared = 1 - np.sum(squaredDiffs) / np.sum(squaredDiffsFromMean)
82
+ output_msg=[]
83
+ if pprint==True:
84
+ output_msg.append(f"R² of fit for marginal income = {rSquared}")
85
+ # print(f"R² of fit for marginal income = {rSquared}")
86
+
87
+ # inspect the parameters
88
+ if pprint==True:
89
+ output_msg.append(f"Marginal income equation Y = {a} * x - {b} * x^2 + {c} * x^3 + {d}")
90
+
91
+ # calculate max costs when it becomes negative ROAS
92
+ df_marginal = pd.DataFrame(zip(np.arange(500000),
93
+ polynomial_func(np.arange(500000), a,b,c,d)
94
+ )).rename(columns={0:'cumulative_cost',1:'marginal_income'})
95
+ # join actuals to get the full picture
96
+ _ = df_temp[['cost','cpc','cum_cost', 'cum_clicks', 'cum_cpc', 'cum_revenue', 'cum_income']]
97
+ # converting cost to integer in order to join with the marginal dataset
98
+ _['cum_cost']=_['cum_cost'].apply(lambda x:np.round(x,0).astype(int))
99
+ df_marginal = pd.merge(df_marginal,
100
+ _.groupby('cum_cost')[
101
+ ['cost','cpc','cum_clicks', 'cum_cpc', 'cum_revenue', 'cum_income']
102
+ ].max().reset_index().rename(columns={#'cum_cost':'cumulative_cost',
103
+ 'cpc':'marginal_cpc'}),
104
+ how='left',left_on='cumulative_cost',right_on='cum_cost').fillna(method='ffill')
105
+ max_spend_threshold = df_marginal.loc[(df_marginal.marginal_income.shift(1)>=0) & (df_marginal.marginal_income.shift(-1)<0),'cumulative_cost'].min()
106
+ highest_amount_spent = df_marginal.cum_cost.max()
107
+ except:
108
+ if pprint==True:
109
+ output_msg.append("Something happened during marginal CPC and equation calculations. We couldn't calculate max_spend_threshold and highest_amount_spent...")
110
+ max_spend_threshold=np.nan
111
+ highest_amount_spent=np.nan
112
+ rSquared=np.nan
113
+ total_negative_roas_spend=np.nan
114
+
115
+ if pd.notnull(max_spend_threshold):
116
+ if (max_spend_threshold>highest_amount_spent):
117
+ marginal_cpc,cum_clicks,cum_cpc,cum_revenue = np.nan, np.nan, np.nan, np.nan
118
+ if pprint==True:
119
+ output_msg.append(f"Highest amount spent during this period is: {highest_amount_spent}")
120
+ else:
121
+ marginal_cpc,cum_clicks,cum_cpc,cum_revenue = df_marginal.loc[df_marginal.cumulative_cost==max_spend_threshold,
122
+ ['marginal_cpc','cum_clicks','cum_cpc','cum_revenue']].values[0]
123
+ # any spend beyond the max_spend_threshold needs to be saved
124
+ total_negative_roas_spend = df_temp.loc[df_temp.cum_cost>max_spend_threshold,['cost']].sum().values[0]
125
+ # # so to get the max cost for this tactic or strategy,
126
+ if pprint==True:
127
+ output_msg.append(f"Max spend threshold is: {max_spend_threshold}")
128
+ output_msg.append(f"For this spend, marginal_cpc={marginal_cpc}, cumulative_clicks={cum_clicks}, cumulative_cpc={cum_cpc}, cumulative_revenue={cum_revenue}")
129
+ output_msg.append(f"Total amount spent in negative ROAS={total_negative_roas_spend}")
130
+ print (output_msg)
131
+
132
+ plt.style.use('ggplot')
133
+ fig = plt.figure(figsize=(18,6))
134
+ ax1 = fig.add_subplot(121)
135
+ plt.plot(df_temp['cum_cost'],df_temp['marginal_income'], '.', label="data")
136
+ plt.plot(df_temp['cum_cost'], polynomial_func(df_temp['cum_cost'], a,b,c,d), '--', color='blue', label="fitted")
137
+ plt.xlabel('cumulative cost'); plt.ylabel('marginal income')
138
+ ax1.title.set_text('Net income at different spend levels')
139
+ ax2 = fig.add_subplot(122);
140
+ plt.scatter(df_temp['cum_cost'],df_temp['cpc']);
141
+ plt.axhline(1*cvr*aov,color='black',linestyle='--');plt.axhline(1*cvr*aov*pc,color='blue',linestyle='--');
142
+ plt.annotate(f'Any clicks above {np.round(cvr*aov,2)} SEK cpc has negative ROAS \n Above {np.round(cvr*aov*pc,2)} SEK is negative PC',size=12,xy=[0,60], color="black")
143
+ plt.xlabel('cumulative cost'); plt.ylabel('cpc')
144
+ # ax2.axes.get_xaxis().set_visible(False)
145
+ ax2.title.set_text('CPC for at different spend levels');
146
+ return fig,output_msg
147
+ else:
148
+ marginal_cpc,cum_clicks,cum_cpc,cum_revenue = np.nan, np.nan, np.nan, np.nan
149
+ total_negative_roas_spend=np.nan
150
+ if pprint==True:
151
+ print (f"Not enough data for mCPC analysis. Max threshold resulted in {max_spend_threshold}. Highest amount spent during this period is: {highest_amount_spent}")
152
+ # return df_marginal
153
+ return max_spend_threshold,highest_amount_spent,total_negative_roas_spend,marginal_cpc,cum_clicks,cum_cpc,cum_revenue,rSquared
154
+
155
+ def loop_mCPC_countries(df,min_date,max_date,
156
+ country_list=['UK', 'DE', 'US', 'NL', 'SE', 'CH', 'BE', 'EU', 'FR', 'AU'],
157
+ platform='meta'):
158
+ country_flag = ''
159
+ df_result = pd.DataFrame()
160
+
161
+ for i,d in df[(df.index>min_date) &
162
+ (df.index<max_date) & (df.platform==platform) &
163
+ (df.country.isin(country_list))].groupby(['country','sub_channel'])['cost'].sum().reset_index().iterrows():
164
+ if country_flag!=d['country']:
165
+ country_flag=d['country']
166
+ print (f"Processing {country_flag}...")
167
+
168
+ # run numbers for prior delta
169
+ mid, mad = get_comparison_dates(min_date,max_date,period='previous_period')
170
+ mcost = calculate_max_spend(
171
+ df,min_date=mid,max_date=mad,
172
+ revenue_or_ebitda='revenue',
173
+ country=d['country'],
174
+ platform=platform,
175
+ sub_channel=d['sub_channel'],
176
+ pprint=False)
177
+
178
+ # rerun values for LY this time
179
+ mid, mad = get_comparison_dates(min_date,max_date,period='LY')
180
+ mcost_ly = calculate_max_spend(
181
+ df,min_date=mid,max_date=mad,
182
+ revenue_or_ebitda='revenue',
183
+ country=d['country'],
184
+ platform=platform,
185
+ sub_channel=d['sub_channel'],
186
+ pprint=False)
187
+ # max_spend_threshold,highest_amount_spent,marginal_cpc,cum_clicks,cum_cpc,cum_revenue,rSquared = \
188
+ # combine the 2 results
189
+ df_result = pd.concat([df_result,
190
+ pd.Series({'platform':platform,
191
+ 'country':d['country'],
192
+ 'sub_channel':d['sub_channel'],
193
+ 'budget':'',
194
+ 'max_spend_PP':mcost[0],
195
+ 'amt_spent_PP':mcost[1],
196
+ 'rSquared_PP':mcost[6], # can you trust this mcpc analysis for this market?
197
+ 'max_spend_LY':mcost_ly[0],
198
+ 'amt_spent_LY':mcost_ly[1],
199
+ 'rSquared_LY':mcost_ly[6], # can you trust this mcpc analysis for this market?
200
+ 'cpc_PP':mcost[2],
201
+ 'cpc_LY':mcost_ly[2],
202
+ 'cum_clicks_PP':mcost[3],
203
+ 'cum_clicks_LY':mcost_ly[3],
204
+ 'cum_cpc_PP':mcost[4],
205
+ 'cum_cpc_LY':mcost_ly[4],
206
+ })],axis=1)
207
+ df_result = df_result.T
208
+ df_result['diff']=df_result['max_spend_PP']-df_result['amt_spent_PP']
209
+ print (f"Total impact due to unrealized revenue and negative ROAS spend between planned and threshold costs is {(df_result['diff'].apply(abs)*-1).sum()} SEK")
210
+ return df_result.sort_values('diff',ascending=True).reset_index(drop=True)
meta_daily_ads_level_data/meta_ad_day_level_raw_data.csv.gz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cb08473861a94a374e979a765c03e66a572e948379422d4bedaeb69223763c80
3
+ size 1982487
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ matplotlib==3.7.3
2
+ numpy==1.24.4
3
+ pandas==2.0.3
4
+ Pillow==9.5.0
5
+ requests==2.31.0
6
+ scipy==1.10.1
7
+ huggingface_hub==0.17.2