Spaces:
Sleeping
Sleeping
Commit
·
3bd8465
1
Parent(s):
ab5f5e1
upload meta data and app files
Browse files- ecom_cvr_aov_by_month.csv +157 -0
- hs_logo.png +0 -0
- mCPC_dashboard.py +128 -0
- marginal_cpc_calculator.py +210 -0
- meta_daily_ads_level_data/meta_ad_day_level_raw_data.csv.gz +3 -0
- requirements.txt +7 -0
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
|