BassModel#

class pymc_marketing.bass.model.BassModel(model_config=None, sampler_config=None)[source]#

Bass diffusion model for product adoption forecasting.

Wraps the functional create_bass_model() inside the ModelBuilder interface, providing standardised .fit(), .save(), .load() and related methods. The underlying pm.Model is accessible via model.model for direct use with PyMC functions.

Parameters:
model_configdict, optional

Dictionary with keys "m", "p", "q", "likelihood" mapping to Prior or a term recipe built from pymc_marketing.terms. A recipe may carry covariates through Dot; the dataset they are read from is the one passed to fit().

A recipe that carries its own name must name itself after the config key, so the posterior keeps a variable named m/p/ q; compositions and nameless terms (e.g. Transform) are wrapped in Named automatically, so no Named is needed here. p and q must not vary over T — Bass uses the closed-form solution, which assumes time-constant rates, and a time-varying rate is rejected — but may carry other dimensions such as product, giving a per-product rate. See default_model_config() for defaults.

sampler_configdict, optional

Dictionary of sampler settings (draws, tune, chains, …). See default_sampler_config() for defaults.

Notes

Data format#

When using xr.Dataset, the T coordinate is required and represents the time index. An observed data variable can hold adoption counts (omit for prior-predictive only).

Single-product — 1-D observed with T as the only dimension:

xr.Dataset(
    {"observed": ("T", counts)},
    coords={"T": np.arange(N)},
)

Multi-product — observed with T and product dimensions:

xr.Dataset(
    {"observed": (("T", "product"), counts)},
    coords={"T": np.arange(N), "product": ["A", "B", "C"]},
)

Other input types (np.ndarray, pd.Series, pd.DataFrame) are auto-converted via to_bass_dataset().

Examples

Fit a single-product model

import numpy as np
import arviz as az
from pymc_marketing.bass import BassModel

y = np.random.poisson(lam=100, size=50)
model = BassModel()
idata = model.fit(data=y)
print(az.summary(idata, var_names=["m", "p", "q"]))

Multi-product with custom priors

import xarray as xr
from pymc_extras.prior import Prior

data = xr.Dataset(
    {"observed": (("T", "product"), np.random.poisson(100, size=(50, 3)))},
    coords={"T": np.arange(50), "product": ["A", "B", "C"]},
)
model = BassModel(
    model_config={
        "m": Prior("Normal", mu=5_000, sigma=1_000),
        "p": Prior("Beta", alpha=1.5, beta=20),
        "q": Prior("Beta", alpha=2, beta=5),
        "likelihood": Prior("Poisson"),
    },
)
idata = model.fit(data=data)
print(az.summary(idata, var_names=["m", "p", "q"]))

Term recipes, with a per-product covariate

m/p/q accept recipes composed from pymc_marketing.terms, so a rate can be scaled by a covariate read from the dataset. A recipe that names itself must use the config key as its name; compositions and nameless terms are wrapped automatically, so no Named is needed here. Only the variables a recipe names are registered, and a covariate must vary across a dimension other than T (p and q assume time-constant rates).

import pytensor.xtensor as ptx
from pymc_extras.prior import Prior
from pymc_marketing.terms import Dot, Parameter, Transform

data = xr.Dataset(
    {
        "observed": (("T", "product"), np.random.poisson(100, size=(50, 3))),
        # one covariate value per product
        "log_market_size": ("product", np.log([100.0, 250.0, 60.0])),
    },
    coords={"T": np.arange(50), "product": ["A", "B", "C"]},
)
model = BassModel(
    model_config={
        "m": Parameter("m", prior=Prior("HalfNormal", sigma=100_000)),
        # a per-product innovation rate, positive by construction
        "p": Parameter("p_base", prior=Prior("Beta", alpha=1.5, beta=20))
        * Transform(
            Dot(
                var_name="log_market_size",
                name="p_coef",
                prior=Prior("Normal", mu=0, sigma=0.3),
            ),
            ptx.math.exp,
        ),
        "q": Prior("Beta", alpha=2, beta=5),
        "likelihood": Prior("NegativeBinomial", n=1.5, dims="product"),
    },
)
idata = model.fit(data=data)
# p now carries a "product" dim, and the covariate is shared data
print(az.summary(idata, var_names=["m", "p", "q", "p_coef"]))

