Script 89: Tag AdGroups with Dynamic Action Value (Cross Client; DC Market=NL)

Purpose

Tag AdGroups with Dynamic Action value calculated based on mapped Strategy ROAS and historical ROAS with a 120-day lookback.

To Elaborate

The Python script solves the problem of tagging AdGroups with a dynamic action value. This value is calculated based on two factors: the mapped Strategy ROAS (Return on Ad Spend) and the historical ROAS with a 120-day lookback. The goal is to adjust bids for AdGroups to optimize future ROAS and bring it closer to the target ROAS.

Walking Through the Code

  1. Custom Parameters:
    • The script defines a dictionary called ROAS_BID_ADJUSTMENT_TABLE that maps the difference between historical ROAS and target ROAS to a bid adjustment percentage.
  2. Column Constants:
    • The script defines various column constants used for data manipulation and output.
  3. User Changeable Parameters:
    • The script initializes two variables, outputDf[BULK_COL_DASTRATEGY] and outputDf[BULK_COL_DAVALUE], with placeholder values.
  4. Strategy ROAS Assignment:
    • The script assigns a strategy ROAS value to each row in the input dataframe based on the current ROAS performance.
    • Rows with ROAS <= 2.75 are assigned the strategy “NoRevenue_ROAS_2.75” with a strategy ROAS of 2.75.
    • Rows with ROAS > 2.75 and <= 3.40 are assigned the strategy “Low_ROAS_3” with a strategy ROAS of 3.0.
    • Rows with ROAS > 3.40 and < 5.00 are assigned the strategy “Medium_ROAS_3.5” with a strategy ROAS of 3.5.
    • Rows with ROAS >= 5.00 are assigned the strategy “High_ROAS_5” with a strategy ROAS of 5.0.
  5. Target ROAS Calculation:
    • The script defines a function parse_roas that extracts the target ROAS from the strategy name in each row of the input dataframe.
    • The function splits the strategy name by spaces and assumes that the target ROAS is the last value.
    • The target ROAS is then assigned to the TARGET_ROAS column in the input dataframe.
  6. Bid Adjustment Calculation:
    • The script defines a function calc_bid_adj that calculates the bid adjustment based on the historical ROAS and target ROAS in each row of the input dataframe.
    • The function checks if the target ROAS is valid (not NaN or <= 0) and if the historical ROAS is greater than 0.
    • If both conditions are met, the script calculates the difference between the historical ROAS and target ROAS (roas_diff).
    • The roas_diff is then used to look up the bid adjustment percentage from the ROAS_BID_ADJUSTMENT_TABLE.
    • The bid adjustment is assigned to the TMP_DA_VALUE column in the input dataframe.
  7. Output Preparation:
    • The script fills any NaN values in the input dataframe with empty strings.
    • It identifies rows where the bid adjustment or strategy has changed compared to the original values.
    • The script creates a new dataframe outputDf with the changed rows and selected columns.
    • The column names are renamed to match the desired output format.
  8. Output Display:
    • If the outputDf dataframe is not empty, it is displayed in a tabular format.
    • Otherwise, a message indicating an empty output is displayed.
  9. Finished:
    • The script prints “Finished” to indicate the completion of the code execution.

Vitals

  • Script ID : 89
  • Client ID / Customer ID: 1306919857 / 64302
  • Action Type: Bulk Upload
  • Item Changed: AdGroup
  • Output Columns: Account, Campaign, Group, DA-Value, DA-Strategy
  • Linked Datasource: M1 Report
  • Reference Datasource: None
  • Owner: Michael Huang (mhuang@marinsoftware.com)
  • Created by Michael Huang on 2023-04-25 06:53
  • Last Updated by Grégory Pantaine on 2023-12-06 04:01
> 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
#
# Tag AdGroup with ROAS adj value to be applied by Dynamic Action
#
# Michael S. Huang
# Created: 2023-04-25
# Updated: 2023-07-04
#

##### Custom Parameters #########

# ROAS DIFF := Hist Roas - Target ROAS
# When Hist ROAS is above Target ROAS (ROAS DIFF > 0), need to push Bids UP 
# so that future ROAS comes down towards Target ROAS
ROAS_BID_ADJUSTMENT_TABLE = {
  # <ROAS DIFF>: <BID ADJ PERCENTAGE>
    -9: -50,  
    -8: -45,
    -7: -40,
    -6: -35,
    -5: -30,
    -4: -25,
    -3: -20,
    -2: -15,
    -1: -10,
    0: 0,
    1: 10,
    2: 12,
    3: 15,
    4: 20,
    5: 20,
    6: 25,
    7: 25,
    8: 30,
    9: 35,
    10: 40,
}

##### END Custom Parameters #####

RPT_COL_CLIENT = 'Client'
RPT_COL_CURRENCY = 'Currency'
RPT_COL_GROUP = 'Group'
RPT_COL_ACCOUNT = 'Account'
RPT_COL_CAMPAIGN = 'Campaign'
RPT_COL_DASTRATEGY = 'DA-Strategy'
RPT_COL_DAVALUE = 'DA-Value'
RPT_COL_STRATEGY = 'Strategy'
RPT_COL_ROAS = 'ROAS'
RPT_COL_REVENUE = 'Revenue £'
RPT_COL_PUB_COST = 'Pub. Cost £'
BULK_COL_ACCOUNT = 'Account'
BULK_COL_CAMPAIGN = 'Campaign'
BULK_COL_GROUP = 'Group'
BULK_COL_DASTRATEGY = 'DA-Strategy'
BULK_COL_DAVALUE = 'DA-Value'

