Skip to content

Commit

Permalink
Allow plot_expected_purchases_pcc in BetaGeoModel and `ModifiedBe…
Browse files Browse the repository at this point in the history
…taGeoModel` (#1470)

* Remove Exception in plot_expected_purchases_pcc. Update notebooks. Fix ModifiedBetaGeoNBDRV sim_data

* Clean notebook cell outputs

* Remove exception in test_plot_expected_purchases_ppc_exceptions

* Parametrize tests

* Parametrize fixture
  • Loading branch information
PabloRoque authored Feb 11, 2025
1 parent 596e7d4 commit cf3eab2
Show file tree
Hide file tree
Showing 7 changed files with 3,083 additions and 1,269 deletions.
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),
"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]

0 comments on commit cf3eab2

Please sign in to comment.