Skill Tracer
Skills › Scientific Agent Skills › pymc › model_patterns.md

model_patterns.md

136 lines · skills/pymc/references/model_patterns.md at 5f2bc03

Open on GitHub
1# Common Model Patterns and Comparison
2
3Reusable model structures (hierarchical, regression variants, mixtures, time series) and
4then predictive model comparison. Targets PyMC 6.3.2 / ArviZ 1.3.0.
5Fragments below are illustrative and require the named data/dimensions; native
6regression and hierarchical template execution is tested separately.
7

Common Model Patterns and Comparison

Reusable model structures (hierarchical, regression variants, mixtures, time series) and then predictive model comparison. Targets PyMC 6.3.2 / ArviZ 1.3.0. Fragments below are illustrative and require the named data/dimensions; native regression and hierarchical template execution is tested separately.

Raw lines 1 to 7
8## Common Model Patterns
9

Common Model Patterns

Raw lines 8 to 9
10### Linear Regression
12For continuous outcomes with linear relationships:
14```python
15with pm.Model() as linear_model:
16 alpha = pm.Normal('alpha', mu=0, sigma=10)
17 beta = pm.Normal('beta', mu=0, sigma=10, shape=n_predictors)
18 sigma = pm.HalfNormal('sigma', sigma=1)
20 mu = alpha + pm.math.dot(X, beta)
21 y = pm.Normal('y', mu=mu, sigma=sigma, observed=y_obs)
22```
24**Use template:** `assets/linear_regression_template.py`

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

Raw lines 10 to 25
26### Logistic Regression
28For binary outcomes:
30```python
31with pm.Model() as logistic_model:
32 alpha = pm.Normal('alpha', mu=0, sigma=10)
33 beta = pm.Normal('beta', mu=0, sigma=10, shape=n_predictors)
35 logit_p = alpha + pm.math.dot(X, beta)
36 y = pm.Bernoulli('y', logit_p=logit_p, observed=y_obs)
37```

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)
Raw lines 26 to 38
39### Hierarchical Models
41For grouped data (non-centering is often helpful for weakly informed group effects):
43```python
44with pm.Model(coords={'groups': group_names}) as hierarchical_model:
45 # Hyperpriors
46 mu_alpha = pm.Normal('mu_alpha', mu=0, sigma=10)
47 sigma_alpha = pm.HalfNormal('sigma_alpha', sigma=1)
49 # Group-level (non-centered)
50 alpha_offset = pm.Normal('alpha_offset', mu=0, sigma=1, dims='groups')
51 alpha = pm.Deterministic('alpha', mu_alpha + sigma_alpha * alpha_offset, dims='groups')
53 # Observation-level
54 mu = alpha[group_idx]
55 sigma = pm.HalfNormal('sigma', sigma=1)
56 y = pm.Normal('y', mu=mu, sigma=sigma, observed=y_obs)
57```
59**Use template:** `assets/hierarchical_model_template.py`
61**Check geometry:** Compare centered/non-centered parameterizations when necessary;
62non-centering is not a guarantee against divergences or weak identification.

Hierarchical Models

For grouped data (non-centering is often helpful for weakly informed group effects):

with pm.Model(coords={'groups': group_names}) as hierarchical_model:
    # Hyperpriors
    mu_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-level
    mu = 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

Check geometry: Compare centered/non-centered parameterizations when necessary; non-centering is not a guarantee against divergences or weak identification.

Raw lines 39 to 63
64### Poisson Regression
66For count data:
68```python
69with pm.Model() as poisson_model:
70 alpha = pm.Normal('alpha', mu=0, sigma=10)
71 beta = pm.Normal('beta', mu=0, sigma=10, shape=n_predictors)
73 log_lambda = alpha + pm.math.dot(X, beta)
74 y = pm.Poisson('y', mu=pm.math.exp(log_lambda), observed=y_obs)
75```
77For overdispersed counts, use `NegativeBinomial` instead.

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.

