Convenient Bayesian Marketing Mix Modeling with PyMC Marketing | by Dr. Robert Kübler | Apr, 2023
A new and shiny library from the PyMC team worth trying out
You can tell the importance of a topic by how many big companies are releasing software packages on it. In the field of marketing mix modeling, you can see that
Even better than marketing mix modeling is Bayesian marketing mix modeling, which Google’s and PyMC Labs’ libraries provide. While LMMM is certainly interesting as well, today we will focus on PyMC Marketing.
In this article, you will learn how easy it is to build a state-of-the-art Bayesian marketing mix model nowadays!
In case you need a refresher, please check out my old article that tells you what Bayesian marketing mix modeling is all about.
In my old article (see above), I was coding a Bayesian marketing mix model myself. In order to do this, I needed to define a function for the carryover effect of the media spendings, which was a bit cumbersome. Still using the older PyMC3, it looked like this:
import theano.tensor as ttdef carryover(x, strength, length):
w = tt.as_tensor_variable(
[tt.power(strength, i) for i in range(length)]
)
x_lags = tt.stack(
[tt.concatenate([
tt.zeros(i),
x[:x.shape[0]-i]
]) for i in range(length)]
)
return tt.dot(w, x_lags)
This one works, but it is not easy to parse and may not be efficient as well. Also, using Theano in PyMC is outdated, as I would have to use PyTensor now, a fork of Aesara based on Theano. A complicated history, it seems.
So I am happily relying on more professional and general code now to achieve my goals. I learned a lot by checking out how they implemented the carryover effect.
Before we continue, make sure you have the packages pymc and pymc-marketing installed. I installed PyMC using mamba as described in their Github and then installed pymc-marketing via
pip install pymc-marketing
Let us revisit our old example from my Bayesian marketing mix modeling article. We start by importing a dataset that I synthetically created.
import pandas as pddata = pd.read_csv(
'https://raw.githubusercontent.com/Garve/datasets/4576d323bf2b66c906d5130d686245ad205505cf/mmm.csv',
parse_dates=['Date']
)
The data looks like this:
Model Definition
Now, let’s get our star on stage and define the model:
from pymc_marketing.mmm import DelayedSaturatedMMMmmm = DelayedSaturatedMMM(
data=data,
target_column="Sales",
date_column="Date",
channel_columns=["TV", "Radio", "Banners"],
)
This creates a model with saturations and carryover effects per channel, similar to what I did manually before. That’s why I will not go into detail about how this model works from a mathematical perspective.
We can now visualize what we have created:
import pymc as pmpm.model_to_graphviz(model=mmm.model)
Here, we can see that first the adstock (carryover) is applied, and then the saturation. We have three parameters alpha
, beta_channel
and lam
per channel, where
alpha
is the carryover rate that is between 0 and 1,lam
is the saturation rate, andbeta_channel
is the actual linear regression coefficient.
To give a bit more context, the abridged model formula is
where c runs over all different channels.
Model Fit
Fitting the model is as easy as in scikit-learn:
mmm.fit()
Model Inference
After the model is trained, we can check the parameters as follows:
import arviz as azaz.summary(
data=mmm.fit_result,
var_names=["intercept", "beta_channel", "alpha", "lam", "sigma"]
)
I got something like this:
The first thing we can see is that the chains seem to have converged nicely as the r_hat
in the rightmost columns is 1.0 everywhere.
From an inference standpoint, we can check what the model believes are the correct values for all of the parameters. As an example, the carryover of the channel TV alpha[TV]
is between 0.465 and 0.515 with a 94% probability.
Note: For creating this dataset, I used saturation values of 0.5 for TV, 0.2 for radio and 0 for banners. Our PyMC model was able to pick this up quite decently!
For the visual folks:
mmm.plot_channel_parameter(param_name="alpha", figsize=(9, 5))
We can even check out the channel contributions using the convenient method
mmm.plot_channel_contribution_share_hdi()
Looks like TV is responsible for about 40% of the additional sales (additional to the base), radio for about 26%, and banners for about 34%.
Posterior Predictive Check
We can do a posterior predictive check, i.e. sampling predictions (blue), and see how well they follow the model (black).
Looks like a fine fit! We can even decompose the signal into a baseline and the channel contributions via
mmm.plot_components_contributions()
Insightful, but in addition, it might be useful to add the following to the library as well:
Bayesian marketing mix modeling is the current best way to find out which of your marketing channels perform well and which don’t. Building such a model is not too complicated, but still by far not as straightforward as clicking together a scikit-learn model.
Luckily, the new PyMC Marketing makes Bayesian marketing mix modeling a breeze, compared to what we had to manually code before.
Don’t get me wrong, I like coding and I think it is important that you know how to code it as well. But still, it is nice to have a well-maintained package that probably gets even more common marketing mix model functionalities. Let’s see what the future brings!
And I did not even cover all of the functionalities. PyMC Marketing can even:
- deal with control variables in an easy way by passing a list of columns via the
control_columns
into theDelayedSaturatedMMM
class - plot saturation curves via
mmm.plot_contribution_curves()
- calculate the ROAS, although it is still manual work.
For more information, also check out this great notebook by the PyMC people.
I hope that you learned something new, interesting, and useful today. Thanks for reading!
As the last point, if you
- want to support me in writing more about machine learning and
- plan to get a Medium subscription anyway,
why not do it via this link? This would help me a lot! 😊
To be transparent, the price for you does not change, but about half of the subscription fees go directly to me.
Thanks a lot, if you consider supporting me!
If you have any questions, write me on LinkedIn!
A new and shiny library from the PyMC team worth trying out
You can tell the importance of a topic by how many big companies are releasing software packages on it. In the field of marketing mix modeling, you can see that
Even better than marketing mix modeling is Bayesian marketing mix modeling, which Google’s and PyMC Labs’ libraries provide. While LMMM is certainly interesting as well, today we will focus on PyMC Marketing.
In this article, you will learn how easy it is to build a state-of-the-art Bayesian marketing mix model nowadays!
In case you need a refresher, please check out my old article that tells you what Bayesian marketing mix modeling is all about.
In my old article (see above), I was coding a Bayesian marketing mix model myself. In order to do this, I needed to define a function for the carryover effect of the media spendings, which was a bit cumbersome. Still using the older PyMC3, it looked like this:
import theano.tensor as ttdef carryover(x, strength, length):
w = tt.as_tensor_variable(
[tt.power(strength, i) for i in range(length)]
)
x_lags = tt.stack(
[tt.concatenate([
tt.zeros(i),
x[:x.shape[0]-i]
]) for i in range(length)]
)
return tt.dot(w, x_lags)
This one works, but it is not easy to parse and may not be efficient as well. Also, using Theano in PyMC is outdated, as I would have to use PyTensor now, a fork of Aesara based on Theano. A complicated history, it seems.
So I am happily relying on more professional and general code now to achieve my goals. I learned a lot by checking out how they implemented the carryover effect.
Before we continue, make sure you have the packages pymc and pymc-marketing installed. I installed PyMC using mamba as described in their Github and then installed pymc-marketing via
pip install pymc-marketing
Let us revisit our old example from my Bayesian marketing mix modeling article. We start by importing a dataset that I synthetically created.
import pandas as pddata = pd.read_csv(
'https://raw.githubusercontent.com/Garve/datasets/4576d323bf2b66c906d5130d686245ad205505cf/mmm.csv',
parse_dates=['Date']
)
The data looks like this:
Model Definition
Now, let’s get our star on stage and define the model:
from pymc_marketing.mmm import DelayedSaturatedMMMmmm = DelayedSaturatedMMM(
data=data,
target_column="Sales",
date_column="Date",
channel_columns=["TV", "Radio", "Banners"],
)
This creates a model with saturations and carryover effects per channel, similar to what I did manually before. That’s why I will not go into detail about how this model works from a mathematical perspective.
We can now visualize what we have created:
import pymc as pmpm.model_to_graphviz(model=mmm.model)
Here, we can see that first the adstock (carryover) is applied, and then the saturation. We have three parameters alpha
, beta_channel
and lam
per channel, where
alpha
is the carryover rate that is between 0 and 1,lam
is the saturation rate, andbeta_channel
is the actual linear regression coefficient.
To give a bit more context, the abridged model formula is
where c runs over all different channels.
Model Fit
Fitting the model is as easy as in scikit-learn:
mmm.fit()
Model Inference
After the model is trained, we can check the parameters as follows:
import arviz as azaz.summary(
data=mmm.fit_result,
var_names=["intercept", "beta_channel", "alpha", "lam", "sigma"]
)
I got something like this:
The first thing we can see is that the chains seem to have converged nicely as the r_hat
in the rightmost columns is 1.0 everywhere.
From an inference standpoint, we can check what the model believes are the correct values for all of the parameters. As an example, the carryover of the channel TV alpha[TV]
is between 0.465 and 0.515 with a 94% probability.
Note: For creating this dataset, I used saturation values of 0.5 for TV, 0.2 for radio and 0 for banners. Our PyMC model was able to pick this up quite decently!
For the visual folks:
mmm.plot_channel_parameter(param_name="alpha", figsize=(9, 5))
We can even check out the channel contributions using the convenient method
mmm.plot_channel_contribution_share_hdi()
Looks like TV is responsible for about 40% of the additional sales (additional to the base), radio for about 26%, and banners for about 34%.
Posterior Predictive Check
We can do a posterior predictive check, i.e. sampling predictions (blue), and see how well they follow the model (black).
Looks like a fine fit! We can even decompose the signal into a baseline and the channel contributions via
mmm.plot_components_contributions()
Insightful, but in addition, it might be useful to add the following to the library as well:
Bayesian marketing mix modeling is the current best way to find out which of your marketing channels perform well and which don’t. Building such a model is not too complicated, but still by far not as straightforward as clicking together a scikit-learn model.
Luckily, the new PyMC Marketing makes Bayesian marketing mix modeling a breeze, compared to what we had to manually code before.
Don’t get me wrong, I like coding and I think it is important that you know how to code it as well. But still, it is nice to have a well-maintained package that probably gets even more common marketing mix model functionalities. Let’s see what the future brings!
And I did not even cover all of the functionalities. PyMC Marketing can even:
- deal with control variables in an easy way by passing a list of columns via the
control_columns
into theDelayedSaturatedMMM
class - plot saturation curves via
mmm.plot_contribution_curves()
- calculate the ROAS, although it is still manual work.
For more information, also check out this great notebook by the PyMC people.
I hope that you learned something new, interesting, and useful today. Thanks for reading!
As the last point, if you
- want to support me in writing more about machine learning and
- plan to get a Medium subscription anyway,
why not do it via this link? This would help me a lot! 😊
To be transparent, the price for you does not change, but about half of the subscription fees go directly to me.
Thanks a lot, if you consider supporting me!
If you have any questions, write me on LinkedIn!