Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: pymc description: Bayesian modeling with PyMC. Build hierarchical models, MCMC (NUTS), variational inference, LOO/WAIC comparison, posterior checks, for probabilistic programming and inference. license: Apache License, Version 2.0 tags: [bayesian-inference, probabilistic-programming, mcmc-sampling, hierarchical-models, pymc] metadata: skill-author: K-Dense Inc.
PyMC Bayesian Modeling
Overview
PyMC is a Python library for Bayesian modeling and probabilistic programming. Build, fit, validate, and compare Bayesian models using PyMC's modern API (version 5.x+), including hierarchical models, MCMC sampling (NUTS), variational inference, and model comparison (LOO, WAIC).
When to Use This Skill
This skill should be used when:
- Building Bayesian models (linear/logistic regression, hierarchical models, time series, etc.)
- Performing MCMC sampling or variational inference
- Conducting prior/posterior predictive checks
- Diagnosing sampling issues (divergences, convergence, ESS)
- Comparing multiple models using information criteria (LOO, WAIC)
- Implementing uncertainty quantification through Bayesian methods
- Working with hierarchical/multilevel data structures
- Handling missing data or measurement error in a principled way
Standard Bayesian Workflow
Follow this workflow for building and validating Bayesian models:
1. Data Preparation
import pymc as pmimport arviz as azimport numpy as np# Load and prepare dataX = ... # Predictorsy = ... # Outcomes# Standardize predictors for better samplingX_mean = X.mean(axis=0)X_std = X.std(axis=0)X_scaled = (X - X_mean) / X_std
Key practices:
- Standardize continuous predictors (improves sampling efficiency)
- Center outcomes when possible
- Handle missing data explicitly (treat as parameters)
- Use named dimensions with
coordsfor clarity
2. Model Building
coords = {'predictors': ['var1', 'var2', 'var3'],'obs_id': np.arange(len(y))}with pm.Model(coords=coords) as model:# Priorsalpha = pm.Normal('alpha', mu=0, sigma=1)beta = pm.Normal('beta', mu=0, sigma=1, dims='predictors')sigma = pm.HalfNormal('sigma', sigma=1)# Linear predictormu = alpha + pm.math.dot(X_scaled, beta)# Likelihoody_obs = pm.Normal('y_obs', mu=mu, sigma=sigma, observed=y, dims='obs_id')
Key practices:
- Use weakly informative priors (not flat priors)
- Use
HalfNormalorExponentialfor scale parameters - Use named dimensions (
dims) instead ofshapewhen possible - Use
pm.Data()for values that will be updated for predictions
3. Prior Predictive Check
Always validate priors before fitting:
with model:prior_pred = pm.sample_prior_predictive(samples=1000, random_seed=42)# Visualizeaz.plot_ppc(prior_pred, group='prior')
Check:
- Do prior predictions span reasonable values?
- Are extreme values plausible given domain knowledge?
- If priors generate implausible data, adjust and re-check
4. Fit Model
with model:# Optional: Quick exploration with ADVI# approx = pm.fit(n=20000)# Full MCMC inferenceidata = pm.sample(draws=2000,tune=1000,chains=4,target_accept=0.9,random_seed=42,idata_kwargs={'log_likelihood': True} # For model comparison)
Key parameters:
draws=2000: Number of samples per chaintune=1000: Warmup samples (discarded)chains=4: Run 4 chains for convergence checkingtarget_accept=0.9: Higher for difficult posteriors (0.95-0.99)- Include
log_likelihood=Truefor model comparison
5. Check Diagnostics
Use the diagnostic script:
from scripts.model_diagnostics import check_diagnosticsresults = check_diagnostics(idata, var_names=['alpha', 'beta', 'sigma'])
Check:
- R-hat < 1.01: Chains have converged
- ESS > 400: Sufficient effective samples
- No divergences: NUTS sampled successfully
- Trace plots: Chains should mix well (fuzzy caterpillar)
If issues arise:
- Divergences → Increase
target_accept=0.95, use non-centered parameterization - Low ESS → Sample more draws, reparameterize to reduce correlation
- High R-hat → Run longer, check for multimodality
6. Posterior Predictive Check
Validate model fit:
with model:pm.sample_posterior_predictive(idata, extend_inferencedata=True, random_seed=42)# Visualizeaz.plot_ppc(idata)
Check:
- Do posterior predictions capture observed data patterns?
- Are systematic deviations evident (model misspecification)?
- Consider alternative models if fit is poor
7. Analyze Results
# Summary statisticsprint(az.summary(idata, var_names=['alpha', 'beta', 'sigma']))# Posterior distributionsaz.plot_posterior(idata, var_names=['alpha', 'beta', 'sigma'])# Coefficient estimatesaz.plot_forest(idata, var_names=['beta'], combined=True)
8. Make Predictions
X_new = ... # New predictor valuesX_new_scaled = (X_new - X_mean) / X_stdwith model:pm.set_data({'X_scaled': X_new_scaled})post_pred = pm.sample_posterior_predictive(idata.posterior,var_names=['y_obs'],random_seed=42)# Extract prediction intervalsy_pred_mean = post_pred.posterior_predictive['y_obs'].mean(dim=['chain', 'draw'])y_pred_hdi = az.hdi(post_pred.posterior_predictive, var_names=['y_obs'])
Common Model Patterns
Linear Regression
For continuous outcomes with linear relationships:
with pm.Model() as linear_model:alpha = pm.Normal('alpha', mu=0, sigma=10)beta = pm.Normal('beta', mu=0, sigma=10, shape=n_predictors)sigma = pm.HalfNormal('sigma', sigma=1)mu = alpha + pm.math.dot(X, beta)y = pm.Normal('y', mu=mu, sigma=sigma, observed=y_obs)
Use template: assets/linear_regression_template.py
Logistic Regression
For binary outcomes:
with pm.Model() as logistic_model:alpha = pm.Normal('alpha', mu=0, sigma=10)beta = pm.Normal('beta', mu=0, sigma=10, shape=n_predictors)logit_p = alpha + pm.math.dot(X, beta)y = pm.Bernoulli('y', logit_p=logit_p, observed=y_obs)
Hierarchical Models
For grouped data (use non-centered parameterization):
with pm.Model(coords={'groups': group_names}) as hierarchical_model:# Hyperpriorsmu_alpha = pm.Normal('mu_alpha', mu=0, sigma=10)sigma_alpha = pm.HalfNormal('sigma_alpha', sigma=1)# Group-level (non-centered)alpha_offset = pm.Normal('alpha_offset', mu=0, sigma=1, dims='groups')alpha = pm.Deterministic('alpha', mu_alpha + sigma_alpha * alpha_offset, dims='groups')# Observation-levelmu = alpha[group_idx]sigma = pm.HalfNormal('sigma', sigma=1)y = pm.Normal('y', mu=mu, sigma=sigma, observed=y_obs)
Use template: assets/hierarchical_model_template.py
Critical: Always use non-centered parameterization for hierarchical models to avoid divergences.
Poisson Regression
For count data:
with pm.Model() as poisson_model:alpha = pm.Normal('alpha', mu=0, sigma=10)beta = pm.Normal('beta', mu=0, sigma=10, shape=n_predictors)log_lambda = alpha + pm.math.dot(X, beta)y = pm.Poisson('y', mu=pm.math.exp(log_lambda), observed=y_obs)
For overdispersed counts, use NegativeBinomial instead.
Time Series
For autoregressive processes:
with pm.Model() as ar_model:sigma = pm.HalfNormal('sigma', sigma=1)rho = pm.Normal('rho', mu=0, sigma=0.5, shape=ar_order)init_dist = pm.Normal.dist(mu=0, sigma=sigma)y = pm.AR('y', rho=rho, sigma=sigma, init_dist=init_dist, observed=y_obs)
Model Comparison
Comparing Models
Use LOO or WAIC for model comparison:
from scripts.model_comparison import compare_models, check_loo_reliability# Fit models with log_likelihoodmodels = {'Model1': idata1,'Model2': idata2,'Model3': idata3}# Compare using LOOcomparison = compare_models(models, ic='loo')# Check reliabilitycheck_loo_reliability(models)
Interpretation:
- Δloo < 2: Models are similar, choose simpler model
- 2 < Δloo < 4: Weak evidence for better model
- 4 < Δloo < 10: Moderate evidence
- Δloo > 10: Strong evidence for better model
Check Pareto-k values:
- k < 0.7: LOO reliable
- k > 0.7: Consider WAIC or k-fold CV
Model Averaging
When models are similar, average predictions:
from scripts.model_comparison import model_averagingaveraged_pred, weights = model_averaging(models, var_name='y_obs')
Distribution Selection Guide
For Priors
Scale parameters (σ, τ):
pm.HalfNormal('sigma', sigma=1)- Default choicepm.Exponential('sigma', lam=1)- Alternativepm.Gamma('sigma', alpha=2, beta=1)- More informative
Unbounded parameters:
pm.Normal('theta', mu=0, sigma=1)- For standardized datapm.StudentT('theta', nu=3, mu=0, sigma=1)- Robust to outliers
Positive parameters:
pm.LogNormal('theta', mu=0, sigma=1)pm.Gamma('theta', alpha=2, beta=1)
Probabilities:
pm.Beta('p', alpha=2, beta=2)- Weakly informativepm.Uniform('p', lower=0, upper=1)- Non-informative (use sparingly)
Correlation matrices:
pm.LKJCorr('corr', n=n_vars, eta=2)- eta=1 uniform, eta>1 prefers identity
For Likelihoods
Continuous outcomes:
pm.Normal('y', mu=mu, sigma=sigma)- Default for continuous datapm.StudentT('y', nu=nu, mu=mu, sigma=sigma)- Robust to outliers
Count data:
pm.Poisson('y', mu=lambda)- Equidispersed countspm.NegativeBinomial('y', mu=mu, alpha=alpha)- Overdispersed countspm.ZeroInflatedPoisson('y', psi=psi, mu=mu)- Excess zeros
Binary outcomes:
pm.Bernoulli('y', p=p)orpm.Bernoulli('y', logit_p=logit_p)
Categorical outcomes:
pm.Categorical('y', p=probs)
See: references/distributions.md for comprehensive distribution reference
Sampling and Inference
MCMC with NUTS
Default and recommended for most models:
idata = pm.sample(draws=2000,tune=1000,chains=4,target_accept=0.9,random_seed=42)
Adjust when needed:
- Divergences →
target_accept=0.95or higher - Slow sampling → Use ADVI for initialization
- Discrete parameters → Use
pm.Metropolis()for discrete vars
Variational Inference
Fast approximation for exploration or initialization:
with model:approx = pm.fit(n=20000, method='advi')# Use for initializationstart = approx.sample(return_inferencedata=False)[0]idata = pm.sample(start=start)
Trade-offs:
- Much faster than MCMC
- Approximate (may underestimate uncertainty)
- Good for large models or quick exploration
See: references/sampling_inference.md for detailed sampling guide
Diagnostic Scripts
Comprehensive Diagnostics
from scripts.model_diagnostics import create_diagnostic_reportcreate_diagnostic_report(idata,var_names=['alpha', 'beta', 'sigma'],output_dir='diagnostics/')
Creates:
- Trace plots
- Rank plots (mixing check)
- Autocorrelation plots
- Energy plots
- ESS evolution
- Summary statistics CSV
Quick Diagnostic Check
from scripts.model_diagnostics import check_diagnosticsresults = check_diagnostics(idata)
Checks R-hat, ESS, divergences, and tree depth.
Common Issues and Solutions
Divergences
Symptom: idata.sample_stats.diverging.sum() > 0
Solutions:
- Increase
target_accept=0.95or0.99 - Use non-centered parameterization (hierarchical models)
- Add stronger priors to constrain parameters
- Check for model misspecification
Low Effective Sample Size
Symptom: ESS < 400
Solutions:
- Sample more draws:
draws=5000 - Reparameterize to reduce posterior correlation
- Use QR decomposition for regression with correlated predictors
High R-hat
Symptom: R-hat > 1.01
Solutions:
- Run longer chains:
tune=2000, draws=5000 - Check for multimodality
- Improve initialization with ADVI
Slow Sampling
Solutions:
- Use ADVI initialization
- Reduce model complexity
- Increase parallelization:
cores=8, chains=8 - Use variational inference if appropriate
Best Practices
Model Building
- Always standardize predictors for better sampling
- Use weakly informative priors (not flat)
- Use named dimensions (
dims) for clarity - Non-centered parameterization for hierarchical models
- Check prior predictive before fitting
Sampling
- Run multiple chains (at least 4) for convergence
- Use `target_accept=0.9` as baseline (higher if needed)
- Include `log_likelihood=True` for model comparison
- Set random seed for reproducibility
Validation
- Check diagnostics before interpretation (R-hat, ESS, divergences)
- Posterior predictive check for model validation
- Compare multiple models when appropriate
- Report uncertainty (HDI intervals, not just point estimates)
Workflow
- Start simple, add complexity gradually
- Prior predictive check → Fit → Diagnostics → Posterior predictive check
- Iterate on model specification based on checks
- Document assumptions and prior choices
Resources
This skill includes:
References (references/)
- `distributions.md`: Comprehensive catalog of PyMC distributions organized by category (continuous, discrete, multivariate, mixture, time series). Use when selecting priors or likelihoods.
- `sampling_inference.md`: Detailed guide to sampling algorithms (NUTS, Metropolis, SMC), variational inference (ADVI, SVGD), and handling sampling issues. Use when encountering convergence problems or choosing inference methods.
- `workflows.md`: Complete workflow examples and code patterns for common model types, data preparation, prior selection, and model validation. Use as a cookbook for standard Bayesian analyses.
Scripts (scripts/)
- `model_diagnostics.py`: Automated diagnostic checking and report generation. Functions:
check_diagnostics()for quick checks,create_diagnostic_report()for comprehensive analysis with plots.
- `model_comparison.py`: Model comparison utilities using LOO/WAIC. Functions:
compare_models(),check_loo_reliability(),model_averaging().
Templates (assets/)
- `linear_regression_template.py`: Complete template for Bayesian linear regression with full workflow (data prep, prior checks, fitting, diagnostics, predictions).
- `hierarchical_model_template.py`: Complete template for hierarchical/multilevel models with non-centered parameterization and group-level analysis.
Quick Reference
Model Building
with pm.Model(coords={'var': names}) as model:# Priorsparam = pm.Normal('param', mu=0, sigma=1, dims='var')# Likelihoody = pm.Normal('y', mu=..., sigma=..., observed=data)
Sampling
idata = pm.sample(draws=2000, tune=1000, chains=4, target_accept=0.9)
Diagnostics
from scripts.model_diagnostics import check_diagnosticscheck_diagnostics(idata)
Model Comparison
from scripts.model_comparison import compare_modelscompare_models({'m1': idata1, 'm2': idata2}, ic='loo')
Predictions
with model:pm.set_data({'X': X_new})pred = pm.sample_posterior_predictive(idata.posterior)
Additional Notes
- PyMC integrates with ArviZ for visualization and diagnostics
- Use
pm.model_to_graphviz(model)to visualize model structure - Save results with
idata.to_netcdf('results.nc') - Load with
az.from_netcdf('results.nc') - For very large models, consider minibatch ADVI or data subsampling