Script 1073: Monthly Spend Target Rollover
Purpose
Python script that takes the unspent Strategy Spend Target from the previous month and adds it to the current Spend Target.
To Elaborate
The Python script solves the problem of rolling over unspent budget from the previous month to the current month for strategies that have a Budget constraint. It performs the following tasks:
- Cleans up the input data by converting the Strategy Target column to numeric and replacing NaN values with 0.0.
- Filters the data to include only strategies with a Budget constraint and the Roll Over Status set to ‘traffic’.
- Calculates the unspent budget by subtracting the Public Cost from the Strategy Target.
- Adds the unspent budget to the current Strategy Target.
- Selects the changed strategies based on the difference in Strategy Target between the original and modified data.
- Renames the Strategy Target column to ‘Spend Target’ in the output data.
The script also includes local development mode configuration and debugging options.
Walking Through the Code
- The script starts by checking if it is executing on the server or locally.
- If running locally, it loads the input data from a pickled file and sets up the necessary dependencies.
- It defines the primary data source and column constants.
- User changeable parameters include the values for the ‘Goal’ and ‘Spend Target’ columns in the output data.
- The input data is cleaned up by converting the Strategy Target column to numeric and replacing NaN values with 0.0.
- The data is filtered to include only strategies with a Budget constraint and the Roll Over Status set to ‘traffic’.
- The cleaned and filtered data is printed for debugging purposes.
- The unspent budget is calculated by subtracting the Public Cost from the Strategy Target.
- The unspent budget is added to the current Strategy Target.
- The select_changed function is used to select the changed strategies based on the difference in Strategy Target between the original and modified data.
- The output data is assigned to the outputDf variable and the debug data (if applicable) is assigned to the debugDf variable.
- The ‘Strategy Target’ column in the output data is renamed to ‘Spend Target’.
- The shape and a sample of the output data are printed for verification.
- If running in local development mode, the output and debug data are saved to CSV files for further analysis.
Vitals
- Script ID : 1073
- Client ID / Customer ID: 1306927889 / 60270375
- Action Type: Bulk Upload (Preview)
- Item Changed: Strategy
- Output Columns: Strategy, Goal, Spend Target
- Linked Datasource: M1 Report
- Reference Datasource: None
- Owner: Michael Huang (mhuang@marinsoftware.com)
- Created by Michael Huang on 2024-05-13 07:28
- Last Updated by Michael Huang on 2024-05-13 22:19
> 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
##
## name: Monthly Spend Target Rollover
## description:
## - Takes unspent Strategy Spend Target from previous month and add it to current Spend Target
## - Limited to Strategy using Budget constraint
##
## author: Michael S. Huang
## created: 2024-05-13
##
########### 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/gatorworks_strategy_target_rollover.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
outputDf = dataSourceDict["1"].copy()
# setup timezone
import datetime
CLIENT_TIMEZONE = datetime.timezone(datetime.timedelta(hours=+8))
# import pandas
import pandas as pd
import numpy as np
# import Marin util functions
from marin_scripts_utils import tableize, select_changed
else:
print("Running locally but no pickle path defined. dataSourceDict not loaded.")
exit(1)
########### END - Local Mode Setup ###########
# today in client timezone
today = datetime.datetime.now(CLIENT_TIMEZONE).date()
# primary data source and columns
inputDf = dataSourceDict["1"]
RPT_COL_STRATEGY = 'Strategy'
RPT_COL_STRATEGY_TARGET = 'Target'
RPT_COL_PUB_COST = 'Pub. Cost $'
RPT_COL_GOAL = 'Goal'
RPT_COL_CONSTRAINT = 'Constraint'
RPT_COL_ROLL_OVER_STATUS = 'Roll Over Status'
# output columns and initial values
BULK_COL_GOAL = 'Goal'
BULK_COL_SPEND_TARGET = 'Spend Target'
outputDf[BULK_COL_GOAL] = "<<YOUR VALUE>>"
outputDf[BULK_COL_SPEND_TARGET] = "<<YOUR VALUE>>"
########### User Code Starts Here ###########
## DEBUG: enable rollover flag on a couple of strategies
# inputDf.iloc[0, inputDf.columns.get_loc(RPT_COL_ROLL_OVER_STATUS)] = 'traffic'
# inputDf.iloc[2, inputDf.columns.get_loc(RPT_COL_ROLL_OVER_STATUS)] = 'traffic'
### Cleanup input
# Convert RPT_COL_STRATEGY_TARGET to numeric, coercing errors to NaN
inputDf[RPT_COL_STRATEGY_TARGET] = pd.to_numeric(inputDf[RPT_COL_STRATEGY_TARGET], errors='coerce')
# Replace NaN values with 0.0 if that's the desired behavior
inputDf[RPT_COL_STRATEGY_TARGET].fillna(0.0, inplace=True)
# convert Constraint column into string
# NB: Bulk needs to include Goal column in order to change Spend Target, hence,
# remove non-Budget strategies just in case.
inputDf[RPT_COL_CONSTRAINT] = inputDf[RPT_COL_CONSTRAINT].astype(str)
budget_constraint = inputDf[RPT_COL_CONSTRAINT] == 'Budget'
# Create filter for RPT_COL_ROLL_OVER_STATUS where the value is 'traffic'
roll_over_enabled = inputDf[RPT_COL_ROLL_OVER_STATUS].astype(str).str.lower() == 'traffic'
# apply cleanup filters
inputDf = inputDf.loc[budget_constraint & roll_over_enabled].reset_index()
print("after cleanup inputDf")
print("inputDf.shape", inputDf.shape)
print("inputDf.info", inputDf.info())
# fill in missing target as 0 for comparison later
inputDf = inputDf.fillna(value={RPT_COL_STRATEGY_TARGET: 0})
# make copy of input for use with select_changed
originalDf = inputDf.copy()
### calculate unspent budget and add it to current budget target
# Calculate unspent budget by subtracting Pub. Cost $ from Target
inputDf['Unspent Budget'] = inputDf[RPT_COL_STRATEGY_TARGET] - inputDf[RPT_COL_PUB_COST]
# Add unspent budget to current strategy target
inputDf[RPT_COL_STRATEGY_TARGET] = inputDf[RPT_COL_STRATEGY_TARGET] + inputDf['Unspent Budget']
outputDf, debugDf = select_changed(inputDf,
originalDf,
diff_cols=[RPT_COL_STRATEGY_TARGET],
select_cols=[RPT_COL_STRATEGY, RPT_COL_GOAL, RPT_COL_STRATEGY_TARGET],
merged_cols=[RPT_COL_STRATEGY]
)
# Bulk column is 'Spend Target', whereas report is 'Target'
outputDf.rename(columns={RPT_COL_STRATEGY_TARGET: BULK_COL_SPEND_TARGET}, inplace=True)
print("outputDf.shape", outputDf.shape)
print("outputDf sample")
print(tableize(outputDf.tail(10)))
## 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-05-15 07:44:05 GMT