Logo
Overview

AdStock & Saturation Transformations Practice

September 8, 2025
5 min read
!pip install pyomo

Requirement already satisfied: pyomo in /usr/local/lib/python3.12/dist-packages (6.9.4) Requirement already satisfied: ply in /usr/local/lib/python3.12/dist-packages (from pyomo) (3.11)


!apt-get install -y -qq glpk-utils
  • Setting up glpk-utils (5.0-1) …
  • /sbin/ldconfig.real: /usr/local/lib/libtcm_debug.so.1 is not a symbolic link
  • (similar warnings repeated…)

import pandas as pd
import numpy as np
from google.colab import drive
drive.mount('/content/drive', force_remount=True)

Mounted at /content/drive


dt_simulated_weekly = pd.read_csv('/content/drive/My Drive/Colab Notebooks/dt_simulated_weekly.csv')
dt_simulated_weekly
DATErevenuetv_Sooh_Sprint_Ssearch_Sfacebook_S
11/23/152754371.66722358.350.012728.490.07607.13
11/30/152584276.66728613.450.00.04133.331141.95
12/07/152547386.6670.0132278.40453.873786.674256.38
12/14/152875220.083450.310.017680.04253.332800.49
12/21/152215953.3330.0277336.00.03613.33689.58
10/14/192456240.00.032230.9320496.4914946.670.0
10/21/192182825.020655.680.00.013826.674454.15
10/28/192377706.6672931.07516.82330.1315293.330.0
11/04/192732825.02993.730.03206.8417880.00.0
11/11/192767788.3330.00.00.00.012206.36

(208 rows × 7 columns)


dt_simulated_weekly.isnull().sum()
ColumnNull Count
DATE0
revenue0
tv_S0
ooh_S0
print_S0
search_S0
facebook_S0

dt_simulated_weekly.describe()
revenuetv_Sooh_Sprint_Ssearch_Sfacebook_S
count208.0208.0208.0208.0208.0208.0
mean1.82e+0614843.6943217.943728.635915.512145.66
std7.16e+0528558.3783991.436483.074702.503160.36
min6.72e+050.000.000.000.000.00
25%1.16e+060.000.000.002353.330.00
50%1.87e+060.000.000.004806.670.00
75%2.38e+0618406.7150858.134767.838536.673623.01
max3.83e+06158046.57500361.631922.3117880.015400.39

import matplotlib.pyplot as plt
dt_simulated_weekly['DATE'] = pd.to_datetime(dt_simulated_weekly['DATE'])
dt_simulated_weekly = dt_simulated_weekly.set_index('DATE')
plt.figure(figsize=(12, 6))
plt.plot(dt_simulated_weekly.index, dt_simulated_weekly['revenue'], marker='o')
plt.title("Weekly Time Series")
plt.xlabel("Date")
plt.ylabel("revenue")
plt.grid(True)
plt.show()

Warning: Could not infer format, falling back to dateutil


import random
hyperparameters = {
'facebook_S_alpha': 1.115,
'facebook_S_gamma': 2145.66,
'facebook_S_theta': 0.216,
'print_S_alpha': 2.735,
'print_S_gamma': 3728.63,
'print_S_theta': 0.130,
'tv_S_alpha': 2.317,
'tv_S_gamma': 14843.69,
'tv_S_theta': 0.441,
'search_S_alpha': 2.008,
'search_S_gamma': 5915.51,
'search_S_theta': 0.156,
'ooh_S_alpha': 2.646,
'ooh_S_gamma': 43217.94,
'ooh_S_theta': 0.179
}
hyperparameters
{'facebook_S_alpha': 1.115, 'facebook_S_gamma': 2145.66, ... }

def adstock_transformation(x, decay):
adstock = np.zeros(len(x))
for t in range(len(x)):
if t == 0:
adstock[t] = x[t]
else:
adstock[t] = x[t] + decay * adstock[t-1]
return adstock

FutureWarning: Series indexing behavior changing in future pandas


def saturation_transformation(adstock_var, alpha, gamma):
epsilon = 1e-6
return 1/(1+(gamma/(adstock_var+epsilon))**alpha)

from sklearn.linear_model import LinearRegression
X = saturated
y = dt_simulated_weekly["revenue"]
model = LinearRegression()
model.fit(X, y)
print(f"Intercept: {model.intercept_}")
print(f"Coefficients: {model.coef_}")
Intercept: 672269.07
Coefficients: [879382.83 182146.33 152446.92 647846.63 796880.31]

np.max(model.coef_)
879382.83

Step 4: Optimal budget allocation

import pyomo.environ as pyo
from pyomo.opt import SolverFactory
budget = {}
for m in media_var:
budget[m] = [20000]
adstocked_spend = {}
for m in media_var:
adstocked_spend[m] = adstock_transformation(budget[m], hyperparameters[m+'_theta'])
adstocked_spend = pd.DataFrame.from_dict(adstocked_spend)
adstocked_spend
saturated_spend = {}
for m in media_var:
saturated_spend[m] = saturation_transformation(adstocked_spend[m], hyperparameters[m+'_alpha'],hyperparameters[m+'_gamma'])
saturated_spend = pd.DataFrame.from_dict(saturated_spend)
saturated_spend
# scikit-learn 회귀 모델에서 intercept_와 coef_를 가져옴
intercept_ = model.intercept_ # 회귀 모델의 절편
coef_ = model.coef_ # 회귀 모델의 계수
# Pyomo 모델 생성
model_pyomo = pyo.ConcreteModel()
# 광고 채널의 개수 (예: 5개 채널)
n_channels = len(X.columns)
# 변수 설정 (각 채널에 할당할 광고비)
model_pyomo.spend = pyo.Var(range(n_channels), domain=pyo.NonNegativeReals)
# 목적 함수 정의
def objective_function(model_pyomo):
# Pyomo에서 회귀 모델 기반 목적 함수 정의
return (model.intercept_ + sum(coef_[i] * model_pyomo.spend[i] for i in range(n_channels)))
model_pyomo.objective = pyo.Objective(rule=objective_function, sense=pyo.maximize)
# 제약 조건: 총 광고비는 sum(saturated_spend.iloc[0]) 을 넘지 않도록
def total_budget_constraint(model_pyomo):
return sum(model_pyomo.spend[i] for i in range(n_channels)) <= sum(saturated_spend.iloc[0])
model_pyomo.budget_constraint = pyo.Constraint(rule=total_budget_constraint)
# # 제약 조건: TV 채널은 전체 예산의 50% 이하
def tv_constraint(model_pyomo):
total_budget = sum(model_pyomo.spend[i] for i in range(n_channels))
return model_pyomo.spend[0] <= 0.5 * total_budget
model_pyomo.tv_constraint = pyo.Constraint(rule=tv_constraint)
# Solver 설정 및 최적화 실행
opt = SolverFactory('glpk', executable='/usr/bin/glpsol')
result = opt.solve(model_pyomo)
# 최적화 결과 출력
optimal_spend = np.array([pyo.value(model_pyomo.spend[i]) for i in range(n_channels)])
actual_budget = sum(v[0] for v in budget.values())
print("Optimal spend allocation:")
df_alloc = pd.DataFrame({"Channel": ['tv_S', 'ooh_S', 'print_S', 'search_S', 'facebook_S'], "Spend": optimal_spend/(sum(optimal_spend))*actual_budget
})
display(df_alloc)

Optimal spend allocation:

ChannelSpend
tv_S50000.0
ooh_S0.0
print_S0.0
search_S0.0
facebook_S50000.0