Skip to main content

pymc-modeling

Load 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 backends. Use for building probabilistic models, specifying priors, running MCMC, diagnosing convergence, or comparing models. Triggers include: Bayesian inference, posterior sampling, hierarchical/multilevel models, GLMs, time series, Gaussian processes, HSGP, BART, mixture models, prior/posterior predictive checks, MCMC diagnostics, LOO-CV, model comparison, causal inference with do/observe, and any PyTensor Op or graph work.

Source facts

Repository
pymc-labs/python-analytics-skills
Last source activity
June 22, 2026 at 14:35
Detected SKILL.md language
English
Stars
82
Forks
10

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
14 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
pymc-modeling
description
Load 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 backends. Use for building probabilistic models, specifying priors, running MCMC, diagnosing convergence, or comparing models. Triggers include: Bayesian inference, posterior sampling, hierarchical/multilevel models, GLMs, time series, Gaussian processes, HSGP, BART, mixture models, prior/posterior predictive checks, MCMC diagnostics, LOO-CV, model comparison, causal inference with do/observe, and any PyTensor Op or graph work.
# PyMC Modeling 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. `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. **Modeling strategy**: Build models iteratively — start simple, check prior predictions, fit and diagnose, check posterior predictions, expand one piece at a time. See [references/workflow.md](references/workflow.md) for the full workflow. ## Model Specification ### Basic Structure ```python import pymc as pm import arviz as az with pm.Model(coords=coords) as model: # Data containers (for out-of-sample prediction) x = pm.Data("x", x_obs, dims="obs") # Priors beta = pm.Normal("beta", mu=0, sigma=1, dims="features") sigma = pm.HalfNormal("sigma", sigma=1) # Likelihood mu = pm.math.dot(x, beta) y = pm.Normal("y", mu=mu, sigma=sigma, observed=y_obs, dims="obs") # Inference idata = pm.sample(random_seed=42) # PyMC 6 uses nutpie automatically when installed ``` ### Coords and Dims Use coords/dims for an interpretable DataTree when the model has meaningful structure: ```python coords = { "obs": np.arange(n_obs), "features": ["intercept", "age", "income"], "group": group_labels, } ``` Skip for simple models where overhead exceeds benefit. ### Parameterization Prefer non-centered parameterization for hierarchical models with weak data: ```python # Non-centered (better for divergences) offset = pm.Normal("offset", 0, 1, dims="group") alpha = mu_alpha + sigma_alpha * offset # Centered (better with strong data) alpha = pm.Normal("alpha", mu_alpha, sigma_alpha, dims="group") ``` ## Inference ### Default Sampling (nutpie preferred) In PyMC 6, `pm.sample` uses nutpie automatically whenever it is installed and the model can be compiled — do not pass `nuts_sampler="nutpie"` explicitly: ```python with model: idata = pm.sample( draws=1000, tune=1000, chains=4, random_seed=42, ) idata.to_netcdf("results.nc") # Save immediately after sampling ``` **Important**: For LOO-CV, model comparison, or LOO-PIT checks, ensure the `log_likelihood` group exists. In PyMC 6, do not pass a top-level `compute_log_likelihood=` argument to `pm.sample`. Either request it during conversion with `idata_kwargs={"log_likelihood": True}` or compute it explicitly after sampling: ```python idata = pm.sample(idata_kwargs={"log_likelihood": True}, random_seed=42) # or, after an existing sample: pm.compute_log_likelihood(idata, model=model) ``` This applies to every sampler (nutpie, PyMC NUTS, NumPyro) — not just nutpie. ### When to Use PyMC's Default NUTS Instead 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"`: ```python idata = pm.sample(draws=1000, tune=1000, chains=4, nuts_sampler="pymc", random_seed=42) ``` Never change the model specification to work around sampler limitations. If nutpie is not installed, install it (`pip install nutpie`) or fall back to `nuts_sampler="numpyro"`. ### Alternative MCMC Backends See [references/inference.md](references/inference.md) for: - **NumPyro/JAX**: GPU acceleration, vectorized chains ### Approximate Inference For fast (but inexact) posterior approximations: - **ADVI/DADVI**: Variational inference with Gaussian approximation - **Pathfinder**: Quasi-Newton optimization for initialization or screening ## Diagnostics and ArviZ Workflow **Minimum workflow checklist** — every model script should include: 1. Prior predictive check (`pm.sample_prior_predictive`) 2. Save results immediately after sampling (`idata.to_netcdf(...)`) 3. Divergence count + r_hat + ESS check 4. Posterior predictive check (`pm.sample_posterior_predictive`) Follow this systematic workflow after every sampling run: ### Phase 1: Immediate Checks (Required) ```python # 1. Check for divergences (must be 0 or near 0) # idata is an xarray.DataTree; path-access gets a DataArray n_div = idata["sample_stats"]["diverging"].sum().item() print(f"Divergences: {n_div}") # 2. Summary with convergence diagnostics # Default CI is 0.89 ETI (equal-tailed) — bounds labelled eti89_lb / eti89_ub summary = az.summary(idata, var_names=["~offset"]) # exclude auxiliary print(summary[["mean", "sd", "eti89_lb", "eti89_ub", "ess_bulk", "ess_tail", "r_hat"]]) # 3. Visual convergence check az.plot_trace_dist(idata, compact=True) az.plot_rank(idata, var_names=["beta", "sigma"]) ``` **Pass criteria** (all must pass before proceeding): - Zero divergences (or < 0.1% and randomly scattered) - `r_hat < 1.01` for all parameters - `ess_bulk > 400` and `ess_tail > 400` - Trace plots show good mixing (overlapping densities, fuzzy caterpillar) ### Phase 2: Deep Convergence (If Phase 1 marginal) ```python # ESS evolution (should grow linearly) az.plot_ess_evolution(idata) # Energy diagnostic (HMC health) az.plot_energy(idata) # Autocorrelation (should decay rapidly) az.plot_autocorr(idata, var_names=["beta"]) ``` ### Phase 3: Model Criticism (Required) ```python # Generate posterior predictive with model: idata.update(pm.sample_posterior_predictive(idata)) # Does the model capture the data? az.plot_ppc_dist(idata, kind="ecdf") # Calibration check az.plot_loo_pit(idata, var_names=["y"]) ``` **Critical rule**: Never interpret parameters until Phases 1-3 pass. ### Phase 4: Parameter Interpretation ```python # Posterior summaries az.plot_dist(idata, var_names=["beta"]) # Forest plots for hierarchical parameters az.plot_forest(idata, var_names=["alpha"], combined=True) # Parameter correlations (identify non-identifiability) az.plot_pair(idata, var_names=["alpha", "beta", "sigma"]) ``` See [references/arviz.md](references/arviz.md) for comprehensive ArviZ usage. See [references/diagnostics.md](references/diagnostics.md) for troubleshooting. ## Prior and Posterior Predictive Checks ### Prior Predictive (Before Fitting) Always check prior implications before fitting: ```python with model: prior_pred = pm.sample_prior_predictive(draws=500) az.plot_ppc_dist(prior_pred, group="prior_predictive", kind="ecdf") prior_y = prior_pred["prior_predictive"]["y"].values.flatten() print(f"Prior predictive range: [{prior_y.min():.1f}, {prior_y.max():.1f}]") ``` **Rule**: Run prior predictive checks before `pm.sample()` on any new model. If the range is implausible (negative counts, probabilities > 1), adjust priors before proceeding. ### Posterior Predictive (After Fitting) ```python with model: idata.update(pm.sample_posterior_predictive(idata)) az.plot_ppc_dist(idata, kind="ecdf") az.plot_loo_pit(idata, var_names=["y"]) ``` Observed data (dark line) should fall within posterior predictive distribution. See [references/arviz.md](references/arviz.md) for detailed interpretation. ## Model Debugging Before sampling, validate the model with `model.debug()` and `model.point_logps()`. Use `print(model)` for structure and `pm.model_to_graphviz(model)` for a DAG visualization. ### Common Issues | Symptom | Likely Cause | Fix | |---------|--------------|-----| | `ValueError: Shape mismatch` | Parameter vs observation dimensions | Use index vectors: `alpha[group_idx]` | | `Initial evaluation failed` | Data outside distribution support | Check bounds; use `init="adapt_diag"` | | `Mass matrix contains zeros` | Unscaled predictors or flat priors | Standardize features; use weakly informative priors | | High divergence count | Funnel geometry | Non-centered parameterization | | `NaN` in log-probability | Invalid parameter combinations | Check parameter constraints, add bounds | | `-inf` log-probability | Observations outside likelihood support | Verify data matches distribution domain | | Slow discrete sampling | NUTS incompatible with discrete | Marginalize discrete variables | See [references/troubleshooting.md](references/troubleshooting.md) for comprehensive problem-solution guide. For debugging divergences, use `az.plot_pair(idata, divergences=True)` to locate clusters. See [references/diagnostics.md](references/diagnostics.md) § Divergence Troubleshooting. For profiling slow models, see [references/troubleshooting.md](references/troubleshooting.md) § Performance Issues. ## Model Comparison ### LOO-CV (Preferred) ```python # Compute LOO with pointwise diagnostics loo = az.loo(idata, pointwise=True) print(f"ELPD: {loo.elpd:.1f} ± {loo.se:.1f}") # Check Pareto k values (must be < 0.7 for reliable LOO) print(f"Bad k (>0.7): {(loo.pareto_k > 0.7).sum().item()}") az.plot_khat(loo) ``` ### Comparing Models ```python # PyMC 6 requires an explicit log-likelihood compute before LOO pm.compute_log_likelihood(idata_a, model=model_a) pm.compute_log_likelihood(idata_b, model=model_b) # ArviZ 1.1 — only loo is supported (waic was removed) comparison = az.compare({ "model_a": idata_a, "model_b": idata_b, }) print(comparison[["rank", "elpd", "elpd_diff", "weight"]]) az.plot_compare(comparison) ``` **Decision rule**: If two models have similar stacking weights, they are effectively equivalent. See [references/arviz.md](references/arviz.md) for detailed model comparison workflow. For detailed LOO-CV workflows, model stacking, and calibration diagnostics, see the [model-evaluation skill](../model-evaluation/SKILL.md). ### Iterative Model Building Build complexity incrementally: fit the simplest plausible model first, diagnose it, check posterior predictions, then add ONE piece of complexity at a time. Compare each expansion via LOO. If stacking weights are similar, the models are effectively equivalent. See [references/workflow.md](references/workflow.md) for the full iterative workflow. ## Saving and Loading Results ### DataTree Persistence `pm.sample()` returns an `xarray.DataTree`. Persist with NetCDF; the `idata` name is convention. ```python # Save to NetCDF (recommended format) idata.to_netcdf("results/model_v1.nc") # Load idata = az.from_netcdf("results/model_v1.nc") ``` For compressed storage of large DataTree objects, see [references/workflow.md](references/workflow.md). **Critical**: Save IMMEDIATELY after sampling — late crashes destroy valid results: ```python with model: idata = pm.sample() # nutpie by default in PyMC 6; returns a DataTree idata.to_netcdf("results.nc") # Save before any post-processing! with model: idata.update(pm.sample_posterior_predictive(idata)) # .update() merges the new group in place idata.to_netcdf("results.nc") # Update with posterior predictive ``` **Note**: Use `.update({...})` or direct assignment (`idata["posterior_predictive"] = ppd_ds`) to add groups. ## Prior Selection See [references/priors.md](references/priors.md) for: - Weakly informative defaults by distribution type - Prior predictive checking workflow - Domain-specific recommendations For constrained priors, expert elicitation workflows, and PreliZ integration, see the [prior-elicitation skill](../prior-elicitation/SKILL.md). ## Common Patterns ### Hierarchical/Multilevel ```python with pm.Model(coords={"group": groups, "obs": obs_idx}) as hierarchical: # Hyperpriors mu_alpha = pm.Normal("mu_alpha", 0, 1) sigma_alpha = pm.HalfNormal("sigma_alpha", 1) # Group-level (non-centered) alpha_offset = pm.Normal("alpha_offset", 0, 1, dims="group") alpha = pm.Deterministic("alpha", mu_alpha + sigma_alpha * alpha_offset, dims="group") # Likelihood y = pm.Normal("y", alpha[group_idx], sigma, observed=y_obs, dims="obs") ``` ### GLMs ```python # Logistic regression with pm.Model() as logistic: alpha = pm.Normal("alpha", 0, 2.5) beta = pm.Normal("beta", 0, 2.5, dims="features")
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub