Priors and Boundaries

For the workflow guidance behind these controls, start with Stage 2: Model and Priors. This page is the technical contract for dsambayesplus prior and boundary behaviour.

Use this page when you need exact dsambayesplus semantics: supported prior families, override syntax, default generation rules, and scaling behaviour. Do not use it as the main argument for why a prior is reasonable. That reasoning belongs in the workflow pages and in your modelling rationale.

Purpose

This page defines how dsambayesplus specifies, defaults, overrides, and scales coefficient priors and parameter boundaries for all model classes. It covers the prior schema, supported families, default-generation logic, YAML override contract, and the interaction between priors, boundaries, and the scale=TRUE pathway.

How to read this page

  • Use Minimal-Prior Policy if you want the short recommended operating rule.
  • Use this page when you need to know exactly how dsambayesplus will interpret a prior or boundary specification.
  • Return to Stage 2: Model and Priors if the question is whether a custom prior should be added at all.

Prior schema

Each model object carries a .prior tibble with one row per parameter. The columns are:

Column Type Meaning
parameter character Parameter name (matches design-matrix column or special name)
description character Human-readable label
distribution call R distribution call, e.g. normal(0, 5)
is_default logical Whether the row was generated by default_prior()

Supported prior families

Family Stan encoding Use case
normal(mean, sd) Default (prior_family_noise_sd = 0) Coefficient priors (location–scale)
lognormal_ms(mean, sd) Encoded with log-transformed parameters Positive noise_sd and hierarchical sd_<idx>[<term>] priors

All coefficient priors use normal(). The lognormal_ms family is parameterised by the mean and standard deviation on the original (non-log) scale; dsambayesplus converts these internally to log-space parameters.

Default prior generation

BLM and hierarchical (population terms)

default_prior.blm() calls standard_prior_terms(). On the reporting scale, the intercept prior is normal(ybar, sy), each slope prior is normal(0, sy / sx), and the residual-SD prior is normal(0, sy), where sy and sx are the response and term standard deviations. These defaults therefore map to unit-scale priors after internal standardisation and respond coherently when measurement units change.

Hierarchical (group-level standard deviations)

default_prior.hierarchical() additionally generates sd_<idx>[<term>] rows for each group factor. Each constrained group SD receives a zero-centred normal prior whose scale matches the corresponding coefficient scale: sy for a group intercept and sy / sx for a random slope. Because the SD parameter is constrained positive in Stan, this acts as a half-normal prior. It remains positive when observed group outcome means happen to be equal, and random-slope defaults change coherently when predictor measurement units change.

Specify hierarchical standard-deviation priors in reporting-scale units. With scale = TRUE, dsambayesplus transforms them to the Stan scale: intercept heterogeneity is divided by the response standard deviation, while slope heterogeneity is multiplied by the predictor-to-response standard-deviation ratio. The group intercept is defined at the internally centred predictor reference point. It is not a group intercept at raw predictor values of zero. With the defaults above, these transformations produce unit-scale group-SD priors in Stan space. Explicit user overrides retain their supplied reporting-scale meaning and are transformed by the same rules.

Default population and group-SD scales use the same population-model complete-case frame. This keeps the reporting-to-Stan transformation exact when a population term contains missing values. Random-slope terms must be numeric. Group-only random-slope terms are evaluated on the retained population rows. dsambayesplus aborts rather than deriving a group-SD default when a group term would drop additional rows or when a factor random slope is unsupported.

BLM from lm (Bayesian updating)

default_prior.bayes_lm_updater() initialises coefficient priors from the OLS point estimates (mean) and standard errors (sd), enabling informative Bayesian updating.

Pooled

default_prior.pooled() uses the BLM defaults for non-pooled terms (intercept, base regressors, noise_sd) and normal(0, 5) for each dimension-level pooled coefficient. Default pooled boundaries remain unconstrained; add explicit boundaries if a pooled dimension should be sign-restricted.

Boundary schema

Each model object carries a .boundaries tibble with one row per parameter:

Column Type Meaning
parameter character Parameter name
description character Human-readable label
boundary list-column List with $lower and $upper (numeric scalars)
is_default logical Whether the row was generated by default_boundary()

Default boundaries are lower = -Inf, upper = Inf for all terms. No sign constraints are imposed by default.

YAML override contract

Prior overrides

priors:
  use_defaults: true
  overrides:
    - { parameter: m_tv, mean: 0.5, sd: 0.2 }
    - { parameter: price_index, mean: -0.2, sd: 0.1 }

Each override replaces the distribution call for the named parameter with normal(mean, sd). Overrides are sparse: only the listed parameters are changed; all other parameters keep their defaults.

In M1, use_defaults must remain true. The v2 runner is default-first: it always starts from the generated prior table, then applies sparse grouped aliases and explicit overrides.

The friendly YAML surface also accepts an explicit alias style:

priors:
  media_beta:
    distribution: HalfNormal
    sigma: 1

or:

priors:
  intercept:
    distribution: Normal
    mu: 120000
    sigma: 15000

HalfNormal is implemented by compiling to normal(0, sigma) plus an implied lower bound of 0 on targeted parameters that are otherwise unconstrained. The priors.likelihood.sigma alias compiles to the dsambayesplus noise_sd prior family.

Boundary overrides

