Script 1245: Tag Performance of Meta Ads
Purpose
The Python script tags Meta Ads campaigns at the ad level based on performance metrics, using specific business rules to determine if campaigns are “winning” or “losing.”
To Elaborate
The script is designed to evaluate and tag Meta Ads campaigns at the ad level by applying specific business rules to determine their performance status as either “winning” or “losing.” It uses metrics such as eCPM and CTR to assess the performance of campaigns based on their names and predefined thresholds. The script processes data from a pickled file, applies the business rules to categorize campaigns into dimensions like Reach, Traffic, and Conversion, and outputs the results. The business rules are defined in a dictionary, allowing for easy adjustments to the criteria used for tagging campaigns. This process helps in structured budget allocation (SBA) by identifying which campaigns are performing well and which are not, thereby aiding in strategic decision-making.
Walking Through the Code
- Initialization and Setup
- The script begins by determining whether it is running on a server or locally. If running locally, it loads data from a specified pickle file.
- It imports necessary libraries such as
pandas
,numpy
, and others for data manipulation and processing.
- Data Preparation
- The script sets up the input DataFrame (
inputDf
) from the loaded data and initializes the output DataFrame (outputDf
) with default values for Reach, Traffic, and Conversion dimensions.
- The script sets up the input DataFrame (
- Business Rules Definition
- Business rules are defined in a list of dictionaries, specifying keywords, metrics, thresholds, and the corresponding dimension values for “winning” and “losing” statuses.
- These rules are user-configurable, allowing adjustments to the keywords, metrics, and thresholds as needed.
- Performance Tagging
- The script calculates missing eCPM values using the formula:
(Pub. Cost / Impressions) * 1000
. - It applies the business rules to the input DataFrame, tagging each campaign based on its performance relative to the defined thresholds.
- The script calculates missing eCPM values using the formula:
- Output Generation
- The script uses a utility function to select and output the changed data, focusing on the specified columns for Reach, Traffic, and Conversion.
- If running locally, it writes the output and debug information to CSV files for further analysis.
Vitals
- Script ID : 1245
- Client ID / Customer ID: 1306920543 / 60268855
- Action Type: Bulk Upload
- Item Changed: Ad
- Output Columns: Account, Campaign, Group, Creative ID, Conversion, Reach, Traffic
- Linked Datasource: M1 Report
- Reference Datasource: None
- Owner: Michael Huang (mhuang@marinsoftware.com)
- Created by Michael Huang on 2024-07-01 00:30
- Last Updated by afarrokhi@marinsoftware.com on 2024-07-10 18:50
> See it in Action
Python Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
##
## name: Tag Performance of Meta Campaigns
## description: Tags the following Marin Dimensions based on performance
## - Dimensions: Reach, Traffic, Conversion
##
##
## author: Michael S. Huang
## created: 2024-06-19
##
########### START - Local Mode Config ###########
# Step 1: Uncomment download_preview_input flag and run Preview successfully with the Datasources you want
download_preview_input=False
# Step 2: In MarinOne, go to Scripts -> Preview -> Logs, download 'dataSourceDict' pickle file, and update pickle_path below
# pickle_path = ''
pickle_path = '/Users/mhuang/Downloads/pickle/windsor_fashions_meta_campaign_tagging_20240623.pkl'
# Step 3: Copy this script into local IDE with Python virtual env loaded with pandas and numpy.
# Step 4: Run locally with below code to init dataSourceDict
# determine if code is running on server or locally
def is_executing_on_server():
try:
# Attempt to access a known restricted builtin
dict_items = dataSourceDict.items()
return True
except NameError:
# NameError: dataSourceDict object is missing (indicating not on server)
return False
local_dev = False
if is_executing_on_server():
print("Code is executing on server. Skip init.")
elif len(pickle_path) > 3:
print("Code is NOT executing on server. Doing init.")
local_dev = True
# load dataSourceDict via pickled file
import pickle
dataSourceDict = pickle.load(open(pickle_path, 'rb'))
# print shape and first 5 rows for each entry in dataSourceDict
for key, value in dataSourceDict.items():
print(f"Shape of dataSourceDict[{key}]: {value.shape}")
# print(f"First 5 rows of dataSourceDict[{key}]:\n{value.head(5)}")
# set outputDf same as inputDf
inputDf = dataSourceDict["1"]
outputDf = inputDf.copy()
# setup timezone
import datetime
# Chicago Timezone is GMT-5. Adjust as needed.
CLIENT_TIMEZONE = datetime.timezone(datetime.timedelta(hours=-5))
# import pandas
import pandas as pd
import numpy as np
# other imports
import re
import urllib
# import Marin util functions
from marin_scripts_utils import tableize, select_changed
# pandas settings
pd.set_option('display.max_columns', None) # Display all columns
pd.set_option('display.max_colwidth', None) # Display full content of each column
else:
print("Running locally but no pickle path defined. dataSourceDict not loaded.")
exit(1)
########### END - Local Mode Setup ###########
today = datetime.datetime.now(CLIENT_TIMEZONE).date()
# primary data source and columns
inputDf = dataSourceDict["1"]
RPT_COL_CAMPAIGN = 'Campaign'
RPT_COL_PUBLISHER = 'Publisher'
RPT_COL_ACCOUNT = 'Account'
RPT_COL_GROUP = 'Group'
RPT_COL_CREATIVE_ID = 'Creative ID'
RPT_COL_CAMPAIGN_TYPE = 'Campaign Type'
RPT_COL_CAMPAIGN_STATUS = 'Campaign Status'
RPT_COL_IMPR = 'Impr.'
RPT_COL_CLICKS = 'Clicks'
RPT_COL_PUB_COST = 'Pub. Cost $'
RPT_COL_ECPM = 'eCPM $'
RPT_COL_CTR = 'CTR %'
RPT_COL_REACH = 'Reach'
RPT_COL_TRAFFIC = 'Traffic'
RPT_COL_CONVERSION = 'Conversion'
# output columns and initial values
BULK_COL_ACCOUNT = 'Account'
BULK_COL_CAMPAIGN = 'Campaign'
BULK_COL_GROUP = 'Group'
BULK_COL_CREATIVE_ID = 'Creative ID'
BULK_COL_REACH = 'Reach'
BULK_COL_TRAFFIC = 'Traffic'
BULK_COL_CONVERSION = 'Conversion'
outputDf[BULK_COL_REACH] = "<<YOUR VALUE>>"
outputDf[BULK_COL_TRAFFIC] = "<<YOUR VALUE>>"
outputDf[BULK_COL_CONVERSION] = "<<YOUR VALUE>>"
# user code starts here
### Business Rules as Configurable Params
# Define the business rules in a dictionary
business_rules = [
# 1. “Reach” dimension - campaign names containing “Evergreen” and “Reach” that perform below $2.21 CPM are “winning”, and if performing above $2.21 “losing”
{
"keywords": ["Evergreen", "Reach"],
"metric": RPT_COL_ECPM,
"threshold": 2.21,
"dimension": BULK_COL_REACH,
"value_high": "Losing",
"value_low": "Winning"
},
# 2. “Reach” dimension - campaign names containing “GNO” and “Reach” that perform below $2.21 CPM are “winning”, and if performing above $2.21 “losing”
{
"keywords": ["GNO", "Reach"],
"metric": RPT_COL_ECPM,
"threshold": 2.21,
"dimension": BULK_COL_REACH,
"value_high": "Losing",
"value_low": "Winning"
},
# 3. “Reach” dimension - campaign names containing “Formal” and “Reach” that perform below $2.36 CPM are “winning”, and if performing above $2.36 “losing”
{
"keywords": ["Formal", "Reach"],
"metric": RPT_COL_ECPM,
"threshold": 2.36,
"dimension": BULK_COL_REACH,
"value_high": "Losing",
"value_low": "Winning"
},
# 4. “Traffic” dimension - campaign names containing “Evergreen” and “Traffic” that perform below 0.62% CTR are “losing”, and if performing above 0.62% “winning”
{
"keywords": ["Evergreen", "Traffic"],
"metric": RPT_COL_CTR,
"threshold": 0.0062,
"dimension": BULK_COL_TRAFFIC,
"value_high": "Winning",
"value_low": "Losing"
},
# 5. “Traffic” dimension - campaign names containing “GNO” and “Traffic” that perform below 0.55% CTR are “losing”, and if performing above 0.55% CTR “winning”
{
"keywords": ["GNO", "Traffic"],
"metric": RPT_COL_CTR,
"threshold": 0.0055,
"dimension": BULK_COL_TRAFFIC,
"value_high": "Winning",
"value_low": "Losing"
},
# 6. “Traffic” dimension - campaign names containing “Formal” and “Traffic” that perform below 0.77% CTR are “losing”, and if performing above 0.77% CTR “winning”
{
"keywords": ["Formal", "Traffic"],
"metric": RPT_COL_CTR,
"threshold": 0.0077,
"dimension": BULK_COL_TRAFFIC,
"value_high": "Winning",
"value_low": "Losing"
},
# 7. “Conversion” dimension - campaign names containing “Evergreen” and “Conv” that perform below 0.33% CTR are “losing”, and if performing above 0.33% “winning”
{
"keywords": ["Evergreen", "Conv"],
"metric": RPT_COL_CTR,
"threshold": 0.0033,
"dimension": BULK_COL_CONVERSION,
"value_high": "Winning",
"value_low": "Losing"
},
# 8. “Conversion” dimension - campaign names containing “GNO” and “Conv” that perform below 0.27% CTR are “losing”, and if performing above 0.27% “winning”
{
"keywords": ["GNO", "Conv"],
"metric": RPT_COL_CTR,
"threshold": 0.0027,
"dimension": BULK_COL_CONVERSION,
"value_high": "Winning",
"value_low": "Losing"
},
# 9. “Conversion” dimension - campaign names containing “Formal” and “Conv” that perform below 0.45% CTR are “losing”, and if performing above 0.45% “winning”
{
"keywords": ["Formal", "Conv"],
"metric": RPT_COL_CTR,
"threshold": 0.0045,
"dimension": BULK_COL_CONVERSION,
"value_high": "Winning",
"value_low": "Losing"
}
]
print("inputDf.shape", inputDf.shape)
print("inputDf.dtypes", inputDf.dtypes)
print("inputDf sample", inputDf.head())
# fill in missing eCPM from Ads Perf report
inputDf[RPT_COL_ECPM] = np.round(inputDf[RPT_COL_PUB_COST] / inputDf[RPT_COL_IMPR] * 1000, 2)
originalDf = inputDf.copy()
# Apply the business rules to set dimensions
for rule in business_rules:
# Build the condition to filter DataFrame based on all keywords
condition = inputDf[RPT_COL_CAMPAIGN].apply(lambda x: all(keyword in x for keyword in rule["keywords"]))
# Apply the rule based on the metric threshold
inputDf.loc[condition & (inputDf[rule["metric"]] > rule["threshold"]), rule["dimension"]] = rule["value_high"]
inputDf.loc[condition & (inputDf[rule["metric"]] <= rule["threshold"]), rule["dimension"]] = rule["value_low"]
outputDf, debugDf = select_changed(inputDf,
originalDf,
diff_cols = [RPT_COL_REACH, RPT_COL_TRAFFIC, RPT_COL_CONVERSION],
select_cols = [RPT_COL_ACCOUNT, RPT_COL_CAMPAIGN, RPT_COL_GROUP, RPT_COL_CREATIVE_ID, RPT_COL_REACH, RPT_COL_TRAFFIC, RPT_COL_CONVERSION],
merged_cols = [RPT_COL_ACCOUNT, RPT_COL_CAMPAIGN, RPT_COL_GROUP, RPT_COL_CREATIVE_ID]
)
print("outputDf.shape", outputDf.shape)
### local debug
if local_dev:
output_filename = 'outputDf.csv'
outputDf.to_csv(output_filename, index=False)
print(f"Local Dev: Output written to: {output_filename}")
debug_filename = 'debugDf.csv'
debugDf.to_csv(debug_filename, index=False)
print(f"Local Dev: Debug written to: {debug_filename}")
Post generated on 2024-11-27 06:58:46 GMT