Distribution Classes
The Monte Carlo Shapley estimator requires conditional sampling from the joint input distribution — drawing from \(P(\mathbf{X}_{-u} \mid \mathbf{X}_u = \mathbf{x}_u)\) for arbitrary subsets \(u\). ShapleyX defines a distribution class interface that any input model can implement to plug into the MC Shapley machinery.
This page describes the built-in classes, the interface contract, and several custom classes developed for the example case studies.
Interface Contract
A distribution class must provide four attributes and methods:
| Requirement | Signature | Description |
|---|---|---|
self.d |
int |
Number of input dimensions |
sample_joint(n) |
(n,) → (n, d) array |
Draw \(n\) i.i.d. samples from the full joint distribution |
sample_conditional(u, x) |
(list, 1D array) → (d,) array |
Draw one sample conditioned on \(\mathbf{X}_u = \mathbf{x}\) |
sample_conditional_batch(u, X) |
(list, 2D array) → (N, d) array |
Draw \(N\) conditional samples, vectorised — used by the MC loops for efficiency |
Where:
uis a list of variable indices (e.g.,[0, 2])xis a 1D array of fixed values for those variablesXis a 2D array of shape(N, len(u))— \(N\) different conditioning points
The sample_conditional method can delegate to sample_conditional_batch:
def sample_conditional(self, u, x):
X = self.sample_conditional_batch(u, np.atleast_2d(x))
return X[0]
Built-in Classes
MultivariateNormal
Jointly normal inputs with analytical conditional distributions.
from shapleyx.utilities.mc_shapley import MultivariateNormal
joint = MultivariateNormal(
mean=[0.0, 0.0, 0.0],
cov=[[1.0, 0.5, 0.0],
[0.5, 1.0, 0.0],
[0.0, 0.0, 1.0]],
)
| Method | Implementation |
|---|---|
sample_joint |
np.random.multivariate_normal |
sample_conditional_batch |
Closed-form: \(\boldsymbol{\mu}_{v\mid u} = \boldsymbol{\mu}_v + \boldsymbol{\Sigma}_{vu}\boldsymbol{\Sigma}_{uu}^{-1}(\mathbf{x}_u - \boldsymbol{\mu}_u)\), Cholesky of conditional covariance |
Fastest of the built-in classes — uses a single Cholesky decomposition per subset.
GaussianCopulaUniform
Uniform marginals \([a_i, b_i]\) with dependence induced by a latent multivariate normal.
from shapleyx.utilities.mc_shapley import GaussianCopulaUniform
joint = GaussianCopulaUniform(
lows=[-np.pi, -np.pi, -np.pi],
highs=[np.pi, np.pi, np.pi],
corr=np.eye(3),
)
| Method | Implementation |
|---|---|
sample_joint |
Draw latent normal → norm.cdf → scale to \([a_i, b_i]\) |
sample_conditional_batch |
Map fixed variables to latent normal via norm.ppf, condition with MultivariateNormal formulas, map back via norm.cdf |
Used in the Iooss & Prieur correlation sweep tutorials.
TruncatedMultivariateNormal
Jointly normal with per-dimension truncation bounds \([a_i, b_i]\).
from shapleyx.utilities.mc_shapley import TruncatedMultivariateNormal
joint = TruncatedMultivariateNormal(
mean=[0.0, 0.0, 0.0],
cov=[[1.0, 0.5, 0.0],
[0.5, 1.0, 0.0],
[0.0, 0.0, 1.0]],
lower=[-1.0, -1.0, -1.0],
upper=[1.0, 1.0, 1.0],
)
sample_joint | Vectorised Gibbs sampling — iterates over variables, each drawn from a univariate truncated normal conditioned on current values of all others |
| sample_conditional_batch | Gibbs on the \(\lvert v\rvert\)-dimensional truncated conditional, started at the well-centred untruncated conditional mean |
Tune joint_burn_in (default 30) and cond_burn_in (default 5) to balance
speed and convergence. Use -np.inf / np.inf for unbounded dimensions.
Custom Classes from Case Studies
Several notebooks define ad-hoc distribution classes to handle challenging input models. They illustrate the flexibility of the interface.
GaussianCopulaMixed — Cantilever Beam
Mixed Normal + LogNormal marginals with a Gaussian copula for correlation.
Defined inline in Examples/cantilever_beam.ipynb.
class GaussianCopulaMixed:
def __init__(self, marginals, latent_corr):
self.d = len(marginals)
self._mvn = MultivariateNormal(mean=np.zeros(d), cov=latent_corr)
# ...
def _to_latent(self, x, params): # original → N(0,1)
def _from_latent(self, z, params): # N(0,1) → original
def sample_joint(self, n): # latent draw → _to_original
def sample_conditional_batch(...): # map fixed to latent → condition →
# map back
Key pattern: the class wraps a MultivariateNormal in latent space and
provides two static methods (_to_latent, _from_latent) that are dispatched
per column based on the marginal type. Adding a new marginal type (e.g.,
Weibull, Beta) requires only a new branch in these two methods.
GaussianCopulaFull — Borehole Function
Extends the mixed-copula pattern to Uniform marginals in addition to
Normal and LogNormal. Defined in Examples/borehole.ipynb.
The only change from GaussianCopulaMixed is an extra branch in the
transform methods:
# Uniform[a, b]: map via Φ⁻¹((x-a)/(b-a))
if params[0] == 'uniform':
_, a, b = params
u = np.clip((x - a) / (b - a), 1e-15, 1 - 1e-15)
return norm.ppf(u)
Writing Your Own Distribution Class
Follow these steps to create a custom distribution:
Step 1: Choose a strategy
| If your distribution is... | Consider... |
|---|---|
| A known multivariate family | Wrapping MultivariateNormal in latent space (Gaussian copula) |
| Defined by physical constraints | Rejection sampling on top of a base distribution |
| Available only via a black-box simulator | Pre-generating a large sample and using nearest-neighbour approximation (see Demange-Chryst 2022, Appendix I) |
Step 2: Implement the interface
class MyDistribution:
def __init__(self, ...):
self.d = ... # number of dimensions
def sample_joint(self, n):
"""Return (n, d) array of joint samples."""
...
def sample_conditional(self, u_indices, fixed_x):
"""Return (d,) array — single conditional sample."""
X = self.sample_conditional_batch(
u_indices, np.atleast_2d(np.asarray(fixed_x, dtype=float))
)
return X[0]
def sample_conditional_batch(self, u_indices, fixed_X):
"""Return (N, d) array — N conditional samples."""
...
Step 3: Test your class
Verify that the class produces correct correlations and respects the conditional structure:
joint = MyDistribution(...)
# Check marginals
X = joint.sample_joint(10000)
print("Means:", X.mean(axis=0))
# Check conditional: E[X_i | X_j = some_value] should differ from E[X_i]
x_cond = joint.sample_conditional([0], [some_value])
# Check batch: should be faster than a Python loop
X_batch = joint.sample_conditional_batch([0], np.array([[v1], [v2], [v3]]))
Step 4: Plug into ShapleyX
from shapleyx.utilities.mc_shapley import shapley_effects
effects, sh, var = shapley_effects(my_model, joint, N=5000, method='exhaustive')
Your class is now a first-class citizen of the MC Shapley pipeline — it works with the exhaustive method, permutation method, bootstrap, batch prediction, and progress bars.
Case Studies
| Report | Distribution | Key Features |
|---|---|---|
| Cantilever Beam | GaussianCopulaMixed |
6 inputs, LogNormal + Normal marginals, correlated dimensional parameters, analytical and surrogate target Shapley effects validated against Demange-Chryst (2022) [PDF report] |
| Borehole Function | GaussianCopulaFull |
8 inputs, Normal + LogNormal + Uniform marginals, optional geological correlations, Sobol + Shapley from single run |
| Truncated Normal | TruncatedMultivariateNormal (built-in) |
3 inputs, per-dimension truncation, multiple truncation schemes, RS-HDMR surrogate comparison |
| Iooss & Prieur Correlation Sweep | GaussianCopulaUniform (built-in) |
3 inputs, Shapley + \(S_i\) + \(T_i\) vs correlation \(\rho\), exhaustive and permutation methods |
| Wing Weight Function | GaussianCopulaUniform (built-in) |
10 inputs, all Uniform spanning 5 orders of magnitude, independent and correlated analyses, validated against OpenTURNS reference [PDF report] |
| Fire Spread Model | GaussianCopulaFire |
10 inputs, mixed LogNormal + Normal + scaled LogNormal with truncation, target Shapley effects without IS, demonstrates MC limits for rare events (\(p_f \approx 10^{-4}\)) [PDF report] |
| SAC-SMA Model | — | High-dimensional conceptual rainfall-runoff model for flood forecasting, operational hydrologic parameters, RS-HDMR with ARD [PDF report] |
See Also
- MC Shapley How-to Guide — usage instructions
- Theory: Shapley Effects with Correlated Inputs
- MC Shapley Reference — API documentation
- Owen & Prieur (2017) — the covariance formulation