Greenacre 2023 - Compositional Outcome Models in PyMC
Summary
Three Bayesian likelihoods cover compositional outcomes: logistic-normal (Gaussian on ALR/ILR coordinates, flexible covariance), Dirichlet (mean-precision, only negative correlations) and Dirichlet-multinomial (counts with extra-multinomial variation). This concept note (not in the paper) sketches each in PyMC and the LOO comparison trap across scales. Code is untested.
Overview
Greenacre et al. stress that once a composition is expressed in logratios, “everything that follows is standard statistical practice”. Bayesian regression with a compositional response is therefore a multivariate-normal regression on logratio coordinates, or a Dirichlet family on the simplex. The motivating example E70 (Arctic lake sediments; 2024 UK election shares) compares these.
Main Content
Logistic-normal regression
Let yi∈SD−1 and zi=alr(yi)∈RD−1. Then zi∼N(B⊤xi,Σ), Σ=diag(σ)Rdiag(σ), R∼LKJ(η). Covariate-dependent scale: logσik=ak+ck⊤xi. The ALR reference choice does not change the model class (any ALR is a linear map of any other) but changes the prior geometry.
For counts ni with total Ni: pi∼Dir(ϕmi), ni∼Multinomial(Ni,pi), marginalised analytically. Overdispersion relative to the multinomial is 1+(Ni−1)/(ϕ+1). Natural for election counts, microbiome counts, and the genomics case in Sec. 6 (multinomial conditioned on totals).
PyMC 5 sketches (untested)
import numpy as np, pymc as pm, pytensor.tensor as pt# Y: (n, D) compositions with no zeros; X: (n, p) with intercept columnn, D = Y.shape; p = X.shape[1]Z = np.log(Y[:, :-1] / Y[:, [-1]]) # ALR, last part = referencewith pm.Model() as logistic_normal: B = pm.Normal("B", 0, 1, shape=(p, D - 1)) chol, corr, sds = pm.LKJCholeskyCov("chol", n=D - 1, eta=2.0, sd_dist=pm.Exponential.dist(2.0), compute_corr=True) pm.MvNormal("Z", mu=pt.dot(X, B), chol=chol, observed=Z)with pm.Model() as dirichlet: B = pm.Normal("B", 0, 1, shape=(p, D - 1)) eta = pt.concatenate([pt.dot(X, B), pt.zeros((n, 1))], axis=1) m = pt.special.softmax(eta, axis=1) log_phi = pm.Normal("log_phi", 3, 1) pm.Dirichlet("Y", a=pt.exp(log_phi) * m, observed=Y)with pm.Model() as dirmult: # counts N: (n, D) B = pm.Normal("B", 0, 1, shape=(p, D - 1)) m = pt.special.softmax(pt.concatenate([pt.dot(X, B), pt.zeros((n, 1))], axis=1), axis=1) phi = pm.Gamma("phi", 2, 0.1) pm.DirichletMultinomial("N", n=N.sum(1), a=phi * m, observed=N)
Comparing across scales. Logistic-normal likelihoods are densities on RD−1 (ALR scale), Dirichlet likelihoods are densities on the simplex. LOO/ELPD values are comparable only after adding the Jacobian of the ALR map, log∣J∣=−∑j=1Dlogyj (so that the logistic-normal density is on SD−1); forgetting it makes the Gaussian look spuriously better or worse. This is the E70 “LOO across scales via the log-ratio Jacobian” point.
Spurious correlation. Compare the correlation matrix of raw shares (forced negative bias) with that of the ALR coordinates; coefficient interpretation should be in terms of log-ratios (effect of x on log(yj/yref)).
For constituency-level vote shares with covariates: Dirichlet-multinomial on vote counts (accounts for sampling noise from constituency size) plus a logistic-normal random effect for region is the multi-level version; partial pooling across regions follows Hierarchical Models.