<jupyter_start><jupyter_text>Generalized Bass Model Generalized Bass model allows to estimate how marketing mix variables (e.g., price, ad spending) affect product diffussion.$$\hat{A}(t) = M\cdot\frac{1-exp(-(p+q)t^*)}{1+\frac{q}{p}exp(-(p+q)t^*)}$$ where $t^* = t + b1\cdot ln(\frac{price(t)}{price(1)}) + p2\cdot ln(\frac{adv(t)}{adv(1)}) $ is the **effective diffussion time**.$$\hat{N}(t) = \hat{A}(t)-\hat{A}(t-1)$$ The only difference between GBM and vanilla BM is that GBM adjusts time to **"effective time"**, which accounts for the impact of marketing mixes. GBM also has two more parameters: price impact $b1$ and advertising impact $b2$. You need to have marketing mix data to use GBM, and it's ok to include only price or only advertising.  Estimate GBM<jupyter_code>import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from scipy.optimize import least_squares                 # package to conduct Nonlinear least square 

# import historical data 
url = "https://raw.githubusercontent.com/zoutianxin1992/MarketingAnalyticsPython/main/Marketing%20Analytics%20in%20Python/Bass%20model/Dataset/3-3%20GBM%20floppy%20disk.csv"
df = pd.read_csv(url) 
df.info()
# Rename the variables to t, N, and Price
df.rename(columns = {df.columns[1]:"t",df.columns[2]:"N",df.columns[3]:"Price"}, inplace = True)  # "inplace" apply the name change to df itself 
df.head()
# define A_hat(t) and N_hat(t)
# Remember GBM has an additional variable (Price) and an additional parameter (b1)
Price_1 = df['Price'][0]
def A_hat(t,Price,p,q,M,b1):                                     # two more arguments: Price and b1
    tstar = t + b1 * np.log(Price/Price_1)                        #tstar: effective time
    return M * (1 - np.exp(-(p+q)*tstar))/(1 + q / p* np.exp(-(p+q)*tstar))    # the "t" in BM is replaced with "t*" in GBM

# define N_hat(t) 
def N_hat(t,Price,p,q,M,b1):  
    return A_hat(t,Price,p,q,M,b1) - A_hat(t-1,Price,p,q,M,b1)  # We can use the A_hat function instead of manually typing the formula
# define prediction errors as a fucntion of p,q,M, and b1
T = len(df['N'])   # number of periods for historical data


def prediction_error(params):   # Note that we input p,q,M,b1 as a 1*4 array "params."  
    p = params[0]
    q = params[1]
    M = params[2]
    b1 = params[3]
    Nhat = [N_hat(t,df['Price'][t-1],p,q,M,b1) for t in range(1,T+1)]            # Given p,q,M, generate Bass prediction for each period
    return df['N'] - Nhat                                 # Prediction error for each period<jupyter_output><empty_output><jupyter_text>The initial guesses for $p ,q , M$ are the same for BM. The initial guesses are better to be set at -0.3 for $b1$ and 0.3 for $b2$.<jupyter_code># estimate p,q,M,b1 using least_squares
# Bass model requires 0<p<1, 0<q<1, M>0, and b1<0 so we need to add the constraints
A_t = sum(df['N'])           # calculate already adopters until period t
params0 = [0.01,0.16,3*A_t,-0.3]  # initial guess for p,q,M, b1. Required by least_squares
estim_results= least_squares(prediction_error, params0, bounds = ([0,0,0,-np.Inf],[np.Inf,np.Inf,np.Inf,0]) )
estim_results
#########################
# prediction_error: an array of prediction errors for each period
# param0: initial guesses
# bounds: The bounds for p,q,M, and b1. In our case p,q,M>0 and b1<0
#store estimated parameters
p_estim = estim_results.x[0]
q_estim = estim_results.x[1]
M_estim = estim_results.x[2]
b1_estim = estim_results.x[3]<jupyter_output><empty_output><jupyter_text>Predict future sales for the next three periods We will predict the sales for period 13-15, assuming the prices will be 350, 325, and 300.<jupyter_code># Appending the future prices to the historical price data
Price_15 = df["Price"].append(pd.Series([350,325,300]))
T_pred = 15  # number of periods for prediction
predictA = [A_hat(t,Price_15.iat[t-1],p_estim,q_estim,M_estim,b1_estim) for t in range(1,T_pred+1)]  # predict already adopters for T periods
predictN = [N_hat(t,Price_15.iat[t-1],p_estim,q_estim,M_estim,b1_estim) for t in range(1,T_pred+1)]  # predict already adopters for T periods

# Plot the trajectory of new adopters

plt.rcParams['figure.figsize'] = [12,8]  # set figure size to be 12*8 inch
plt.plot(range(1,T_pred+1),predictN)
plt.scatter(range(1,T+1),df["N"],color = "red")    # Also plot historical sales data
plt.xticks(range(1,T_pred+1,2), fontsize = 18)
plt.yticks(fontsize = 18)
plt.ylabel("New adopters",fontsize = 18)
plt.xlabel("time", fontsize = 18)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.14093218466975924, 'fraction_numerical': 0.03468080406450188, 'mean_word_length': 4.3649289099526065, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 15, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '532498', 'license': 'MIT', 'n_tokens_mistral': 1718, 'n_tokens_neox': 1623, 'n_words': 480, 'path': 'Marketing Analytics in Python/Bass model/notebook/GBM.ipynb', 'repo_name': 'zoutianxin1992/MarketingAnalyticsPython', 'size': '4527'}