Skip to content

Migrate to the Bambi rewrite - #1387

Draft
digicosmos86 wants to merge 107 commits into
mainfrom
feat/bambi-rewrite-migation
Draft

digicosmos86 wants to merge 107 commits into
mainfrom
feat/bambi-rewrite-migation

Conversation

@digicosmos86

@digicosmos86 digicosmos86 commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

This PR brings HSSM up to the Bambi rewrite (bambinos/bambi#1002). It is large because Bambi changed a lot underneath us, so the notes below explain the most important changes and why they were made.

Tracking issue: #1305.

Summary

Table of contents

Bambi 0.20 separates its frontend, which describes the model, from its backend, which builds and evaluates the PyMC graph. Prediction, log-likelihood computation, and parameter transforms now run on the PyMC/PyTensor graph instead of being rebuilt with NumPy/xarray. Bambi also renamed its model vocabulary (components → parameters) and reorganized its modules.

HSSM relied on several internals that were removed or moved: Model._compute_likelihood_params, Family._make_dist_kwargs_and_coords, response_evaluate_new_data, sample_new_groups, and the split linkinv/linkinv_backend links, among others. This PR replaces each of them with the graph-based paths Bambi now provides. Where Bambi's new behaviour would have changed what HSSM users see, the PR keeps HSSM's public results stable. Where Bambi's new behaviour is more correct, the PR adopts it and documents the change.

The work began by marking every test failure under Bambi dev as xfail and grouping them by root cause (design/bambi-dev-upgrade-test-failures.md, design/bambi-migration-fix-plan.md). Each group was then fixed under its own sub-issue and its xfail marks removed. No migration xfail marks remain.

Breaking changes

Custom links take a single inverse_link

Bambi no longer keeps one inverse link for NumPy computations and another for the PyTensor graph (#1311). hssm.Link follows suit:

# before
hssm.Link("custom_log", link=np.log, linkinv=np.exp, linkinv_backend=pt.exp)
# after
hssm.Link("custom_log", link=np.log, inverse_link=np.exp)

inverse_link must work on PyTensor tensors. NumPy ufuncs such as np.exp already do, because they dispatch to symbolic operations. Reading link.linkinv or link.linkinv_backend now raises AttributeError. The forward link is optional. The built-in bounded gen_logit exposes the same transformation through the new attribute, so its callers are unaffected.

Dimension names follow Bambi 0.20

  • Group-specific terms use {factor}_dim instead of {factor}__factor_dim.
  • A categorical predictor in a formula with an intercept uses {var}_dim_reduced.
  • A nested factor such as (1|g:h) now gets two dims, g_dim and h_dim, instead of one.
  • The 2-D response dim is rt,response_dim in every group ([Migration] Regenerate the recorded trace fixtures that predate bambi 0.20 (*_Intercept_centered) #1336). Bambi names it rt_dim, and observed_data previously used rt,response_extra_dim_0. SSMFamily declares RESPONSE_NDIM = 2 so Bambi gives the response its correct dims.

Code that selects coordinates by dim name must be updated.

Traces must match the model they predict from

sample_posterior_predictive now raises a ValueError when the posterior is missing a free random variable of the model (#1336). Before, an out-of-sample prediction silently drew the missing variable from its prior. The common case is a trace saved before Bambi 0.20, which has no v_Intercept_centered. Missing *_offset variables are allowed when their base term is present. Old traces need to be resampled.

Changes to existing functionality

Centered intercepts and initial values

When a parameter has an intercept, at least one common predictor, and center_predictors=True (the default), Bambi now samples {param}_Intercept_centered and records the original-scale {param}_Intercept as a deterministic (#1375). az.summary and plot_trace therefore show two rows for such an intercept. Both are kept, because Bambi's predict and compute_log_likelihood need the centered one. The intercept prior means the same thing as before. To report only the usual intercept, use:

az.summary(idata, var_names=["~_centered"], filter_vars="like")

Initial values follow the free variable:

The new explanation page Predictor centering and the intercept covers this in detail.

Likelihood parameters and out-of-sample log-likelihood

HSSM now evaluates the trial-wise likelihood parameters with Model.predict(kind="response_params") instead of the removed Bambi internals (#1313). In sample, they are still added to posterior, and keep_likelihood_params / add_likelihood_parameters_to_datatree behave as before. Out of sample, Bambi writes them to a predictions group. log_likelihood(data=...) uses that group for its computation and then removes it, so the only change to the trace is the new log_likelihood group. It no longer overwrites posterior with out-of-sample parameters.

Rebuilding the response term on new data now goes through formulae.design_matrices, which replaces Bambi's removed response_evaluate_new_data. For that reason formulae>=0.7.0 is now a direct dependency.

Out-of-sample posterior predictive

Bambi now stores out-of-sample predictions in predictions and predictions_constant_data instead of posterior_predictive (#1315). HSSM moves the response draws back into posterior_predictive, so in-sample and out-of-sample results keep one layout. This also keeps the plotting helpers working, since they always predict on the data frame they are given. Stale prediction groups are cleared before a new response prediction. kind="response_params" still returns Bambi's result unchanged.

Predictions for new or unidentified participants

Bambi retired sample_new_groups (HSSM always passed False) and now decides how to handle each observation from its grouping value (#1316):

  • A missing value (None, np.nan, pd.NA) means the participant's identity is unknown. The observation borrows the coefficients of a randomly chosen fitted group, which matches the old behaviour.
  • A non-missing value not seen during fitting is a new group. Its coefficients are drawn from the population-level model using the fitted hyperparameters, and all of its observations share them. Previously an unseen level was handled like a missing one.

This affects sample_posterior_predictive(data=...) and log_likelihood(data=...). Because those coefficients are random, log_likelihood now takes a random_seed to make the result reproducible (#1376). The new how-to guide Predict for new or unidentified participants explains the semantics and shows how to average draws into a trial-level or participant-level predictive score.

Trace contents

Errors for unknown formula variables

A formula that names a column missing from data now raises a ValueError that names the parameter and the formula (#1317). Before, users saw a bare KeyError/IndexError from formulae/pandas, and its type had changed across releases.

Fixes

Dependency, packaging, and CI changes

  • bambi tracks the dev branch through [tool.uv.sources] until 0.20 is released. The floor must be pinned to the published bambi>=0.20 before this ships (see the release plan below).
  • formulae>=0.7.0 is now a direct dependency (see above).
  • The pytest filterwarnings setting turns Bambi's "X is deprecated. Use Y instead." FutureWarnings into errors, so any deprecated accessor still in use fails CI ([Migration] Migrate deprecated bambi accessors and refresh test doubles #1314). All of HSSM's uses have been migrated: components → parameters, distributional_components → conditional_parameters, response_component.term → response_term.
  • pyrefly's floor and pre-commit hook are bumped to 1.3.0, and the new errors it reported are fixed.
  • The version is bumped to 0.6.0.

Significant internal changes

  • base.py no longer rebuilds likelihood parameters itself. It calls Model.predict and reorganizes the output groups (predictions → posterior_predictive, removing temporary groups).
  • utils.log_likelihood rebuilds the response with formulae.design_matrices and evaluates the parameters returned by _compute_likelihood_params.
  • _vi_compat.py holds the shared-variable freeze for JAX VI.
  • link.py keeps a single inverse_link path.
  • _clean_posterior_group drops every parameter Bambi built as a pm.Deterministic except *_Intercept_centered.
  • _remap_centered_intercept_initvals handles user initvals passed to sample().

Tests

  • The trace fixtures (cavanagh_idata.nc, idata_cavanagh_cartoon.nc, test_idata_race.nc) are regenerated for Bambi 0.20 by the new tests/trace_fixtures.py. The stale idata_race_cartoon.nc is removed.
  • New tests/test_new_group_predictions.py covers the unknown-identity and new-group prediction paths, including random_seed.
  • New tests cover centered-intercept initvals, the remap of sample(initvals=...), constant parameters dropped from prior/posterior, the _mean cleanup, the trace guard in sample_posterior_predictive, JAX VI on missing-data models, and the ValueError for unknown formula variables.
  • A test asserts that HSSM's out-of-sample path raises no FutureWarning.
  • Every migration xfail mark has been removed.

Documentation

Merge from main

main is merged into this branch. The conflicts were resolved as follows:

Compatibility and release plan

  • Do not release from this branch until Bambi 0.20 is on PyPI. It currently installs Bambi from the dev branch via [tool.uv.sources], which pip install hssm ignores. Before 0.6.0 is tagged, remove the git source and pin bambi>=0.20.
  • Users should expect to update code that relies on custom Link objects (linkinv → inverse_link), selects coordinates by dim name, or reads *_Intercept as a free variable. Traces saved before 0.6.0 must be resampled before they can be used for out-of-sample prediction.
  • Rolling back means reverting this merge and staying on Bambi 0.19. No user data or on-disk formats are migrated, but traces fit with 0.6.0 carry the new dim names and *_Intercept_centered variables, which 0.5.x will not interpret identically.

Summary by CodeRabbit

  • New Features
    • Updated HSSM for Bambi 0.20, including improved prediction and likelihood handling for fitted, missing, and unseen groups.
    • Posterior predictive sampling now defaults to response predictions and supports reproducible predictions for new groups.
    • Added guidance on predictions for new participants and predictor centering.
  • Bug Fixes
    • Improved error messages for missing formula columns and validation of posterior traces.
    • Improved compatibility of JAX variational inference with models whose data or observation dimensions change.
  • Documentation
    • Updated custom-link examples to use the current inverse_link interface and added 0.6.0 migration notes.

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants