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

excluded _prior suffix from CLV models #1498

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion docs/source/notebooks/clv/bg_nbd.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@
"id": "0a5fc96b-eedd-43ae-bd70-5ae388f1f636",
"metadata": {},
"source": [
"Notice the additional `phi_dropout` and `kappa_dropout` priors. These were added to the default configuration to improve performance, but can be omitted when specifying a custom `model_config` with `a_prior` and `b_prior`.\n",
"Notice the additional `phi_dropout` and `kappa_dropout` priors. These were added to the default configuration to improve performance, but can be omitted when specifying a custom `model_config` with `a` and `b`.\n",
"\n",
"The specified model structure can also be visualized:"
]
Expand Down
8 changes: 4 additions & 4 deletions docs/source/notebooks/clv/clv_quickstart.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -576,10 +576,10 @@
"outputs": [],
"source": [
"model_config = {\n",
" \"a_prior\": Prior(\"HalfNormal\", sigma=10),\n",
" \"b_prior\": Prior(\"HalfNormal\", sigma=10),\n",
" \"alpha_prior\": Prior(\"HalfNormal\", sigma=10),\n",
" \"r_prior\": Prior(\"HalfNormal\", sigma=10),\n",
" \"a\": Prior(\"HalfNormal\", sigma=10),\n",
" \"b\": Prior(\"HalfNormal\", sigma=10),\n",
" \"alpha\": Prior(\"HalfNormal\", sigma=10),\n",
" \"r\": Prior(\"HalfNormal\", sigma=10),\n",
"}"
]
},
Expand Down
8 changes: 4 additions & 4 deletions docs/source/notebooks/clv/dev/beta_geo_beta_binom.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@
],
"source": [
"model_config = {\n",
" \"alpha_prior\": Prior(\"HalfFlat\"),\n",
" \"beta_prior\": Prior(\"HalfFlat\"),\n",
" \"gamma_prior\": Prior(\"HalfFlat\"),\n",
" \"delta_prior\": Prior(\"HalfFlat\"),\n",
" \"alpha\": Prior(\"HalfFlat\"),\n",
" \"beta\": Prior(\"HalfFlat\"),\n",
" \"gamma\": Prior(\"HalfFlat\"),\n",
" \"delta\": Prior(\"HalfFlat\"),\n",
"}\n",
"\n",
"model = BetaGeoBetaBinomModel(data=data,model_config=model_config)\n",
Expand Down
2 changes: 1 addition & 1 deletion docs/source/notebooks/clv/mbg_nbd.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@
"id": "0a5fc96b-eedd-43ae-bd70-5ae388f1f636",
"metadata": {},
"source": [
"Notice the additional `phi_dropout` and `kappa_dropout` priors. These were added to the default configuration to improve performance, but can be omitted when specifying a custom `model_config` with `a_prior` and `b_prior`.\n",
"Notice the additional `phi_dropout` and `kappa_dropout` priors. These were added to the default configuration to improve performance, but can be omitted when specifying a custom `model_config` with `a` and `b`.\n",
"\n",
"The specified model structure can also be visualized:"
]
Expand Down
8 changes: 4 additions & 4 deletions docs/source/notebooks/clv/pareto_nbd.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -588,10 +588,10 @@
"outputs": [],
"source": [
"flat_config = {\n",
" \"r_prior\": Prior(\"HalfFlat\"),\n",
" \"alpha_prior\": Prior(\"HalfFlat\"),\n",
" \"s_prior\": Prior(\"HalfFlat\"),\n",
" \"beta_prior\": Prior(\"HalfFlat\"),\n",
" \"r\": Prior(\"HalfFlat\"),\n",
" \"alpha\": Prior(\"HalfFlat\"),\n",
" \"s\": Prior(\"HalfFlat\"),\n",
" \"beta\": Prior(\"HalfFlat\"),\n",
"}\n",
"\n",
"pnbd_pymc = clv.ParetoNBDModel(data=rfm_data, model_config=flat_config)"
Expand Down
29 changes: 29 additions & 0 deletions pymc_marketing/clv/models/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@
non_distributions: list[str] | None = None,
):
model_config = model_config or {}

deprecated_keys = [key for key in model_config if key.endswith("_prior")]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's add a TODO to remove these codelines in a future release. I'd say 2025-Q4 or early 2026 will give users enough time to update their codebases.

for key in deprecated_keys:
new_key = key.replace("_prior", "")
warnings.warn(

Check warning on line 54 in pymc_marketing/clv/models/basic.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/basic.py#L53-L54

Added lines #L53 - L54 were not covered by tests
f"The key '{key}' in model_config is deprecated and will be removed in future versions."
f"Use '{new_key}' instead.",
DeprecationWarning,
stacklevel=2,
)

model_config[new_key] = model_config.pop(key)

Check warning on line 61 in pymc_marketing/clv/models/basic.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/basic.py#L61

Added line #L61 was not covered by tests

model_config = parse_model_config(
model_config,
non_distributions=non_distributions,
Expand Down Expand Up @@ -279,12 +292,28 @@
model_config=json.loads(idata.attrs["model_config"]), # type: ignore
sampler_config=json.loads(idata.attrs["sampler_config"]),
)

model.idata = idata
model._rename_posterior_variables()

Check warning on line 297 in pymc_marketing/clv/models/basic.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/basic.py#L297

Added line #L297 was not covered by tests

model.build_model() # type: ignore
if model.id != idata.attrs["id"]:
raise ValueError(f"Inference data not compatible with {cls._model_type}")
return model

def _rename_posterior_variables(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's add a TODO to remove this in a future release. I'd say 2025-Q4 or early 2026 will give users enough time to update their codebases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's do an issue instead 🙏

"""Rename variables in the posterior group to remove the _prior suffix.

This is used to support the old model configuration format, which used
to include a _prior suffix for each parameter.
"""
prior_vars = [

Check warning on line 310 in pymc_marketing/clv/models/basic.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/basic.py#L310

Added line #L310 was not covered by tests
var for var in self.idata.posterior.data_vars if var.endswith("_prior")
]
rename_dict = {var: var.replace("_prior", "") for var in prior_vars}
self.idata.posterior = self.idata.posterior.rename(rename_dict)
return self.idata.posterior

Check warning on line 315 in pymc_marketing/clv/models/basic.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/basic.py#L313-L315

Added lines #L313 - L315 were not covered by tests

def thin_fit_result(self, keep_every: int):
"""Return a copy of the model with a thinned fit result.

Expand Down
100 changes: 46 additions & 54 deletions pymc_marketing/clv/models/beta_geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@
* `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("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)`
* `kappa_dropout_prior`: Nested prior for a and b priors; defaults to `Prior("Pareto", alpha=1, m=1)`
* `purchase_covariates_prior`: Coefficients for purchase rate covariates; defaults to `Normal(0, 3)`
* `dropout_covariates_prior`: Coefficients for dropout covariates; defaults to `Normal.dist(0, 3)`
* `alpha`: Scale parameter for time between purchases; defaults to `Prior("Weibull", alpha=2, beta=10)`
* `r`: Shape parameter for time between purchases; defaults to `Prior("Weibull", alpha=2, beta=1)`
* `a`: Shape parameter of dropout process; defaults to `phi_purchase` * `kappa_purchase`
* `b`: Shape parameter of dropout process; defaults to `1-phi_dropout` * `kappa_dropout`
* `phi_dropout`: Nested prior for a and b priors; defaults to `Prior("Uniform", lower=0, upper=1)`
* `kappa_dropout`: Nested prior for a and b priors; defaults to `Prior("Pareto", alpha=1, m=1)`
* `purchase_covariates`: Coefficients for purchase rate covariates; defaults to `Normal(0, 3)`
* `dropout_covariates`: Coefficients for dropout covariates; defaults to `Normal.dist(0, 3)`
* `purchase_covariate_cols`: List containing column names of covariates for customer purchase rates.
* `dropout_covariate_cols`: List containing column names of covariates for customer dropouts.
sampler_config : dict, optional
Expand Down Expand Up @@ -100,10 +100,10 @@
model = BetaGeoModel(
data=data,
model_config={
"r_prior": Prior("Weibull", alpha=2, beta=1),
"alpha_prior": Prior("HalfFlat"),
"a_prior": Prior("Beta", alpha=2, beta=3),
"b_prior": Prior("Beta", alpha=3, beta=2),
"r": Prior("Weibull", alpha=2, beta=1),
"alpha": Prior("HalfFlat"),
"a": Prior("Beta", alpha=2, beta=3),
"b": Prior("Beta", alpha=3, beta=2),
},
sampler_config={
"draws": 1000,
Expand Down Expand Up @@ -185,12 +185,12 @@
def default_model_config(self) -> ModelConfig:
"""Default model configuration."""
return {
"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),
"purchase_coefficient_prior": Prior("Normal", mu=0, sigma=1),
"dropout_coefficient_prior": Prior("Normal", mu=0, sigma=1),
"alpha": Prior("Weibull", alpha=2, beta=10),
"r": Prior("Weibull", alpha=2, beta=1),
"phi_dropout": Prior("Uniform", lower=0, upper=1),
"kappa_dropout": Prior("Pareto", alpha=1, m=1),
"purchase_coefficient": Prior("Normal", mu=0, sigma=1),
"dropout_coefficient": Prior("Normal", mu=0, sigma=1),
"purchase_covariate_cols": [],
"dropout_covariate_cols": [],
}
Expand All @@ -211,16 +211,12 @@
self.data[self.purchase_covariate_cols],
dims=["customer_id", "purchase_covariate"],
)
self.model_config[
"purchase_coefficient_prior"
].dims = "purchase_covariate"
self.model_config["purchase_coefficient"].dims = "purchase_covariate"

Check warning on line 214 in pymc_marketing/clv/models/beta_geo.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/beta_geo.py#L214

Added line #L214 was not covered by tests
purchase_coefficient_alpha = self.model_config[
"purchase_coefficient_prior"
"purchase_coefficient"
].create_variable("purchase_coefficient_alpha")

alpha_scale = self.model_config["alpha_prior"].create_variable(
"alpha_scale"
)
alpha_scale = self.model_config["alpha"].create_variable("alpha_scale")

Check warning on line 219 in pymc_marketing/clv/models/beta_geo.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/beta_geo.py#L219

Added line #L219 was not covered by tests
alpha = pm.Deterministic(
"alpha",
(
Expand All @@ -232,29 +228,27 @@
dims="customer_id",
)
else:
alpha = self.model_config["alpha_prior"].create_variable("alpha")
alpha = self.model_config["alpha"].create_variable("alpha")

# dropout priors
if "a_prior" in self.model_config and "b_prior" in self.model_config:
if "a" in self.model_config and "b" in self.model_config:
if self.dropout_covariate_cols:
dropout_data = pm.Data(
"dropout_data",
self.data[self.dropout_covariate_cols],
dims=["customer_id", "dropout_covariate"],
)

self.model_config[
"dropout_coefficient_prior"
].dims = "dropout_covariate"
self.model_config["dropout_coefficient"].dims = "dropout_covariate"

Check warning on line 242 in pymc_marketing/clv/models/beta_geo.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/beta_geo.py#L242

Added line #L242 was not covered by tests
dropout_coefficient_a = self.model_config[
"dropout_coefficient_prior"
"dropout_coefficient"
].create_variable("dropout_coefficient_a")
dropout_coefficient_b = self.model_config[
"dropout_coefficient_prior"
"dropout_coefficient"
].create_variable("dropout_coefficient_b")

a_scale = self.model_config["a_prior"].create_variable("a_scale")
b_scale = self.model_config["b_prior"].create_variable("b_scale")
a_scale = self.model_config["a"].create_variable("a_scale")
b_scale = self.model_config["b"].create_variable("b_scale")

Check warning on line 251 in pymc_marketing/clv/models/beta_geo.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/beta_geo.py#L250-L251

Added lines #L250 - L251 were not covered by tests
a = pm.Deterministic(
"a",
a_scale
Expand All @@ -268,8 +262,8 @@
dims="customer_id",
)
else:
a = self.model_config["a_prior"].create_variable("a")
b = self.model_config["b_prior"].create_variable("b")
a = self.model_config["a"].create_variable("a")
b = self.model_config["b"].create_variable("b")
else:
# hierarchical pooling of dropout rate priors
if self.dropout_covariate_cols:
Expand All @@ -279,22 +273,20 @@
dims=["customer_id", "dropout_covariate"],
)

self.model_config[
"dropout_coefficient_prior"
].dims = "dropout_covariate"
self.model_config["dropout_coefficient"].dims = "dropout_covariate"

Check warning on line 276 in pymc_marketing/clv/models/beta_geo.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/beta_geo.py#L276

Added line #L276 was not covered by tests
dropout_coefficient_a = self.model_config[
"dropout_coefficient_prior"
"dropout_coefficient"
].create_variable("dropout_coefficient_a")
dropout_coefficient_b = self.model_config[
"dropout_coefficient_prior"
"dropout_coefficient"
].create_variable("dropout_coefficient_b")

phi_dropout = self.model_config[
"phi_dropout_prior"
].create_variable("phi_dropout")
kappa_dropout = self.model_config[
"kappa_dropout_prior"
].create_variable("kappa_dropout")
phi_dropout = self.model_config["phi_dropout"].create_variable(

Check warning on line 284 in pymc_marketing/clv/models/beta_geo.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/beta_geo.py#L284

Added line #L284 was not covered by tests
"phi_dropout"
)
kappa_dropout = self.model_config["kappa_dropout"].create_variable(

Check warning on line 287 in pymc_marketing/clv/models/beta_geo.py

View check run for this annotation

Codecov / codecov/patch

pymc_marketing/clv/models/beta_geo.py#L287

Added line #L287 was not covered by tests
"kappa_dropout"
)

a_scale = pm.Deterministic(
"a_scale",
Expand All @@ -319,18 +311,18 @@
)

else:
phi_dropout = self.model_config[
"phi_dropout_prior"
].create_variable("phi_dropout")
kappa_dropout = self.model_config[
"kappa_dropout_prior"
].create_variable("kappa_dropout")
phi_dropout = self.model_config["phi_dropout"].create_variable(
"phi_dropout"
)
kappa_dropout = self.model_config["kappa_dropout"].create_variable(
"kappa_dropout"
)

a = pm.Deterministic("a", phi_dropout * kappa_dropout)
b = pm.Deterministic("b", (1.0 - phi_dropout) * kappa_dropout)

# r remains unchanged with or without covariates
r = self.model_config["r_prior"].create_variable("r")
r = self.model_config["r"].create_variable("r")

BetaGeoNBD(
name="recency_frequency",
Expand Down
Loading