| Title: | Bayesian Regression for Combinatorial Response Data |
|---|---|
| Description: | Bayesian regression whose response data are integer-valued vectors subject to combinatorial constraints in the form of Ay<=b. Implements the Metropolis-Hastings-within-Gibbs sampler of Zheng et al. (2026) <doi:10.1080/01621459.2026.2714575>, an unconstrained probit baseline for comparison, benchmarking helpers, MCMC and regression diagnostics (effective sample sizes, split-Rhat, structured reports), plotting methods (trace, autocorrelation, violin, effective-sample-size, sampling-efficiency, residual heat map), and utilities for constraint validation (total unimodularity, feasibility) and data simulation. |
| Authors: | Hugh Zheng [aut, cre, cph] |
| Maintainer: | Hugh Zheng <hugh.stats@gmail.com> |
| License: | MIT + file LICENSE |
| Version: | 0.2.0 |
| Built: | 2026-10-13 10:49:38 UTC |
| Source: | https://github.com/yuzh98/combreg |
Convert to posterior draws_array
## S3 method for class 'crr_fit' as_draws(x, ...)## S3 method for class 'crr_fit' as_draws(x, ...)
x |
A |
... |
Unused. |
A posterior::draws_array (requires the 'posterior' package).
Post-warmup, thinned draws per chain.
## S3 method for class 'crr_fit' as.mcmc(x, ...)## S3 method for class 'crr_fit' as.mcmc(x, ...)
x |
A |
... |
Unused. |
Posterior mean coefficients
## S3 method for class 'crr_fit' coef(object, ...)## S3 method for class 'crr_fit' coef(object, ...)
object |
A |
... |
Unused. |
Posterior mean coefficient matrix (p x d).
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) coef(fit) # posterior mean of beta, a p x d matrix (row k = covariate k, # column j = response coordinate j) }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) coef(fit) # posterior mean of beta, a p x d matrix (row k = covariate k, # column j = response coordinate j) }
Fits the latent-utility regression model
where with totally
unimodular A, by MH-within-Gibbs sampling on the dual-certificate
augmented posterior (method = "mhwg"). method = "unconstrained" fits
independent Albert-Chib probit regressions per coordinate, ignoring the
constraints — useful as a baseline demonstrating constraint-ignoring bias.
crr( Y, X, constraints, data = NULL, method = c("mhwg", "unconstrained"), kernel = c("exponential", "half_gaussian"), prior = crr_prior(), n_iter = 5000, warmup = floor(n_iter/2), thin = 1, chains = 1, seed = NULL, control = crr_control(), verbose = FALSE )crr( Y, X, constraints, data = NULL, method = c("mhwg", "unconstrained"), kernel = c("exponential", "half_gaussian"), prior = crr_prior(), n_iter = 5000, warmup = floor(n_iter/2), thin = 1, chains = 1, seed = NULL, control = crr_control(), verbose = FALSE )
Y |
Response matrix ( |
X |
Covariate matrix ( |
constraints |
A crr_constraints object. Ignored (with a warning)
for |
data |
Data frame used to evaluate |
method |
Sampler: |
kernel |
Dual kernel for |
prior |
A crr_prior object. |
n_iter |
Total MCMC iterations per chain (including warmup). |
warmup |
Warmup iterations discarded by |
thin |
Thinning applied by the converters (draws are stored unthinned). |
chains |
Number of independent chains, run serially. |
seed |
Optional integer seed ( |
control |
A crr_control object. |
verbose |
Print progress every 1000 iterations. |
An object of class crr_fit; see summary.crr_fit().
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, chains = 1, seed = 1) summary(fit) }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, chains = 1, seed = 1) summary(fit) }
Fits several methods to the same data and collects a comparison table:
wall-clock time, MH acceptance, effective sample sizes and sampling
efficiency (min ESS per second), in-sample fit, and — when the true
coefficients are supplied, e.g. from simulate_crr() — estimation error
(RMSE) and coverage of the central posterior intervals. This is the
package's canonical way to compare the paper's constrained sampler with
the unconstrained probit baseline (or any subset of registered methods)
without writing bespoke benchmarking code.
crr_benchmark( Y, X, constraints, methods = c("mhwg", "unconstrained"), beta = NULL, prob = 0.95, ... )crr_benchmark( Y, X, constraints, methods = c("mhwg", "unconstrained"), beta = NULL, prob = 0.95, ... )
Y |
Response matrix ( |
X |
Covariate matrix ( |
constraints |
A crr_constraints object (used by constrained methods only). |
methods |
Character vector of methods accepted by |
beta |
True coefficient matrix ( |
prob |
Central posterior interval probability for coverage. |
... |
Passed on to |
An object of class crr_benchmark: a list with table (one row
per method) and fits (the underlying crr_fit objects, named by
method).
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) bm <- crr_benchmark(sim$Y, sim$X, con, beta = sim$beta, n_iter = 200, warmup = 100, seed = 1) bm }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) bm <- crr_benchmark(sim$Y, sim$X, con, beta = sim$beta, n_iter = 200, warmup = 100, seed = 1) bm }
Defines the feasible set of a
combinatorial response. The matrix A must be integer-valued and, for the
exactness of the dual-certificate augmentation used by crr(), totally
unimodular.
crr_constraints(A, b, direction = "<=", check_tum = TRUE, dedup = TRUE)crr_constraints(A, b, direction = "<=", check_tum = TRUE, dedup = TRUE)
A |
Integer constraint matrix ( |
b |
Integer right-hand side vector of length |
direction |
Either a single string or a length- |
check_tum |
Verify total unimodularity of |
dedup |
Remove redundant constraint rows: rows identical in |
An object of class crr_constraints with elements A, b
(in <= form), m, d.
A <- rbind(c(1, 1, 0), c(0, 1, 1)) con <- crr_constraints(A, b = c(1, 1))A <- rbind(c(1, 1, 0), c(0, 1, 1)) con <- crr_constraints(A, b = c(1, 1))
Tuning parameters for the MH-within-Gibbs sampler in crr().
crr_control( n_iter_hit_and_run = 50, n_iter_hit_and_run_mh = NULL, rho = 1, zeta_block = "adaptive", zeta_target_accept = 0.6, max_dir_tries = 1000, bound_truncation = 1e+05, n_threads = 1 )crr_control( n_iter_hit_and_run = 50, n_iter_hit_and_run_mh = NULL, rho = 1, zeta_block = "adaptive", zeta_target_accept = 0.6, max_dir_tries = 1000, bound_truncation = 1e+05, n_threads = 1 )
n_iter_hit_and_run |
Inner hit-and-run steps for the dual refresh
|
n_iter_hit_and_run_mh |
Inner hit-and-run steps for the dual
certificate drawn inside the Metropolis-Hastings update of the latent
utilities. |
rho |
Rate of the exponential dual kernel (ignored for
|
zeta_block |
Number of response coordinates updated per MH step for the
latent utilities. Either a positive integer for a fixed block size (the
paper uses |
zeta_target_accept |
Target MH acceptance rate for the adaptive block
controller (ignored when |
max_dir_tries |
Maximum attempts to draw a valid hit-and-run direction. |
bound_truncation |
Truncation applied to infinite line-segment bounds inside hit-and-run. |
n_threads |
OpenMP threads for the per-observation dual updates. Results do not depend on this value (each observation has its own RNG stream). |
An object of class crr_control.
Assembles a structured diagnostics report for a fitted model: per-parameter
posterior summaries with effective sample sizes, sampling efficiency
(ESS per second), split-Rhat convergence diagnostics, MH acceptance rates,
in-sample regression fit (exact-match and per-coordinate accuracy of the
fitted responses), and posterior predictive goodness-of-fit checks that
calibrate the fit statistics against what the model itself predicts (see
crr_ppc()). When the true coefficients are known (simulation), supplying
beta adds estimation error (RMSE) and interval coverage. Potential
problems (low ESS, high Rhat, low acceptance, extreme predictive p-values)
are collected as warnings and flagged by print().
crr_diagnostics(object, beta = NULL, prob = 0.95, n_rep = 50)crr_diagnostics(object, beta = NULL, prob = 0.95, n_rep = 50)
object |
A |
beta |
Optional true coefficient matrix ( |
prob |
Central posterior interval probability for the summary table. |
n_rep |
Posterior predictive replicates for the goodness-of-fit
checks; set to |
An object of class crr_diagnostics: a list with elements
table (per-parameter data frame with columns mean, sd, interval
bounds, ess, ess_per_sec, rhat), fit_stats (exact-match rate and
per-coordinate accuracy, or NULL if fitted responses are unavailable),
ppc (a crr_ppc object, or NULL if skipped/unavailable), truth
(RMSE, coverage, and the error matrix coef(object) - beta, or NULL
if beta was not supplied), warnings (character vector), and the
sampler configuration.
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) crr_diagnostics(fit, beta = sim$beta, n_rep = 20) }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) crr_diagnostics(fit, beta = sim$beta, n_rep = 20) }
Effective sample size per coefficient, computed from post-warmup thinned
draws and summed across chains (via coda::effectiveSize()).
crr_ess(object)crr_ess(object)
object |
A |
Named numeric vector of length p * d.
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) crr_ess(fit) # effective sample size per coefficient; larger means better # mixing (a few hundred is usually plenty for point estimates) }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) crr_ess(fit) # effective sample size per coefficient; larger means better # mixing (a few hundred is usually plenty for point estimates) }
Simulates replicated response matrices from the posterior predictive
distribution (draw from the retained draws, draw
, map each row to its feasible
maximizer) and compares observed test statistics against their predictive
distribution. Statistics: the exact-match rate and per-coordinate accuracy
of the point predictor fitted(object) applied to each replicate, and the
per-coordinate marginal response frequencies.
crr_ppc(object, n_rep = 50, seed = NULL)crr_ppc(object, n_rep = 50, seed = NULL)
object |
A |
n_rep |
Number of posterior predictive replicates. |
seed |
Optional integer seed. |
Point-prediction accuracy is bounded by the intrinsic utility noise, so a low raw exact-match rate does not by itself indicate misfit: the model fits well when the observed statistics are typical of the predictive distribution (two-sided p-values away from 0). Constrained fits require the 'lpSolve' package.
An object of class crr_ppc: a list with observed (named
statistic vector), replicated (n_rep x n_stats matrix), p_value
(two-sided predictive p-values per statistic), and n_rep.
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) crr_ppc(fit, n_rep = 20, seed = 1) }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) crr_ppc(fit, n_rep = 20, seed = 1) }
Currently a single family: independent Gaussian priors
on all regression coefficients.
crr_prior(family = "gaussian", sd = 1)crr_prior(family = "gaussian", sd = 1)
family |
Prior family; only |
sd |
Prior standard deviation (scalar, positive). |
An object of class crr_prior.
crr_prior() # N(0, 1) crr_prior(sd = 10) # weakly informativecrr_prior() # N(0, 1) crr_prior(sd = 10) # weakly informative
Potential scale reduction factor per coefficient, computed on split
half-chains so it is defined even for chains = 1.
crr_rhat(object)crr_rhat(object)
object |
A |
Named numeric vector of length p * d.
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) crr_rhat(fit) # values near 1 indicate convergence; above ~1.05 is a # sign the chain needs to run longer }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) crr_rhat(fit) # values near 1 indicate convergence; above ~1.05 is a # sign the chain needs to run longer }
Posterior-point-estimate responses on the training covariates:
predict(object, type = "response").
## S3 method for class 'crr_fit' fitted(object, ...)## S3 method for class 'crr_fit' fitted(object, ...)
object |
A |
... |
Unused. |
Binary matrix (n x d).
Check feasibility of responses
is_feasible(constraints, Y)is_feasible(constraints, Y)
constraints |
A crr_constraints object. |
Y |
Response matrix ( |
Logical vector of length n: does each row satisfy A y <= b and
y in {0,1}?
Exhaustively checks whether every square submatrix of A has determinant
in {-1, 0, 1}. Cost grows exponentially in min(nrow(A), ncol(A)); use
only for small-to-moderate matrices.
is_tum(A)is_tum(A)
A |
Integer matrix. |
TRUE or FALSE, with attribute "witness" (a list with rows,
cols, determinant) when FALSE.
is_tum(rbind(c(1, 1, 0), c(0, 1, 1))) # TRUE is_tum(rbind(c(1, 1, 0), c(1, 0, 1), c(0, 1, 1))) # FALSEis_tum(rbind(c(1, 1, 0), c(0, 1, 1))) # TRUE is_tum(rbind(c(1, 1, 0), c(1, 0, 1), c(0, 1, 1))) # FALSE
All plots use post-warmup thinned draws.
## S3 method for class 'crr_fit' plot( x, pars = NULL, type = c("trace", "acf", "violin", "ess", "ess_time", "residual", "beta_diff"), beta = NULL, ... )## S3 method for class 'crr_fit' plot( x, pars = NULL, type = c("trace", "acf", "violin", "ess", "ess_time", "residual", "beta_diff"), beta = NULL, ... )
x |
A |
pars |
Character vector of parameter names (e.g. |
type |
Plot type; see Details. |
beta |
True coefficient matrix ( |
... |
Passed to the underlying base plotting functions. |
"trace": per-parameter trace plots, chains overlaid.
"acf": per-parameter autocorrelation functions (chains pooled).
"violin": violin plot of the marginal posterior of each coefficient,
with median and central 95% interval.
"ess": effective sample size per coefficient, with a dashed reference
line at 100.
"ess_time": effective sample size per second of sampling time —
the efficiency measure used for method comparison in the paper.
"residual": heat map of the training residuals Y - fitted(x)
(entries in {-1, 0, 1}); rows are observations, columns response
coordinates.
"beta_diff": heat map of the coefficient estimation error
coef(x) - beta against a known true coefficient matrix (supplied via
beta, e.g. from simulate_crr()), with the error printed in each
cell.
x, invisibly.
Posterior predictions
## S3 method for class 'crr_fit' predict(object, newdata = NULL, type = c("utility", "response"), ...)## S3 method for class 'crr_fit' predict(object, newdata = NULL, type = c("utility", "response"), ...)
object |
A |
newdata |
Covariate matrix ( |
type |
|
... |
Unused. |
Matrix (n_new x d) of utilities or feasible responses.
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) head(predict(fit, type = "response")) # feasible 0/1 predictions; each row # satisfies the constraints A y <= b }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 50, p = 2, constraints = con, seed = 1) fit <- crr(sim$Y, sim$X, con, n_iter = 200, warmup = 100, seed = 1) head(predict(fit, type = "response")) # feasible 0/1 predictions; each row # satisfies the constraints A y <= b }
For small problems (d * m <= 50 and m >= 2 fails), draws random
{-1, 0, 1} matrices until one is totally unimodular, with b = 1. For
larger problems, generates a random network (incidence-type) matrix with
one +1 and one -1 per row — totally unimodular by construction — and
Bernoulli right-hand sides. This mirrors the simulation design of the
paper.
random_constraints(d, m)random_constraints(d, m)
d |
Response dimension. |
m |
Number of constraints. |
A crr_constraints object.
Training residuals Y - fitted(object), entries in {-1, 0, 1}.
## S3 method for class 'crr_fit' residuals(object, ...)## S3 method for class 'crr_fit' residuals(object, ...)
object |
A |
... |
Unused. |
Integer matrix (n x d).
Draws , coefficients
(unless supplied), latent utilities
with standard normal noise,
and responses
solved by integer programming.
simulate_crr(n, p, constraints, beta = NULL, seed = NULL)simulate_crr(n, p, constraints, beta = NULL, seed = NULL)
n |
Number of observations. |
p |
Number of covariates. |
constraints |
A crr_constraints object defining |
beta |
Optional true coefficient matrix ( |
seed |
Optional integer seed. |
List with Y (n x d), X (n x p), beta (p x d,
the truth), zeta (n x d), and constraints.
if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 20, p = 2, constraints = con, seed = 1) }if (requireNamespace("lpSolve", quietly = TRUE)) { con <- crr_constraints(rbind(c(1, 1, 0), c(0, 1, 1)), b = c(1, 1)) sim <- simulate_crr(n = 20, p = 2, constraints = con, seed = 1) }
Posterior summary of a crr fit
## S3 method for class 'crr_fit' summary(object, ...)## S3 method for class 'crr_fit' summary(object, ...)
object |
A |
... |
Unused. |
A data frame with posterior mean, sd, and 2.5/50/97.5 percent quantiles per coefficient, computed from post-warmup thinned draws pooled across chains.