$npx -y skills add pymc-labs/python-analytics-skills --skill pymc-modelingLoad whenever the user is working on code that imports pymc, pytensor, or arviz, or asks about Bayesian modeling, MCMC, priors, posteriors, sampling, or model diagnostics. Covers PyMC 6+, PyTensor 3+, ArviZ 1.1+ (DataTree API), pymc-bart, pymc-extras, nutpie, and JAX/NumPyro back
| 1 | # PyMC Modeling |
| 2 | |
| 3 | Modern Bayesian modeling with PyMC 6+ on the ArviZ 1.1 / PyTensor 3 stack. Key defaults: nutpie sampler (2-5x faster; PyMC 6 selects it automatically when installed — no `nuts_sampler` argument needed), non-centered parameterization for hierarchical models, HSGP over exact GPs, coords/dims for readable DataTree output, and save-early workflow to prevent data loss from late crashes. |
| 4 | |
| 5 | `pm.sample(...)` returns an `xarray.DataTree` — the `idata` name is kept by convention, but it is a DataTree, not the old `InferenceData`. Access groups by bracket: `idata["posterior"]`, `idata["sample_stats"]`, etc. |
| 6 | |
| 7 | **Modeling strategy**: Build models iteratively — start simple, check prior |
| 8 | predictions, fit and diagnose, check posterior predictions, expand one piece at |
| 9 | a time. See [references/workflow.md](references/workflow.md) for the full workflow. |
| 10 | |
| 11 | ## Model Specification |
| 12 | |
| 13 | ### Basic Structure |
| 14 | |
| 15 | ```python |
| 16 | import pymc as pm |
| 17 | import arviz as az |
| 18 | |
| 19 | with pm.Model(coords=coords) as model: |
| 20 | # Data containers (for out-of-sample prediction) |
| 21 | x = pm.Data("x", x_obs, dims="obs") |
| 22 | |
| 23 | # Priors |
| 24 | beta = pm.Normal("beta", mu=0, sigma=1, dims="features") |
| 25 | sigma = pm.HalfNormal("sigma", sigma=1) |
| 26 | |
| 27 | # Likelihood |
| 28 | mu = pm.math.dot(x, beta) |
| 29 | y = pm.Normal("y", mu=mu, sigma=sigma, observed=y_obs, dims="obs") |
| 30 | |
| 31 | # Inference |
| 32 | idata = pm.sample(random_seed=42) # PyMC 6 uses nutpie automatically when installed |
| 33 | ``` |
| 34 | |
| 35 | ### Coords and Dims |
| 36 | |
| 37 | Use coords/dims for an interpretable DataTree when the model has meaningful structure: |
| 38 | |
| 39 | ```python |
| 40 | coords = { |
| 41 | "obs": np.arange(n_obs), |
| 42 | "features": ["intercept", "age", "income"], |
| 43 | "group": group_labels, |
| 44 | } |
| 45 | ``` |
| 46 | |
| 47 | Skip for simple models where overhead exceeds benefit. |
| 48 | |
| 49 | ### Parameterization |
| 50 | |
| 51 | Prefer non-centered parameterization for hierarchical models with weak data: |
| 52 | |
| 53 | ```python |
| 54 | # Non-centered (better for divergences) |
| 55 | offset = pm.Normal("offset", 0, 1, dims="group") |
| 56 | alpha = mu_alpha + sigma_alpha * offset |
| 57 | |
| 58 | # Centered (better with strong data) |
| 59 | alpha = pm.Normal("alpha", mu_alpha, sigma_alpha, dims="group") |
| 60 | ``` |
| 61 | |
| 62 | ## Inference |
| 63 | |
| 64 | ### Default Sampling (nutpie preferred) |
| 65 | |
| 66 | In PyMC 6, `pm.sample` uses nutpie automatically whenever it is installed and the |
| 67 | model can be compiled — do not pass `nuts_sampler="nutpie"` explicitly: |
| 68 | |
| 69 | ```python |
| 70 | with model: |
| 71 | idata = pm.sample( |
| 72 | draws=1000, tune=1000, chains=4, |
| 73 | random_seed=42, |
| 74 | ) |
| 75 | idata.to_netcdf("results.nc") # Save immediately after sampling |
| 76 | ``` |
| 77 | |
| 78 | **Important**: For LOO-CV, model comparison, or LOO-PIT checks, ensure the |
| 79 | `log_likelihood` group exists. In PyMC 6, do not pass a top-level |
| 80 | `compute_log_likelihood=` argument to `pm.sample`. Either request it during |
| 81 | conversion with `idata_kwargs={"log_likelihood": True}` or compute it explicitly |
| 82 | after sampling: |
| 83 | |
| 84 | ```python |
| 85 | idata = pm.sample(idata_kwargs={"log_likelihood": True}, random_seed=42) |
| 86 | # or, after an existing sample: |
| 87 | pm.compute_log_likelihood(idata, model=model) |
| 88 | ``` |
| 89 | |
| 90 | This applies to every sampler (nutpie, PyMC NUTS, NumPyro) — not just nutpie. |
| 91 | |
| 92 | ### When to Use PyMC's Default NUTS Instead |
| 93 | |
| 94 | nutpie cannot handle discrete parameters or certain transforms (e.g., `ordered` transform with `OrderedLogistic`/`OrderedProbit`). PyMC 6 falls back automatically; to force the PyMC sampler explicitly, pass `nuts_sampler="pymc"`: |
| 95 | |
| 96 | ```python |
| 97 | idata = pm.sample(draws=1000, tune=1000, chains=4, nuts_sampler="pymc", random_seed=42) |
| 98 | ``` |
| 99 | |
| 100 | Never change the model specification to work around sampler limitations. |
| 101 | |
| 102 | If nutpie is not installed, install it (`pip install nutpie`) or fall back to `nuts_sampler="numpyro"`. |
| 103 | |
| 104 | ### Alternative MCMC Backends |
| 105 | |
| 106 | See [references/inference.md](references/inference.md) for: |
| 107 | - **NumPyro/JAX**: GPU acceleration, vectorized chains |
| 108 | |
| 109 | ### Approximate Inference |
| 110 | |
| 111 | For fast (but inexact) posterior approximations: |
| 112 | - **ADVI/DADVI**: Variational inference with Gaussian approximation |
| 113 | - **Pathfinder**: Quasi-Newton optimization for initialization or screening |
| 114 | |
| 115 | ## Diagnostics and ArviZ Workflow |
| 116 | |
| 117 | **Minimum workflow checklist** — every model script should include: |
| 118 | 1. Prior predictive check (`pm.sample_prior_predictive`) |
| 119 | 2. Save results immediately after sampling (`idata.to_netcdf(...)`) |
| 120 | 3. Divergence count + r_hat + ESS check |
| 121 | 4. |