outputDf[BULK_COL_DASTRATEGY] = "<<YOUR VALUE>>"
outputDf[BULK_COL_DAVALUE] = "<<YOUR VALUE>>"

TARGET_ROAS = 'target_roas'
STRATEGY_ROAS = 'strategy_roas'
TMP_DA_STRATEGY = RPT_COL_DASTRATEGY + '_'
TMP_DA_VALUE = RPT_COL_DAVALUE + '_'

# clear temp columns
inputDf[TARGET_ROAS] = np.nan
inputDf[STRATEGY_ROAS] = np.nan
inputDf[TMP_DA_STRATEGY] = np.nan
inputDf[TMP_DA_VALUE] = np.nan

### Assign Strategy ROAS based on current ROAS Performance

# ROAS 2.75
inputDf.loc[ inputDf[RPT_COL_ROAS] <= 2.75, \
             [TMP_DA_STRATEGY, STRATEGY_ROAS] ] \
            = ['NoRevenue_ROAS_2.75', 2.75]

# ROAS 3
inputDf.loc[ (inputDf[RPT_COL_ROAS] > 2.75) & \
             (inputDf[RPT_COL_ROAS] <= 3.40), \
             [TMP_DA_STRATEGY, STRATEGY_ROAS] ] \
            = ['Low_ROAS_3', 3.0]

# ROAS 3.5
inputDf.loc[ (inputDf[RPT_COL_ROAS] > 3.40) & \
             (inputDf[RPT_COL_ROAS] < 5.00), \
             [TMP_DA_STRATEGY, STRATEGY_ROAS] ] \
            = ['Medium_ROAS_3.5', 3.5] 

# ROAS 5
inputDf.loc[ inputDf[RPT_COL_ROAS] >= 5.00, \
             [TMP_DA_STRATEGY, STRATEGY_ROAS] ] \
            = ['High_ROAS_5', 5.0] 


### Get Target ROAS from mapped Strategy

def parse_roas(row):
    SEP = ' '
    vals = row[RPT_COL_STRATEGY].split(SEP)
    # assume Target ROAS is after last separator
    roas = np.nan
    try:
      roas = float(vals[-1])
    except:
      roas = np.nan

    return roas

inputDf[TARGET_ROAS] = inputDf.apply(parse_roas, axis=1)

#print(tableize(inputDf))

### Calc BID adjustment to move Historical ROAS towards Target ROAS

def calc_bid_adj(row):
    # historical ROAS
    from_roas = row[RPT_COL_ROAS]
    # desired target ROAS (parsed from Strategy name)
    to_roas = row[TARGET_ROAS]
    adj = np.nan

    if (np.isnan(to_roas)) or (to_roas <= 0):
      # Target ROAS not parsed correctly, don't adjust
      adj = 0
    elif (from_roas > 0):

      # ROAS DIFF := Hist Roas - Target ROAS
      roas_diff = round(from_roas - to_roas)

      # clip roas_diff to range within ROAS_BID_ADJUSTMENT_TABLE
      if roas_diff > 0:
        roas_diff = min(roas_diff, max(ROAS_BID_ADJUSTMENT_TABLE))
      else:
        roas_diff = max(roas_diff, min(ROAS_BID_ADJUSTMENT_TABLE))

      #print(f"from_roas: {from_roas}, to_roas: {to_roas}, roas_diff: {roas_diff}")

      # lookup adj from customer supplied table
      adj = ROAS_BID_ADJUSTMENT_TABLE[roas_diff]

      #print(f"adj: {adj}")
    else:
      print(f"WARN: Historical ROAS is zero, no adjustment. Campaign: {row[RPT_COL_GROUP]}, AdGroup: {row[RPT_COL_GROUP]}")
      adj = 0

    return adj

inputDf[TMP_DA_VALUE] = inputDf.apply(calc_bid_adj, axis=1)

print("after lookup adj", tableize(inputDf))

### Prepare output with changed rows

# comparing na doesn't work, so replace with empty string
inputDf.fillna('', inplace=True)
changed = (inputDf[TMP_DA_VALUE] != inputDf[RPT_COL_DAVALUE]) | \
          (inputDf[TMP_DA_STRATEGY] != inputDf[RPT_COL_DASTRATEGY])

cols = [RPT_COL_CLIENT, BULK_COL_ACCOUNT, BULK_COL_CAMPAIGN, BULK_COL_GROUP, TMP_DA_STRATEGY, TMP_DA_VALUE]

outputDf = inputDf.loc[ changed, cols ].copy() \
                  .rename(columns = {
                    TMP_DA_STRATEGY: BULK_COL_DASTRATEGY,
                    TMP_DA_VALUE: BULK_COL_DAVALUE
                  })

if not outputDf.empty:
  print(tableize(outputDf))
else:
  print("Empty outputDf")

print("Finished")

Post generated on 2024-05-15 07:44:05 GMT

comments powered by Disqus