Open xijianlim opened 2 years ago
Hello @xijianlim ! Thanks for reporting this one!
There are a few small nuances that could lead to some difference in response curves vs contribution calculation. Although the difference that you report here seem bigger than I would expect.
I see that you are currently running a geo model. Could you confirm if you see this same issue on a national model? Just so I can rule some things out.
Did you use extra features for this model? If so, what are the coefficients of it?
Thanks
Hello again @pabloduque0 . Yes the same thing happens at the national model, and yes there were extra features in the model.
you can clearly see that channel_0's ROI profile is well below $1:
instead of using the media ranges and constructing them using the mmm.predict function, would it be better if you feed them through the adstock-hillfunction equation but using the coefficients from the model?
something like this:
def media_transform_hill_adstock(media_mix_model,
media_data,
lag_weight,
half_max_effective_concentration,
slope, apply_adstock, normalise):
ransforms the input data with the adstock and hill functions.
Args:
media_data: Media data to be transformed. It is expected to have 2 dims for
national models and 3 for geo models.
normalise: Whether to normalise the output values.
Returns:
The transformed media data.
"""
if media_mix_model.n_geos > 1:
lag_weight=jnp.repeat(lag_weight,media_mix_model.n_geos).reshape(half_max_effective_concentration.shape)
slope = jnp.squeeze(jnp.repeat(slope,media_mix_model.n_geos).reshape(half_max_effective_concentration.shape))
if apply_adstock:
return media_transforms.hill(
data=media_transforms.adstock(
data=media_data, lag_weight=lag_weight, normalise=normalise),
half_max_effective_concentration=half_max_effective_concentration,
slope=slope)
else:
return media_transforms.hill(
data=media_data,
half_max_effective_concentration=half_max_effective_concentration,
slope=slope)
percentage_add=2
media_mix_model=mmm
steps=25
prices=average_cost_per_media
media = media_mix_model.media
media_maxes = media.max(axis=0) * (1 + percentage_add)
half_max_effective_concentration=media_mix_model._mcmc.get_samples()['half_max_effective_concentration']#.mean(axis=0)
lag_weight=media_mix_model._mcmc.get_samples()['lag_weight']#.mean(axis=0)
slope=media_mix_model._mcmc.get_samples()['slope']#.mean(axis=0)
beta_media=media_mix_model.trace['beta_media'].mean(axis=0)
media_ranges = jnp.expand_dims(
jnp.linspace(start=0, stop=media_maxes, num=steps), axis=0)
media_ranges=jnp.repeat(media_ranges,len(beta_media)).reshape(steps,len(beta_media),media_mix_model.n_media_channels, media_mix_model.n_geos)
media_response=beta_media*media_transform_hill_adstock(media_mix_model,
media_ranges,
lag_weight,
half_max_effective_concentration=half_max_effective_concentration,
slope=slope, normalise=True)
I can confirm that constructing the response curves using the trace parameters (beta_media, lag_weight, half_max_effective_concentration and slop) as per above has resolved the issue. I've modified this in my version of the code base. I thought I'd share this with you as this has closer alignment with the media contributions. I suggest amending the response_curve plots with this method.
Hello @xijianlim !
Thanks for your response.
There are a couple reasons why those two are designed differently. Mainly the response curves are a forward looking and the other approach is strictly backward looking. However for that we need to make a couple assumptions about lagging and extra features that might not be what users want in some scenarios. I agree that we should offer the flexibility to not take into account those assumptions, or make them optional or similar.
Let me looks into it a little bit and get back on this one.
@xijianlim How did you go about plotting the response curves on the spend vs. KPI axis? I can't seem to make the dimensions or values work for my use case using your function above.
were you be able to plot it?
Hi, sorry for the delay in replying. To be clear, this is only for ADSTOCK-BETAHILL modelling. Unfortunately I can't share the exact code because it is now my company's PI but i'll demonstrate how you can plot this.
1) you have import this function from _mediatransforms.py
media_transforms.hill( data=media_transforms.adstock( data=media_data, lag_weight=lag_weight, normalise=normalise), half_max_effective_concentration=half_max_effective_concentration, slope=slope)
This is in effect is the "response curves" function. All that is needed is to use the trained models's "trace" functions to get the right parameters, namely the lagweight, slope, beta_media and half_effective concentration.
percentage_add=2
media_mix_model=mmm
steps=25
prices=average_cost_per_media
media = media_mix_model.media
media_maxes = media.max(axis=0) * (1 + percentage_add)
half_max_effective_concentration=media_mix_model._mcmc.get_samples()['half_max_effective_concentration'].mean(axis=0)
lag_weight=media_mix_model._mcmc.get_samples()['lag_weight'].mean(axis=0)
slope=media_mix_model._mcmc.get_samples()['slope'].mean(axis=0)
beta_media=media_mix_model.trace['beta_media'].mean(axis=0)
media_ranges = jnp.expand_dims(
jnp.linspace(start=0, stop=media_maxes, num=steps), axis=0)
media_ranges=jnp.repeat(media_ranges,len(beta_media)).reshape(steps,len(beta_media),media_mix_model.n_media_channels, media_mix_model.n_geos)
media_response=beta_media*media_transform_hill_adstock(media_mix_model,
media_ranges,
lag_weight,
half_max_effective_concentration=half_max_effective_concentration,
slope=slope, normalise=True)
You'll then have to plot an XY scatter plot where Y=media response and X = media spend (which is media*prices)
I hope this helps you all
Hi, thank you very much
On Wed, Jun 14, 2023 at 12:06 xijianlim @.***> wrote:
Hi, sorry for the delay in replying. Unfortunately I can't share the exact code because it is now my company's PI but i'll demonstrate how you can plot this.
— Reply to this email directly, view it on GitHub https://github.com/google/lightweight_mmm/issues/51#issuecomment-1590601232, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOSU3722LT3LAFPZLLI4HQLXLFPJFANCNFSM55JCH5DQ . You are receiving this because you commented.Message ID: @.***>
@pabloduque0 @steven-struglia @MuhammedTech and @hawkinsp I've created a collab book showing examples of how you can build functions to export these curves as well as channel tuning
https://colab.research.google.com/drive/1zKHmT_CR6AmVbH-4PdsmUJdumMadcmrO#scrollTo=WcYeesq4w3jT
Hi everyone, I am following up on @xijianlim 's discussion about saturation curves. I was able to build the curves using the model's estimated parameters from scratch. However, I'd like to understand the details behind how LightweightMMM generates these curves. Could someone provide some additional information?
In a related thread, @pabloduque mentioned that "mainly the response curves are forward looking and the other approach is strictly backward looking." I'd appreciate it if someone could clarify the meaning of that.
Thanks!
Even though modifying the response curves function to calculate the response from the fitted parameters from the model, I'm still struggling to understand how to interpret this curve because even taking into account the “lag weight” there's no time dimension in this graph, so would it make sense for me to analyze the ROI of each point and consider the inflection point as the maximum ROI? Since part of the effect of my spending would return in the coming periods?
Even though modifying the response curves function to calculate the response from the fitted parameters from the model, I'm still struggling to understand how to interpret this curve because even taking into account the “lag weight” there's no time dimension in this graph, so would it make sense for me to analyze the ROI of each point and consider the inflection point as the maximum ROI? Since part of the effect of my spending would return in the coming periods?
Response curve are basically industry terms to describe the diminishing effect of revenue being attributed from money being spent on a particular marketing channel. For example. lets just say $50 on Youtube yields $300 return in revenue from your past activity. That is a point on your curve (x-axis being spent and y-axis being revenue). Now on your same curve you can see that if you were to spend $100, the projected revenue is $400. We can say that youtube's effect on spend is diminishing. In all cases in the industry, your clients only care about the bottom line and coming to your model to determine where to optimise spend knowing that everything has a diminishing effect. And in every case, the industry standard is to recommend adjusting only a 10%-20% threshold for every channel. The "Lag weight" and other effects that affect the curvature of the response curves can be see here https://rhydhamgupta.medium.com/transformations-in-mmm-modelling-7fad3e4f8fe5
Discussed in https://github.com/google/lightweight_mmm/discussions/49