Linear & Regression Models
The Gaussian linear model as a starting point, extended in four directions. On the error: a robust Student-t model that downweights outliers via a scale mixture, and a mixture-of-normals regression whose flexible disturbance captures skew and fat tails a single Normal cannot. On the parameters: a linear model with inequality constraints, drawing from a truncated posterior where economic sign restrictions bind and rejection sampling fails. Across equations: multivariate regression (shared design, exact conjugate draws) and seemingly-unrelated regression (different regressors, cross-equation error correlation). And across units: hierarchical linear and hierarchical SUR models that pool coefficients toward a common mean, plus a mixture-heterogeneity variant that asks whether that population is one continuous distribution or discrete segments. Each is built from scratch; most are then cross-checked against a second engine — PyMC or NumPyro — and against R (bayesm and related packages).
Three words that recur. An elasticity is a percentage response to a percentage change — what a regression coefficient becomes when both sides are measured in logs. An own-price elasticity of −2 says that a 1% price rise costs about 2% of unit sales. Below −1 is called elastic (raising the price loses revenue on net); between −1 and 0, inelastic. A credible interval (CrI) is the Bayesian counterpart of the confidence interval, and it means what people usually, and wrongly, take a confidence interval to mean: a 95% CrI is a range the parameter lies in with 95% posterior probability. An interval straddling zero says the data do not settle even the sign. A parameter is identified when the data could in principle distinguish it from the alternatives. If a shop never changes its price, no quantity of sales records from that shop can reveal how its customers would react to a price change — the coefficient is unidentified, and that is a statement about the information in the data, not about the software. It is exactly the condition that pooling repairs.
What a Gibbs sampler is. Nearly every sampler built from scratch in this section is a Gibbs sampler, described below as 4-block or 6-block, so it is worth saying what a block is. The obstacle in a Bayesian model is that the joint posterior over all the unknowns at once is intractable. But very often each unknown on its own, with everything else held fixed, has a posterior you can write down and draw from exactly. Gibbs sampling lives off that asymmetry: hold the variances fixed and draw the coefficients from their conditional; hold the coefficients fixed and draw the variances from theirs; keep rotating. One pass through every block is a sweep, and the striking result is that the sequence of sweeps converges to the joint posterior even though no single step ever evaluates it. A block is simply a group of unknowns updated together, and fewer, larger blocks generally give a faster-mixing chain — which is why the page headings count them. The price is the one NUTS does not pay: every conditional must be available in closed form, which is what conjugacy delivers and what a single non-conjugate prior takes away.
What pooling buys. Anyone holding data on many similar units — 88 supermarkets, 83 stores, 30 rats — faces a choice between two bad options. Fit each unit separately and every estimate is built from that unit's own thin, noisy slice of data. Fit them all together as though identical and any real differences between them are erased. A hierarchical (multilevel) model does a third thing: it treats the unit-level coefficients as themselves drawn from a common population distribution, and estimates that distribution at the same time as the coefficients. The consequence is shrinkage, or partial pooling — each unit's estimate is pulled toward the population mean by an amount set by how little its own data has to say. A shop whose price barely moves gets an estimate close to the pooled one, because its data cannot argue otherwise; a shop with genuinely variable prices keeps its own answer. The cheese panel makes this concrete. Fitted store by store, the log-price elasticities scatter from −12.65 to +2.86 with a standard deviation of 1.77, and several come out positive — meaning those shops supposedly sell more cheese when they charge more, which is not a finding but an artefact of too little price variation per store. Partial pooling cuts that spread to 0.76, and it does so by removing noise rather than signal.
Where the data comes from. Five of the pages below run on retail scanner panels, and since the same three datasets recur it is worth saying once what they are. A scanner panel is supermarket checkout data, aggregated into a row per store per week per brand: how many units sold, the shelf price, and whether the item was being promoted. The three used here are the IRI cheese panel (88 retailers, 5,555 store-weeks), and two from Dominick’s Finer Foods in Chicago — canned tuna (7 brands, 338 weeks) and orange juice (11 brands across 83 stores, roughly 117 weeks each). Economists reach for this kind of data for two reasons. Prices move for reasons that have little to do with the shopper standing in the aisle — promotion calendars, wholesale costs, competitive responses — which makes the variation closer to an experiment than most observational data gets. And the panel shape, many stores each watched over many weeks, is precisely the structure a hierarchical model is built for. The questions asked of them are ordinary commercial ones: if we raise this price, how much do we sell? Is the answer the same in every store? Do the brands on this shelf take sales from each other or move together?
The tooling, introduced once. This is where the probabilistic programming languages first appear, and they recur through every section that follows, so it is worth saying what they are. PyMC and NumPyro are languages for writing down a model rather than an algorithm: you declare the priors and the likelihood, and the library works out how to sample from the posterior. That is the whole appeal — the from-scratch samplers here are hand-derived for one specific model and break the moment it changes, while a declared model can be edited freely. NumPyro is the same idea built on JAX, which compiles the model and runs it on the GPU, so it is generally the faster of the two on large problems.
What NUTS does. The sampler both libraries reach for by default is the No-U-Turn Sampler, and the reason it displaced the Gibbs and Metropolis samplers written from scratch in these notebooks is worth understanding. Metropolis proposes a random step and accepts or rejects it — in high dimensions almost every guess is bad, so it crawls. Gibbs updates one parameter at a time from its exact conditional, which is efficient but requires those conditionals to be available in closed form, which is exactly what conjugacy buys and what breaks the moment a model steps outside it. Hamiltonian Monte Carlo instead uses the gradient of the posterior to travel along its contours, taking long, informed steps rather than blind ones. It has one awkward tuning knob — how far to travel before stopping — and NUTS removes it by doubling the trajectory until it starts to double back on itself, hence the name. The practical effect is that NUTS samples models with hundreds of correlated parameters that Metropolis could not finish in a lifetime, at the price of needing a differentiable model, which is why discrete parameters have to be integrated out or handled another way.
Reading the diagnostics. Three numbers appear beside almost every posterior in the collection. R-hat compares the variance between chains against the variance within them: chains started in different places should end up describing the same distribution, so R-hat near 1.00 is the pass and anything above about 1.01 means they have not converged on a common answer. ESS, the effective sample size, is how many independent draws the correlated chain is worth — 4,000 samples from a sticky chain can carry the information of 200, and it is the ESS, not the raw count, that sets the precision of a posterior mean. Divergences are unique to Hamiltonian methods: they flag places where the trajectory simulation broke down, usually in a sharply curved region of the posterior, and even a handful means the sampler may be systematically failing to visit part of the distribution — a bias, not just noise. Zero divergences, R-hat at 1.00 and ESS in the hundreds or thousands is the standard applied throughout.
What a Gibbs sampler is doing
Six of the eight examples below are built on a Gibbs sampler, and the picture is the same in every one. The joint posterior — the ellipses — cannot be sampled directly. But holding one unknown fixed leaves the other with a distribution you can draw from, shown here as the pale slice through the current point. So the sampler alternates: move sideways to a draw of the first, move vertically to a draw of the second, and repeat. Each pair of moves is one sweep, which is what the 4-block and 6-block labels on the pages below are counting.
Nothing in that procedure ever evaluates the joint posterior, yet the staircase converges to it — the path settles inside the contours and fills them in the right proportions. The drawing is a real run rather than an illustration: 140 sweeps of a genuine two-block sampler, which recovers the target's correlation of 0.88 to within 0.003 and its standard deviations to within 2%. It also shows what the method costs. Because the two unknowns are strongly correlated each conditional is narrow, so every step is short and consecutive draws look alike — a thousand sweeps are worth far fewer than a thousand independent draws. That gap is what effective sample size measures, and closing it is what the Hamiltonian methods described below exist to do. The chain must also walk in from wherever it was started, which here takes barely two sweeps but in general is why the opening draws are discarded as burn-in.
Bayesian Linear Model with Inequality Constraints
A Python implementation of Geweke's (1995) Bayesian linear regression with
linear inequality constraints a ≤ Dβ ≤ w. Two key
algorithms: the GHK probability simulator — which estimates the
posterior probability that all constraints hold — and a
component-wise constrained Gibbs sampler that draws from the
truncated posterior regardless of how small that probability is. Applied to the
UCI Automobile Imports dataset (195 cars): economic sign restrictions on engine
size, horsepower, and fuel economy are imposed; bore and stroke OLS estimates
violate the constraints due to multicollinearity, making
p2|1 ≈ 4 × 10−4 and rejection
sampling completely impractical. Includes GHK vs. analytical validation, a
hard-constraint stress test, and a 95% credible-interval coverage simulation.
Cross-checked against HMC (NumPyro NUTS) — which agrees on sign constraints but
struggles on a tight polytope where the truncated-Gibbs sails through — and R
(TruncatedNormal, tmvtnorm, bain).
Bayesian Student-t Linear Model
A Python implementation of Geweke's (1993) Bayesian linear model with
i.i.d. Student-t errors, estimated via a four-block Gibbs sampler.
The scale-mixture-of-normals representation augments each observation with a
latent precision weight λi: outliers receive
low weights, limiting their leverage on β automatically.
The degrees-of-freedom parameter ν is drawn as a discrete categorical variable
on a grid from 2 to 40, avoiding numerical instability near the boundary.
Applied to synthetic contaminated data (recovers ν = 4) and the Nelson-Plosser
(1982) 14 annual U.S. macroeconomic series (1909–1970): low posterior ν for
Real GNP, Industrial Production, and Employment identifies Depression- and
WWII-era shocks as heavy-tailed outliers rather than evidence of a stochastic
trend. Cross-checked against PyMC (continuous ν via NUTS) and R
(hett::tlm Student-t ML, MASS::rlm robust regression, OLS baseline).
Bayesian Mixture-of-Normals Regression
A Python implementation of a linear regression with a flexible
mixture-of-normals error, estimated via a from-scratch 4-block Gibbs
sampler (component indicators → GLS β → Normal-Inverse-Gamma components → Dirichlet
weights). The continuous sibling of Geweke & Keane (1997): rather than the single-Normal
disturbance of ordinary regression, the error follows
εt ~ Σj pj N(μj, σj²),
which can approximate any density — capturing the skew and fat tails a single Normal cannot.
The mixture is recentred to mean 0 each sweep so β stays the usual mean function and the
mixture describes only the disturbance shape; models are compared by WAIC. Applied to PSID
log-earnings (Geweke 2005; OLS residual kurtosis ≈ 23): the mixture beats the Normal by
≈1,580 WAIC, resolving the error into an ≈86% majority component plus a wide ≈14%
low-earnings left tail. Cross-checked in R (flexmix BIC, bayesm's
rnmixGibbs) and complemented by a PyMC notebook that scores the Normal-vs-mixture
question by Bayes factor (marginal likelihood via SMC), confirming the WAIC
verdict while flagging its prior sensitivity. Distinct from the coefficient-mixture
examples, which flex heterogeneity across units rather than the error in one equation.
Bayesian Multivariate Regression
Conjugate Normal–Inverse-Wishart posterior for the multivariate regression model
(shared design matrix across equations). Because the posterior is exact,
draws are IID — no MCMC, no burn-in. Validated on simulated data, then
applied to a tuna demand system (7 brands): B is an
elasticity matrix and Σ the cross-brand error correlation. Three engines
agree — the from-scratch mvr_conjugate.py, R's bayesm rmultireg, and a PyMC/NUTS fit with an LKJ prior on Σ — recovering the same
elasticities and cross-equation correlation.
Bayesian Seemingly Unrelated Regression
A Python implementation of the Bayesian SUR Gibbs sampler, replicating R's
bayesm rsurGibbs. Unlike multivariate regression, each equation
can have different regressors — cross-equation correlation in the error covariance
Ω is what makes joint estimation more efficient than OLS.
Applied to Dominick's Finer Foods tuna scanner data (338 weeks, 7 brands):
own-price and cross-price elasticity estimation. Includes a hierarchical extension
(Chib & Greenberg 1995) that pools brand-level coefficients toward a common mean.
Bayesian Hierarchical Linear Regression
A Python implementation of the Bayesian hierarchical linear model Gibbs sampler,
replicating R's bayesm rhierLinearModel. Estimated on the
IRI cheese scanner panel (88 retailers, 5,555 store-weeks): pooled shrinkage
reduces the cross-store standard deviation of log-price elasticities by 57%
relative to OLS. Includes unit-covariate extension (large vs small markets)
and full R/Python cross-validation.
Bayesian Hierarchical Linear Model — Mixture Heterogeneity
An extension of the hierarchical linear model that replaces the single-normal coefficient
distribution with a mixture of normals
(βi | si=k ~ N(μk, Σk)),
replicating bayesm's rhierLinearMixture (Rossi, Allenby & McCulloch
Ch. 5) via a from-scratch 6-block conjugate Gibbs sampler. Two applications probe whether the
mixture is a flexible heterogeneity density or a segmentation device. On the IRI
cheese panel (88 retailers) a 2-component mixture is only mildly favored by
WAIC, no store commits to a component, and three engines find different modes — the
coefficient distribution is continuous and weakly non-normal, not discrete segments.
On the canonical Rats growth data (Gelfand et al. 1990; 30 rats × 5 ages) the
K=1 BiRats fit (intercept ≈242 g, slope ≈6.2 g/day, positive intercept–slope correlation,
partial-pooling shrinkage) is confirmed by four independent engines — from-scratch Gibbs,
PyMC, bayesm, and lme4 — and the mixture finds no growth types. The
coefficient-mixture sibling of the multinomial-logit
mixture.
Bayesian Hierarchical SUR
A Python implementation of a 4-block Bayesian Hierarchical SUR Gibbs sampler, combining cross-equation error correlation (Ω) with a random-effects hierarchy across stores. Applied to Dominick's Finer Foods orange juice scanner data (83 stores, 11 brands, ≈117 weeks): population-level price elasticities, store-level heterogeneity, store-demographic extensions, and unrestricted cross-price elasticities.
View example →