Package: mamimo
MaMiMoThis is a small library that helps you with your everydayMarketingMixModelling. It contains a few saturation functions, carryovers and some utilities for creating with time features. You can also read my article about it here:>>>Click<<<.Give it a try viapip install mamimo!Small ExampleYou can create a marketing mix model using different components from MaMiMo as well asscikit-learn. First, we can create a dataset viafrommamimo.datasetsimportload_fake_mmmdata=load_fake_mmm()X=data.drop(columns=['Sales'])y=data['Sales']Xcontains media spends only now, but you can enrich it with more information.Feature EngineeringMaMiMo lets you add time features, for example, viafrommamimo.time_utilsimportadd_time_features,add_date_indicatorsX=(X.pipe(add_time_features,month=True).pipe(add_date_indicators,special_date=["2020-01-05"]).assign(trend=range(200)))This addsa month column (integers between 1 and 12),a binary column named special_date that is 1 on the 5h of January 2020 and 0 everywhere else, anda (so far linear) trend which is only counting up from 0 to 199.Xlooks like this now:Building a ModelWe can now build a final model like this:frommamimo.time_utilsimportPowerTrendfrommamimo.carryoverimportExponentialCarryoverfrommamimo.saturationimportExponentialSaturationfromsklearn.linear_modelimportLinearRegressionfromsklearn.preprocessingimportOneHotEncoderfromsklearn.composeimportColumnTransformerfromsklearn.pipelineimportPipelinecats=[list(range(1,13))]# different months, known beforehandpreprocess=ColumnTransformer([('tv_pipe',Pipeline([('carryover',ExponentialCarryover()),('saturation',ExponentialSaturation())]),['TV']),('radio_pipe',Pipeline([('carryover',ExponentialCarryover()),('saturation',ExponentialSaturation())]),['Radio']),('banners_pipe',Pipeline([('carryover',ExponentialCarryover()),('saturation',ExponentialSaturation())]),['Banners']),('month',OneHotEncoder(sparse=False,categories=cats),['month']),('trend',PowerTrend(),['trend']),('special_date',ExponentialCarryover(),['special_date'])])model=Pipeline([('preprocess',preprocess),('regression',LinearRegression(positive=True,fit_intercept=False# no intercept because of the months))])This builds a model that does the following:the media channels are preprocessed using theadstock transformation, i.e. a carryover effect and a saturation is addedthe month is one-hot (dummy) encodedthe trend is changed from linear to something like t^a, with some exponent a to be optimizedthe special_date 2020-01-05 gets a carryover effect as well, meaning that not only on this special week there was some special effect on the sales, but also the weeks after itTraining The ModelWe can then hyperparameter tune the model viafromscipy.statsimportrandint,uniformfromsklearn.model_selectionimportRandomizedSearchCV,TimeSeriesSplittuned_model=RandomizedSearchCV(model,param_distributions={'preprocess__tv_pipe__carryover__window':randint(1,10),'preprocess__tv_pipe__carryover__strength':uniform(0,1),'preprocess__tv_pipe__saturation__exponent':uniform(0,1),'preprocess__radio_pipe__carryover__window':randint(1,10),'preprocess__radio_pipe__carryover__strength':uniform(0,1),'preprocess__radio_pipe__saturation__exponent':uniform(0,1),'preprocess__banners_pipe__carryover__window':randint(1,10),'preprocess__banners_pipe__carryover__strength':uniform(0,1),'preprocess__banners_pipe__saturation__exponent':uniform(0,1),'preprocess__trend__power':uniform(0,2),'preprocess__special_date__window':randint(1,10),'preprocess__special_date__strength':uniform(0,1),},cv=TimeSeriesSplit(),random_state=0,n_iter=1000,# can take some time, lower number for faster results)tuned_model.fit(X,y)You can also useGridSearch, Optuna, or other hyperparameter tune methods and packages here, as long as it is compatible to scikit-learn.AnalyzingWithtuned_model.predict(X)and some plotting, we getYou can get the best found hyperparameters usingprint(tuned_model.best_params_).PlottingYou can compute the channel contributions viafrommamimo.analysisimportbreakdowncontributions=breakdown(tuned_model.best_estimator_,X,y)This returns a dataframe with the contributions of each channel fo each time step, summing to the historical values present iny. You can get a nice plot viaax=contributions.plot.area(figsize=(16,10),linewidth=1,title="Predicted Sales and Breakdown",ylabel="Sales",xlabel="Date",)handles,labels=ax.get_legend_handles_labels()ax.legend(handles[::-1],labels[::-1],title="Channels",loc="center left",bbox_to_anchor=(1.01,0.5),)Wow, that's a lot of channels. Let us group some of them together.group_channels={'Baseline':[f'month__month_{i}'foriinrange(1,13)]+['Base','trend__trend']}# read: 'Baseline consists of the months, base and trend.'# You can add more groups!contributions=breakdown(tuned_model.best_estimator_,X,y,group_channels)If we plot again, we getYay!