-
Notifications
You must be signed in to change notification settings - Fork 228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Weibull CDF and PDF Adstock Transformations #499
Merged
Merged
Changes from 4 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
4263077
Add Weibull transformations
abdalazizrashid 039e876
Add Weibull tests
abdalazizrashid fb35d50
Update
abdalazizrashid 3efacdf
Update
abdalazizrashid 6655cc8
Docs fix
abdalazizrashid 78b6b9e
Add support for strings in type specs
abdalazizrashid 0470722
Improve tests
abdalazizrashid 3f13fce
Use Union due Python 3.9
abdalazizrashid 773bba4
Spacing fix
abdalazizrashid fb1dcf1
Update docs
abdalazizrashid 56240b7
Merge branch 'main' of github.com:pymc-labs/pymc-marketing into Weibu…
abdalazizrashid a208794
Use numpy for cumprod
abdalazizrashid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
|
||
import numpy as np | ||
import numpy.typing as npt | ||
import pymc as pm | ||
import pytensor.tensor as pt | ||
from pytensor.tensor.random.utils import params_broadcast_shapes | ||
|
||
|
@@ -13,6 +14,11 @@ class ConvMode(Enum): | |
Overlap = "Overlap" | ||
|
||
|
||
class WeibullType(Enum): | ||
PDF = "PDF" | ||
CDF = "CDF" | ||
|
||
|
||
def batched_convolution(x, w, axis: int = 0, mode: ConvMode = ConvMode.Before): | ||
R"""Apply a 1D convolution in a vectorized way across multiple batch dimensions. | ||
|
||
|
@@ -260,6 +266,103 @@ def delayed_adstock( | |
return batched_convolution(x, w, axis=axis) | ||
|
||
|
||
def weibull_adstock( | ||
x, | ||
lam=1, | ||
k=1, | ||
l_max: int = 12, | ||
axis: int = 0, | ||
type: WeibullType = WeibullType.PDF, | ||
): | ||
R"""Weibull Adstocking Transformation. | ||
|
||
This transformation is similar to geometric adstock transformation but has more degrees of freedom, adding more flexibility. | ||
.. plot:: | ||
:context: close-figs | ||
abdalazizrashid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
import arviz as az | ||
from pymc_marketing.mmm.transformers import WeibullType, weibull_adstock | ||
plt.style.use('arviz-darkgrid') | ||
|
||
spend = np.zeros(50) | ||
spend[0] = 1 | ||
|
||
shapes = [0.5, 1., 1.5, 5.] | ||
scales = [10, 20, 40] | ||
modes = [WeibullType.PDF, WeibullType.CDF] | ||
|
||
fig, axes = plt.subplots( | ||
len(shapes), len(modes), figsize=(12, 8), sharex=True, sharey=True | ||
) | ||
fig.suptitle("Effect of Changing Weibull Adstock Parameters", fontsize=16) | ||
|
||
for m, mode in enumerate(modes): | ||
axes[0, m].set_title(f"Mode: {mode.value}") | ||
|
||
for i, shape in enumerate(shapes): | ||
for j, scale in enumerate(scales): | ||
adstock = weibull_adstock( | ||
spend, lam=scale, k=shape, type=mode, l_max=len(spend) | ||
).eval() | ||
|
||
axes[i, m].plot( | ||
np.arange(len(spend)), | ||
adstock, | ||
label=f"Scale={scale}", | ||
linestyle="-", | ||
alpha=0.5 | ||
) | ||
|
||
fig.legend( | ||
*axes[0, 0].get_legend_handles_labels(), | ||
loc="center right", | ||
bbox_to_anchor=(1.2, 0.85), | ||
) | ||
|
||
plt.tight_layout(rect=[0, 0, 0.9, 1]) | ||
plt.show() | ||
|
||
|
||
|
||
Parameters | ||
---------- | ||
x : tensor | ||
Input tensor. | ||
lam : float, by default 1. | ||
Scale parameter of the Weibull distribution. Must be positive. | ||
k : float, by default 1. | ||
Shape parameter of the Weibull distribution. Must be positive. | ||
l_max : int, by default 12 | ||
Maximum duration of carryover effect. | ||
type : WeibullType, by default WeibullType.PDF | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe good to add string to match with the type hint There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
Type of Weibull adstock transformation to be applied (PDF or CDF). | ||
|
||
Returns | ||
------- | ||
tensor | ||
Transformed tensor based on Weibull adstock transformation. | ||
""" | ||
lam = pt.as_tensor(lam)[..., None] | ||
k = pt.as_tensor(k)[..., None] | ||
t = pt.arange(l_max, dtype=x.dtype) + 1 | ||
|
||
if type == WeibullType.PDF: | ||
w = pt.exp(pm.Weibull.logp(t, k, lam)) | ||
w = (w - pt.min(w, axis=-1)[..., None]) / ( | ||
pt.max(w, axis=-1)[..., None] - pt.min(w, axis=-1)[..., None] | ||
) | ||
elif type == WeibullType.CDF: | ||
w = 1 - pt.exp(pm.Weibull.logcdf(t, k, lam)) | ||
shape = (*w.shape[:-1], w.shape[-1] + 1) | ||
padded_w = pt.ones(shape, dtype=w.dtype) | ||
padded_w = pt.set_subtensor(padded_w[..., 1:], w) | ||
w = pt.cumprod(padded_w, axis=-1) | ||
else: | ||
raise ValueError(f"Wrong WeibullType: {type}, expected of WeibullType") | ||
return batched_convolution(x, w, axis=axis) | ||
|
||
|
||
def logistic_saturation(x, lam: Union[npt.NDArray[np.float_], float] = 0.5): | ||
"""Logistic saturation transformation. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use string or a boolean variable so users don't have to import an object to be able to parametrize the function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a similar interface for batched_convolution too. Maybe make it
type: WeibullType | str = WeibullType.PDF
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will only every be pdf or not pdf (cdf), right? Think the boolean simplifies it heavily