Generate synthetic data and fit

Build the model without an observed variable (only a T coordinate), draw a prior predictive sample, then fit to it:

import xarray as xr
import pymc as pm

ds = xr.Dataset({"T": np.arange(50)})
model = BassModel()
model.build_model(data=ds)

with model.model:
    prior = pm.sample_prior_predictive(draws=50, random_seed=42)
    y_sim = prior.prior["y"].sel(draw=0, chain=0)

# Now fit the model to the synthetic data
idata = model.fit(data=y_sim.values)

Posterior predictive checks

Generate posterior predictive samples after fitting:

pp_data = model.sample_posterior_predictive(X=new_time_points)

The posterior contains deterministics such as adopters, innovators, imitators, and peak that can be analysed directly via idata.posterior, e.g.:

azp.plot_forest(idata.posterior["peak"], combined=True)

Methods

BassModel.__init__([model_config, ...])

Initialize model configuration and sampler configuration for the model.

BassModel.attrs_to_init_kwargs(attrs)

Convert the model configuration and sampler configuration from the attributes to keyword arguments.

BassModel.build_from_idata(idata)

Rebuild the model from a DataTree object.

BassModel.build_model([data])

Build the Bass diffusion model from the given data.

BassModel.create_fit_data_group()

Build the fit_data group stored alongside the posterior.

BassModel.create_idata_attrs()

Create attributes for the inference data.

BassModel.fit([data, method, progressbar, ...])

Fit the Bass diffusion model.

BassModel.graphviz(**kwargs)

Get the graphviz representation of the model.

BassModel.idata_to_init_kwargs(idata)

Create the model configuration and sampler configuration from the DataTree to keyword arguments.

BassModel.load(fname[, check])

Create a ModelBuilder instance from a file.

BassModel.load_from_idata(idata[, check])

Create a ModelBuilder instance from a DataTree object.

BassModel.plot_adoption_curve(**kwargs)

Plot the posterior adoption curve with the observed data.

BassModel.plot_cumulative(**kwargs)

Plot the cumulative adoption S-curve with the observed data.

BassModel.plot_decomposition(**kwargs)

Plot the adoption decomposition into innovators and imitators.

BassModel.plot_peak(**kwargs)

Plot the posterior distribution of the peak adoption time.

BassModel.post_sample_model_transformation()

Perform transformation on the model after sampling.

BassModel.sample_posterior_predictive(X[, ...])

Sample from the model's posterior predictive distribution.

BassModel.save(fname, **kwargs)

Save the model's inference data to a file.

BassModel.set_idata_attrs([idata])

Set attributes on a DataTree object.

BassModel.table(**model_table_kwargs)

Get the summary table of the model.

Attributes

BassModel.default_model_config

Default model configuration with weakly informative priors.

BassModel.default_sampler_config

Default sampler configuration.

BassModel.fit_result

Get the posterior fit_result.

BassModel.id

Generate a unique hash value for the model.

BassModel.output_var

Return the name of the output variable.

BassModel.posterior

Access the 'posterior' group of the DataTree.

BassModel.posterior_predictive

Access the 'posterior_predictive' group of the DataTree.

BassModel.predictions

Access the 'predictions' group of the DataTree.

BassModel.prior

Access the 'prior' group of the DataTree.

BassModel.prior_predictive

Access the 'prior_predictive' group of the DataTree.

BassModel.version

BassModel.idata

BassModel.sampler_config

BassModel.model_config

BassModel.model

BassModel.is_fitted_