multivariate-probit
Multivariate probit models for correlated binary outcomes, with a pluggable inner model.
The model
Each outcome is a threshold on a latent Gaussian variable, and the outcomes are tied together by the correlation of those latents:
Y_j = 1[ η_j(x) + e_j > 0 ], e ~ N(0, Σ), j = 1 … d
η_j is an arbitrary real-valued function of the features — linear by default,
or XGBoost, a random forest, or anything else that fits (X, y). Σ is a
correlation matrix (unit diagonal) carrying the dependence between outcomes.
Marginally, P(Y_j = 1 | x) = Φ(η_j(x)).
Fitting is cross-fit two-stage IFM: every margin is fitted independently, then Σ is estimated by maximum likelihood with those margins held fixed. That separation is what lets the inner model be a black box. The algorithm, and the alternatives that were tested and rejected, are in docs/ifm.md.
Install
pip install multivariate-probit # core: numpy + scipy
pip install multivariate-probit[xgboost] # adds the "xgboost" preset
pip install multivariate-probit[all] # every preset
Quickstart
from multivariate_probit import MultivariateProbit
model = MultivariateProbit(inner="linear").fit(X, Y) # Y is (n, d), 0/1
proba = model.predict_proba(X)
proba.marginal # P(Y_j = 1 | x), shape (n, d)
proba.joint([1, 0, 1]) # P(Y = pattern | x), shape (n,)
proba.all() # P(every outcome = 1 | x)
proba.any(outcomes=[0, 2]) # P(at least one of these | x)
model.correlation_ # the fitted Σ, shape (d, d)
model.transform(X) # latent scores η, shape (n, d)
model.sample(X, n_samples=100) # simulated outcome patterns
model.score(X, Y) # mean joint log-likelihood
predict_proba returns an object that behaves like the marginal-probability
array (np.asarray(proba), indexing, .shape) and additionally answers the
joint questions Σ was estimated for. Marginal predictions do not involve Σ at
all; everything joint does.
Everything here is a squashing function over a latent index
The inner model never sees a probability, and never sees another outcome's labels. It produces an unbounded score η_j(x) on (-∞, ∞); Φ is the only squashing function applied to it. Any estimator that emits a real-valued score, or a probability that can be pushed back through Φ⁻¹, is a legal margin.
That is the whole abstraction, and it is why the inner model is swappable without touching the estimation code.
Inner models
| Preset | Estimator | Requires |
|---|---|---|
"linear" (default), "probit" |
native probit via IRLS | — |
"xgboost", "xgb" |
XGBClassifier, tuned for calibration |
xgboost |
"rf", "random_forest" |
RandomForestClassifier |
scikit-learn |
MultivariateProbit(inner="xgboost") # a preset
MultivariateProbit(inner=XGBClassifier(max_depth=4)) # any sklearn-shaped model
MultivariateProbit(inner=["linear", "xgboost", "linear"]) # one per outcome
available_inners() lists the presets; register_inner(name, factory) adds
your own. See docs/api.md for the inner-model contract.
Two knobs that cost time
dependence—"joint"(default) maximises the full d-variate likelihood for Σ."pairwise"maximises each pair's bivariate likelihood instead: consistent, orders of magnitude cheaper, and the right choice once you have more than a handful of outcomes.cv—5by default, cross-fitting the margins so Σ is never estimated from in-sample predictions. This is not optional hygiene: in-sample margins drive every fitted correlation to +1.cv=Noneskips it, which is defensible for the linear default and reckless for anything that can overfit.
Status
Alpha. The linear and XGBoost paths are covered by tests; the rf preset is
wired but untested. Standard errors are not computed — correlation_ is a
point estimate. Known gaps are listed in
docs/limitations.md.
Documentation
- docs/ifm.md — the estimation algorithm, and why IFM over the alternatives
- docs/implementation.md — what is hand-rolled, what comes from SciPy, and why
- docs/api.md — parameters, attributes, methods, extension points
- docs/limitations.md — known gaps and roadmap
Development
pip install -e ".[dev]"
pytest
License
MIT. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file multivariate_probit-0.1.0.tar.gz.
File metadata
- Download URL: multivariate_probit-0.1.0.tar.gz
- Upload date:
- Size: 34.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4018d7dec31b154210d9b40a3b3291ad47fa739c1094a55e6d470ebbfbd4dc3
|
|
| MD5 |
0b31d193d88f70b9df233e925beec285
|
|
| BLAKE2b-256 |
8ddd81edd6437eb2f1dc82412c80e4477fbf85c7d88ffcce57f3b28c6df5200c
|
File details
Details for the file multivariate_probit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: multivariate_probit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
abf1f9df24cef1f4cfdf527449b6089f924709d05db94b296f81e000dae898b1
|
|
| MD5 |
9f99d2496d40d287e05982726288f1d1
|
|
| BLAKE2b-256 |
7e0c6a4ebdbd38b2458cd5e598d7242dc008488d56dfd99f56d9051c6a944e0c
|