Fitting the Model
Use this page after you have prepared X and y for PanelMMM. For input
requirements, see Data Preparation.
Basic workflow
fit() is the main entry point for posterior sampling.
import pandas as pd
from abacus.mmm import GeometricAdstock, LogisticSaturation
from abacus.mmm.panel import PanelMMM
dataset = pd.read_csv("data/demo/timeseries/dataset.csv")
dataset["date"] = pd.to_datetime(dataset["date"])
X = dataset.drop(columns=["revenue"])
y = dataset["revenue"].rename("revenue")
mmm = PanelMMM(
date_column="date",
target_column="revenue",
channel_columns=[
"channel_1",
"channel_2",
"channel_3",
"channel_4",
"channel_5",
"channel_6",
],
yearly_seasonality=2,
adstock=GeometricAdstock(l_max=4),
saturation=LogisticSaturation(),
)
idata = mmm.fit(
X,
y,
draws=500,
tune=500,
chains=2,
cores=2,
progressbar=False,
random_seed=42,
)
fit() returns an arviz.InferenceData object and also stores it on
mmm.idata.
What fit() does
When you call fit(X, y), AMMM3:
- checks that pandas
Xandyuse the same index, if both are pandas objects - builds the PyMC graph automatically if it has not been built already, or
checks that
Xandyequal the training data that built the existing graph (see Fitting an existing graph again) - merges sampler settings from the model’s
sampler_configand your call-time kwargs - runs
pymc.sample(...) - computes deterministic variables and adds them to the posterior group
- stores the training data in an
InferenceData.fit_datagroup - writes model metadata into
idata.attrs
That means fitted contribution variables such as channel_contribution,
intercept_contribution, and yearly_seasonality_contribution are available
in mmm.posterior after fitting when they are part of the configured model.
Configure the sampler
You can configure PyMC sampling in two places:
| Where | Use it for | Precedence |
|---|---|---|
sampler_config= in PanelMMM(...) | Stable defaults you want to reuse across fits | Lower |
fit(..., **kwargs) | Run-specific overrides such as draws, chains, or random_seed | Higher |
AMMM3 merges them so that explicit fit() kwargs win.
mmm = PanelMMM(
date_column="date",
target_column="revenue",
channel_columns=["channel_1", "channel_2"],
adstock=GeometricAdstock(l_max=4),
saturation=LogisticSaturation(),
sampler_config={
"draws": 1000,
"tune": 1000,
"chains": 4,
"target_accept": 0.9,
"progressbar": False,
},
)
# Overrides draws from sampler_config, keeps target_accept
idata = mmm.fit(X, y, draws=500, random_seed=42)
Common sampler arguments
These are passed through to pymc.sample(...).
| Argument | What it controls |
|---|---|
draws | Posterior samples kept after tuning |
tune | Warm-up or adaptation iterations |
chains | Number of MCMC chains |
cores | Number of worker processes used by PyMC |
target_accept | HMC or NUTS acceptance target |
progressbar | Whether PyMC shows a progress bar |
random_seed | Sampling reproducibility |
If you do not specify progressbar, AMMM3 defaults it to True unless your
sampler_config already sets it.
When to build first
For a standard workflow, call fit() directly.
Call build_model(X, y) first only when you need to inspect or modify the
graph before sampling. For example:
mmm.build_model(X, y)
mmm.add_original_scale_contribution_variable(
var=["channel_contribution", "y"]
)
idata = mmm.fit(
X,
y,
draws=500,
tune=500,
chains=2,
progressbar=False,
random_seed=42,
)
This pattern is also useful when you need to add events before fitting. Call
add_events(...) before build_model(...) or fit(...).
Fitting an existing graph again
A built graph holds the observed data, scaling and fitted preprocessing for
one training dataset. fit() never rebuilds an existing graph, so a second
fit(X, y) on the same instance is accepted only when X and y equal the
data that built it.
- Equal copies are accepted. AMMM3 compares values, labels, dates and panel coordinates after its normal input alignment, not object identity. Row order and string versus datetime spellings of the same dates do not matter.
- Sampler settings may change between fits.
draws,tune,chainsandrandom_seedare not part of the training data. - Changed training data raise
ValueErrorbefore sampling starts. The existing graph, scaling andidatastay as they were. This includes changes to the target, media or control values, date labels, panel unit labels, and in-place edits to the originalXoryafter the graph was built.
To fit different data, create a new model instance:
mmm_new = PanelMMM(**mmm_kwargs)
idata_new = mmm_new.fit(X_new, y_new)
The same rule applies to approximate_fit(), to graphs built by
sample_prior_predictive(X, y), and to models restored with PanelMMM.load().
Prediction must also leave the training graph intact before it can be fitted
again. PanelMMM.sample_posterior_predictive() normally uses a separate graph.
For time-series and FE models, clone_model=False updates the training graph
and prevents subsequent fit() or approximate_fit() calls, even with the
original data. This also applies if prediction fails after the update starts.
Create a new model instance or load the saved fit to obtain a training graph.
CRE prediction always uses a separate forecast graph and does not invalidate
the training graph. The shared RegressionModelBuilder prediction method
updates through its data setter and likewise prevents subsequent fitting.
Inspect fitted results
After fitting, common entry points are:
mmm.idatammm.posteriormmm.modelmmm.plotmmm.summarymmm.diagnostics
Example:
posterior = mmm.posterior
channel_mean = posterior["channel_contribution"].mean(dim=["chain", "draw"])
Common pitfalls
- Leaving the target column inside
X - Passing pandas
Xandywith different indexes - Calling
fit()again with different data on an instance whose graph is already built; create a new instance instead - Changing the model graph after fitting and expecting existing samples to stay valid
- Assuming constructor
sampler_configoverrides explicitfit()kwargs; it does not - Adding events after the model has already been built
Next steps
- Run Prior Predictive Checks before posterior sampling when you are tuning priors or model structure.
- Read Save and Load if you want to persist a fitted model.