Skip to content
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

Allow plot_expected_purchases_pcc in BetaGeoModel and ModifiedBetaGeoModel #1470

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,218 changes: 1,545 additions & 673 deletions docs/source/notebooks/clv/bg_nbd.ipynb

Large diffs are not rendered by default.

2,055 changes: 1,481 additions & 574 deletions docs/source/notebooks/clv/mbg_nbd.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies:
- pip
- pydantic
- preliz
- pyprojroot
# NOTE: Keep minimum pymc version in sync with ci.yml `OLDEST_PYMC_VERSION`
- pymc>=5.20.0
- pymc-extras>=0.2.1
Expand Down
14 changes: 7 additions & 7 deletions pymc_marketing/clv/models/beta_geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ class BetaGeoModel(CLVModel):
* `T`: Time between the first purchase and the end of the observation period
model_config : dict, optional
Dictionary of model prior parameters:
* `alpha_prior`: Scale parameter for time between purchases; defaults to `Prior("HalfFlat")`
* `r_prior`: Shape parameter for time between purchases; defaults to `Prior("HalfFlat")`
* `alpha_prior`: Scale parameter for time between purchases; defaults to `Prior("Weibull", alpha=2, beta=10)`
* `r_prior`: Shape parameter for time between purchases; defaults to `Prior("Weibull", alpha=2, beta=1)`
* `a_prior`: Shape parameter of dropout process; defaults to `phi_purchase_prior` * `kappa_purchase_prior`
* `b_prior`: Shape parameter of dropout process; defaults to `1-phi_dropout_prior` * `kappa_dropout_prior`
* `phi_dropout_prior`: Nested prior for a and b priors; defaults to `Prior("Uniform", lower=0, upper=1)`
Expand Down Expand Up @@ -96,10 +96,10 @@ class BetaGeoModel(CLVModel):
model = BetaGeoModel(
data=data,
model_config={
"r_prior": Prior("HalfFlat"),
"r_prior": Prior("Weibull", alpha=2, beta=1),
"alpha_prior": Prior("HalfFlat"),
"a_prior": Prior("HalfFlat"),
"b_prior": Prior("HalfFlat),
"a_prior": Prior("Beta", alpha=2, beta=3),
"b_prior": Prior("Beta", alpha=3, beta=2),
},
sampler_config={
"draws": 1000,
Expand Down Expand Up @@ -166,8 +166,8 @@ def __init__(
def default_model_config(self) -> ModelConfig:
"""Default model configuration."""
return {
"alpha_prior": Prior("HalfFlat"),
"r_prior": Prior("HalfFlat"),
"alpha_prior": Prior("Weibull", alpha=2, beta=10),
"r_prior": Prior("Weibull", alpha=2, beta=1),
Comment on lines +169 to +170
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? (I'm not opposed, just curious.)

"phi_dropout_prior": Prior("Uniform", lower=0, upper=1),
"kappa_dropout_prior": Prior("Pareto", alpha=1, m=1),
}
Expand Down
6 changes: 1 addition & 5 deletions pymc_marketing/clv/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ def plot_expected_purchases_ppc(
) -> plt.Axes:
"""Plot a prior or posterior predictive check for the customer purchase frequency distribution.

At this time only ``ParetoNBDModel`` and ``BetaGeoBetaBinomModel`` are supported.
``ParetoNBDModel``, ``BetaGeoBetaBinomModel``, ``BetaGeoModel`` and ``ModifiedBetaGeoModel`` are supported.

Adapted from legacy ``lifetimes`` library:
https://github.com/CamDavidsonPilon/lifetimes/blob/master/lifetimes/plotting.py#L25
Expand All @@ -515,10 +515,6 @@ def plot_expected_purchases_ppc(
-------
axes : matplotlib.AxesSubplot
"""
# TODO: BetaGeoModel requires its own dist class in distributions.py for this function.
if isinstance(model, BetaGeoModel):
raise AttributeError("BetaGeoModel is unsupported for this function.")

if ax is None:
ax = plt.subplot(111)

Expand Down
15 changes: 6 additions & 9 deletions tests/clv/test_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,25 +182,22 @@ def test_plot_expected_purchases_over_time(
plt.clf()


def test_plot_expected_purchases_ppc_exceptions(fitted_bg, fitted_pnbd):
with pytest.raises(
AttributeError, match="BetaGeoModel is unsupported for this function."
):
plot_expected_purchases_ppc(fitted_bg)

def test_plot_expected_purchases_ppc_exceptions(fitted_model):
with pytest.raises(
NameError, match="Specify 'prior' or 'posterior' for 'ppc' parameter."
):
plot_expected_purchases_ppc(fitted_pnbd, ppc="ppc")
plot_expected_purchases_ppc(fitted_model, ppc="ppc")


@pytest.mark.parametrize(
"ppc, max_purchases, samples, subplot",
[("prior", 10, 100, None), ("posterior", 20, 50, plt.subplot())],
)
def test_plot_expected_purchases_ppc(fitted_pnbd, ppc, max_purchases, samples, subplot):
def test_plot_expected_purchases_ppc(
fitted_model, ppc, max_purchases, samples, subplot
):
ax = plot_expected_purchases_ppc(
model=fitted_pnbd,
model=fitted_model,
ppc=ppc,
max_purchases=max_purchases,
samples=samples,
Expand Down
43 changes: 42 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@
from arviz import InferenceData
from xarray import DataArray, Dataset

from pymc_marketing.clv.models import BetaGeoModel, CLVModel, ParetoNBDModel
from pymc_marketing.clv.models import (
BetaGeoModel,
CLVModel,
ModifiedBetaGeoModel,
ParetoNBDModel,
)
from pymc_marketing.prior import Prior


Expand Down Expand Up @@ -191,6 +196,32 @@ def fitted_bg(test_summary_data) -> BetaGeoModel:
return model


# TODO: This fixture is used in the plotting and utils test modules.
# Consider creating a MockModel class to replace this and other fitted model fixtures.
@pytest.fixture(scope="module")
def fitted_mbg(test_summary_data) -> ModifiedBetaGeoModel:
rng = np.random.default_rng(13)

model_config = {
# Narrow Gaussian centered at MLE params from lifetimes BetaGeoFitter
"a_prior": Prior("DiracDelta", c=1.85034151),
"alpha_prior": Prior("DiracDelta", c=1.86428187),
"b_prior": Prior("DiracDelta", c=3.18105431),
"r_prior": Prior("DiracDelta", c=0.16385072),
}
model = ModifiedBetaGeoModel(
data=test_summary_data,
model_config=model_config,
)
model.build_model()
fake_fit = pm.sample_prior_predictive(draws=50, model=model.model, random_seed=rng)
# posterior group required to pass L80 assert check
fake_fit.add_groups(posterior=fake_fit.prior)
set_model_fit(model, fake_fit)

return model


# TODO: This fixture is used in the plotting and utils test modules.
# Consider creating a MockModel class to replace this and other fitted model fixtures.
@pytest.fixture(scope="module")
Expand Down Expand Up @@ -222,3 +253,13 @@ def fitted_pnbd(test_summary_data) -> ParetoNBDModel:
set_model_fit(pnbd_model, fake_fit)

return pnbd_model


@pytest.fixture(params=["bg_model", "mbg_model", "pnbd_model"])
def fitted_model(request, fitted_bg, fitted_mbg, fitted_pnbd):
fitted_models = {
"bg_model": fitted_bg,
"mbg_model": fitted_mbg,
"pnbd_model": fitted_pnbd,
}
return fitted_models[request.param]