Raw lines 64 to 78
79### Time Series
81For autoregressive processes:
83```python
84with pm.Model() as ar_model:
85 sigma = pm.HalfNormal('sigma', sigma=1)
86 rho = pm.Normal('rho', mu=0, sigma=0.5, shape=ar_order)
87 init_dist = pm.Normal.dist(mu=0, sigma=sigma)
89 y = pm.AR('y', rho=rho, sigma=sigma, init_dist=init_dist, observed=y_obs)
90```

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)
Raw lines 79 to 91
92## Model Comparison

Model Comparison

Raw lines 92 to 93
94### Comparing Models
96Use PSIS-LOO for this helper; ArviZ 1 `compare` does not accept a WAIC selector:
98```python
99from scripts.model_comparison import compare_models, check_loo_reliability
101# Fit models with log_likelihood
102models = {
103 'Model1': idata1,
104 'Model2': idata2,
105 'Model3': idata3
108# Compare using LOO
109comparison = compare_models(models, ic='loo')
111# Check reliability
112check_loo_reliability(models)
113```
115**Interpretation:** ArviZ 1 reports `elpd_diff` relative to the best model;
1160 is best, and negative values are lower predictive scores. Interpret paired
117score differences with their uncertainty and the scientific predictive target.
118Fixed difference thresholds do not establish evidence for a causal mechanism.
120**Check Pareto-k values:** Use the threshold reported by the installed ArviZ version and inspect influential observations when the importance-sampling diagnostic fails. Refit problematic leave-one-out cases or use appropriately structured K-fold validation; switching to WAIC is not a repair for unreliable PSIS-LOO. Record the predictive unit (observation, patient/group, or future time block), because holding out rows can answer a different question from predicting new groups or future data.

Comparing Models

Use PSIS-LOO for this helper; ArviZ 1 compare does not accept a WAIC selector:

from scripts.model_comparison import compare_models, check_loo_reliability

# Fit models with log_likelihood
models = {
    'Model1': idata1,
    'Model2': idata2,
    'Model3': idata3
}

# Compare using LOO
comparison = compare_models(models, ic='loo')

# Check reliability
check_loo_reliability(models)

Interpretation: ArviZ 1 reports elpd_diff relative to the best model; 0 is best, and negative values are lower predictive scores. Interpret paired score differences with their uncertainty and the scientific predictive target. Fixed difference thresholds do not establish evidence for a causal mechanism.

Check Pareto-k values: Use the threshold reported by the installed ArviZ version and inspect influential observations when the importance-sampling diagnostic fails. Refit problematic leave-one-out cases or use appropriately structured K-fold validation; switching to WAIC is not a repair for unreliable PSIS-LOO. Record the predictive unit (observation, patient/group, or future time block), because holding out rows can answer a different question from predicting new groups or future data.

Raw lines 94 to 121
122### Model Averaging
124To combine predictive distributions, sample a mixture (weights are not model probabilities):
126```python
127from scripts.model_comparison import model_averaging
129averaged_pred, weights = model_averaging(models, var_name='y_obs', random_seed=42)
130```
132For AR models, unconstrained independent Normal coefficients do not enforce
133stationarity. Define initial conditions and the desired stationary/nonstationary
134process explicitly; future-block validation generally differs from leaving out
135individual time points. Wide Normal priors on log/logit coefficients can imply
136extreme rates/probabilities: inspect their prior predictive implications.

Model Averaging

To combine predictive distributions, sample a mixture (weights are not model probabilities):

from scripts.model_comparison import model_averaging

averaged_pred, weights = model_averaging(models, var_name='y_obs', random_seed=42)

For AR models, unconstrained independent Normal coefficients do not enforce stationarity. Define initial conditions and the desired stationary/nonstationary process explicitly; future-block validation generally differs from leaving out individual time points. Wide Normal priors on log/logit coefficients can imply extreme rates/probabilities: inspect their prior predictive implications.

Raw lines 122 to 136