boundaries:
  overrides:
    - { parameter: m_tv, lower: 0.0, upper: .Inf }
    - { parameter: competitor_discount, lower: -.Inf, upper: 0.0 }

Each override replaces the boundary entry for the named parameter. YAML infinity tokens (.Inf, -.Inf) are coerced during config resolution.

Scale semantics (scale = TRUE)

When model.scale: true (the default), the response and predictors are standardised before Stan fitting. Centring applies only to population formulae with an intercept. Through-origin formulae divide by the same standard deviations without centring. This affects both priors and boundaries.

Coefficient prior scaling

For BLM and hierarchical population terms, the fitted intercept refers to centred predictors on the standardised model-response scale. The reported population intercept is back-transformed to original predictor zero. For a log-response model, the unstandardised model response is log-KPI, not KPI.

Let cy and sy be the centre and SD of the model response, and cx[j] and sx[j] the corresponding predictor quantities. For authored normal locations m_alpha, m_beta[j] and prior SDs sd_alpha, sd_beta[j], scale_.prior() implements:

intercept location in Stan = (m_alpha - cy + sum(cx * m_beta)) / sy
intercept prior SD in Stan = sd_alpha / sy
slope location in Stan[j]  = m_beta[j] * sx[j] / sy
slope prior SD in Stan[j]  = sd_beta[j] * sx[j] / sy

These locations are the parameters of normal kernels; with finite bounds they are not generally the means of the truncated distributions. Stan assigns independent normal priors to the population coefficients, restricted by their bounds. For noise_sd, the normal location and SD, or the lognormal_ms mean and SD, are divided by sy.

The reported coefficients use the inverse map:

beta[j] = sy * beta_stan[j] / sx[j]
alpha   = sy * alpha_stan + cy - sum(cx * beta)

The current conversion does not generally preserve an independently authored raw-intercept prior jointly with the slopes. The inverse intercept depends on the slope draws, but prior scaling divides its SD only by sy and introduces no intercept-slope covariance. For example, normal(10, 2) for the intercept and normal(3, 4) for one slope, with cy=10, sy=2, cx=5, sx=1, give independent internal priors normal(7.5, 1) and normal(1.5, 2). Without bounds, the reported intercept then has variance 404 rather than the authored 4, and covariance -80 with the reported slope. This is an algebraic consequence of the implementation, not a fitted result.

See R/scale.R (scale_.prior() and unscale_coef_vector()), the preparation paths in R/blm.R and R/hierarchy.R, and the Stan population-prior blocks.

Boundary scaling

  • Zero slope boundaries (0) are preserved by positive multiplicative scaling.
  • Infinite boundaries (±Inf) are invariant under scaling.
  • Finite non-zero boundaries for slope terms are scaled using scale_boundary_for_parameter(), which applies the same sx / sy ratio used for slope priors.
  • Population-intercept boundaries in BLM and hierarchical models: with scale = TRUE, finite non-zero bounds are rejected. Zero and infinite bounds pass through unchanged. A zero lower bound constrains the internal centred intercept, not necessarily the reported raw-zero intercept. In the example above, alpha_stan=0.1 and beta_stan=2 satisfy that internal bound but back-transform to alpha=-9.8.
  • If a finite non-zero boundary is specified for a parameter without a matching scale factor in the design matrix, dsambayesplus aborts with a validation error.

Practical implication

State the model-response units, scaling flag and intercept reference explicitly. Unstandardised coefficient reporting does not imply that centring preserved the authored joint prior or an intercept bound at raw predictor zero. With scale = FALSE, BLM and hierarchical population priors and bounds are passed to the unstandardised fit without these conversions. Through-origin models have no population intercept and retain zero centres when scaling.

Hierarchical group-intercept SDs have the separate centred-reference convention described above. Do not apply the population-intercept interpretation to group heterogeneity or generalise these boundary rules to pooled dimension terms.

Interaction with model classes

Behaviour BLM Hierarchical Pooled
Default priors Data-dependent: normal(ybar, sy) intercept, normal(0, sy / sx) slopes, normal(0, sy) noise_sd (see Default prior generation above) Population: same as BLM; group SD: data-derived Non-pooled: BLM defaults; pooled dimension coefficients only: normal(0, 5) per dimension
Boundary defaults (-Inf, Inf) per term Same as BLM for population terms Per-dimension boundaries for pooled terms
Prior scaling Slope ratio sx / sy; population-intercept conversion and limitation above Same population conversion, computed on pooled model frame; group-SD rules above Shared prior helper; dimension-level terms have a separate parameterisation
Boundary scaling Slope ratio sx / sy; population-intercept exceptions above Same for population terms Per-dimension boundary handling; do not infer population-intercept support

Programmatic API

Inspect priors and boundaries

peek_prior(model)
peek_boundary(model)

Override priors

model <- model %>%
  set_prior(
    m_tv ~ normal(0.5, 0.2),
    price_index ~ normal(-0.2, 0.1)
  )

Override boundaries

model <- model %>%
  set_boundary(
    m_tv > 0,
    competitor_discount < 0
  )

Minimal-prior policy

The recommended operating profile for MMM is documented in Minimal-Prior Policy. The policy keeps priors weak by default and uses hard constraints only when there is structural business knowledge.

Cross-references