Cantilever Beam — Demange-Chryst et al. (2022), Example 4.2¶
This notebook replicates the cantilever beam reliability-oriented sensitivity analysis from Demange-Chryst et al. [1]. The goal is to estimate Shapley effects for a structural engineering problem with correlated mixed-distribution inputs (LogNormal + Normal).
Model. A rectangular cantilever beam under two orthogonal tip forces $F_X$ and $F_Y$. The maximum vertical tip displacement is:
$$ D(\mathbf{x}) = \frac{4L^3}{E\,l_X l_Y} \sqrt{\left(\frac{F_X}{l_X^2}\right)^2 + \left(\frac{F_Y}{l_Y^2}\right)^2} $$
The failure threshold is $D > t = 0.066\,\text{m}$.
Input variables (6) with mixed Normal / LogNormal marginals and correlations between the three dimensional parameters.
[1] Demange-Chryst, J., Bachoc, F., & Morio, J. (2022). "Shapley effect estimation in reliability-oriented sensitivity analysis with correlated inputs by importance sampling." IJUQ, arXiv:2202.12679.
# Import dependencies
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import norm, lognorm
from scipy.stats.qmc import Sobol
from shapleyx.utilities.mc_shapley import (
MultivariateNormal,
shapley_effects,
)
from importlib.metadata import version
print(f"Running on ShapleyX v{version('shapleyx')}")
Running on ShapleyX v0.5.1
Input Distribution¶
Six variables — three LogNormal forces/modulus, three correlated Normal geometric parameters. Correlations (Pearson): $\rho(l_X,l_Y)=0.55$, $\rho(L,l_X)=\rho(L,l_Y)=0.45$.
d = 6
# Marginal parameters: (dist_type, mean, CV)
# LogNormal params converted to underlying normal (mu, sigma)
def lognorm_params(mean, cv):
"""Convert LogNormal (mean, CV) → underlying normal (mu, sigma)."""
sigma = np.sqrt(np.log(1 + cv**2))
mu = np.log(mean) - 0.5 * sigma**2
return mu, sigma
def normal_params(mean, cv):
"""Convert Normal (mean, CV) → (mu, sigma)."""
return mean, mean * cv
# Table 1 from the paper
marginals = {
'FX': ('lognormal', *lognorm_params(556.8, 0.08)),
'FY': ('lognormal', *lognorm_params(453.6, 0.08)),
'E': ('lognormal', *lognorm_params(200e9, 0.06)),
'lX': ('normal', *normal_params(0.062, 0.1)),
'lY': ('normal', *normal_params(0.0987, 0.1)),
'L': ('normal', *normal_params(4.29, 0.1)),
}
labels = list(marginals.keys())
print(f"{'Variable':>6s} {'Type':>10s} {'Mean':>12s} {'Std':>12s}")
print("-" * 48)
for name, (dist, mu, sigma) in marginals.items():
if dist == 'lognormal':
mn = np.exp(mu + 0.5 * sigma**2)
st = mn * np.sqrt(np.exp(sigma**2) - 1)
else:
mn, st = mu, sigma
print(f"{name:>6s} {dist:>10s} {mn:12.4g} {st:12.4g}")
Variable Type Mean Std
------------------------------------------------
FX lognormal 556.8 44.54
FY lognormal 453.6 36.29
E lognormal 2e+11 1.2e+10
lX normal 0.062 0.0062
lY normal 0.0987 0.00987
L normal 4.29 0.429
# Correlation matrix (Pearson) — only dimensional params are correlated
# Indices: 0=FX, 1=FY, 2=E, 3=lX, 4=lY, 5=L
corr_pearson = np.array([
[1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, -0.55, 0.45],
[0.0, 0.0, 0.0, -0.55, 1.0, 0.45],
[0.0, 0.0, 0.0, 0.45, 0.45, 1.0],
])
print("Pearson correlation matrix:")
print(pd.DataFrame(corr_pearson, index=labels, columns=labels).round(2))
Pearson correlation matrix:
FX FY E lX lY L
FX 1.0 0.0 0.0 0.00 0.00 0.00
FY 0.0 1.0 0.0 0.00 0.00 0.00
E 0.0 0.0 1.0 0.00 0.00 0.00
lX 0.0 0.0 0.0 1.00 -0.55 0.45
lY 0.0 0.0 0.0 -0.55 1.00 0.45
L 0.0 0.0 0.0 0.45 0.45 1.00
Gaussian Copula Distribution Class¶
To handle mixed LogNormal + Normal marginals with correlation, we build a Gaussian copula wrapper: transform each marginal to a standard normal via the probability integral transform, apply the correlation structure in the latent normal space, then map back.
The correlation matrix uses the exact Demange-Chryst et al. (2022) latent correlations: ρ(lX,lY)=0.55, ρ(L,lX)=ρ(L,lY)=0.45. For Normal marginals (lX, lY, L), the Pearson correlation equals the latent normal correlation. The LogNormal variables (FX, FY, E) are uncorrelated, so the latent correlation matrix is the identity for that block.
class GaussianCopulaMixed:
"""Gaussian copula with arbitrary marginal distributions.
Supports LogNormal and Normal marginals with a user-specified
latent correlation matrix. Conditional sampling is performed
in the latent normal space using the multivariate normal
conditional distribution, then mapped back to the original space.
"""
def __init__(self, marginals, latent_corr):
self.d = len(marginals)
self.labels = list(marginals.keys())
self._marginals = marginals
self._latent_corr = np.asarray(latent_corr)
# Underlying multivariate normal for latent-space sampling
self._mvn = MultivariateNormal(
mean=np.zeros(self.d), cov=self._latent_corr
)
@staticmethod
def _marginal_to_latent(x, dist, mu, sigma):
"""Transform a single marginal column original -> N(0,1)."""
if dist == "lognormal":
x = np.clip(np.asarray(x), 1e-15, None)
return norm.ppf(lognorm.cdf(x, s=sigma, scale=np.exp(mu)))
else:
return (np.asarray(x) - mu) / sigma
@staticmethod
def _marginal_from_latent(z, dist, mu, sigma):
"""Transform a single marginal column N(0,1) -> original."""
if dist == "lognormal":
return lognorm.ppf(norm.cdf(np.asarray(z)), s=sigma, scale=np.exp(mu))
else:
return mu + sigma * np.asarray(z)
def _to_original(self, Z):
"""Transform latent normal -> original space, vectorised."""
X = np.zeros_like(Z)
for j in range(self.d):
name = self.labels[j]
dist, mu, sigma = self._marginals[name]
X[:, j] = self._marginal_from_latent(Z[:, j], dist, mu, sigma)
return X
def sample_joint(self, n):
"""Draw n joint samples."""
Z = self._mvn.sample_joint(n)
return self._to_original(Z)
def sample_conditional(self, u_indices, fixed_x):
"""Draw one 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):
"""Draw N conditional samples.
Strategy: map fixed_X to latent space, condition in the
latent multivariate normal, map the result back.
"""
u = np.asarray(u_indices)
N = fixed_X.shape[0]
fixed_X = np.asarray(fixed_X, dtype=float)
if len(u) == 0:
return self.sample_joint(N)
# Map fixed variables to latent space
Z_fixed = np.zeros((N, len(u)))
for k, idx in enumerate(u):
name = self.labels[idx]
dist, mu, sigma = self._marginals[name]
Z_fixed[:, k] = self._marginal_to_latent(
fixed_X[:, k], dist, mu, sigma
)
# Condition in latent normal space
Z_cond = self._mvn.sample_conditional_batch(u, Z_fixed)
# Map back to original space
return self._to_original(Z_cond)
joint = GaussianCopulaMixed(marginals, corr_pearson)
print("GaussianCopulaMixed distribution ready.")
print(f"Dimension: {joint.d}")
GaussianCopulaMixed distribution ready. Dimension: 6
Quick Validation: Empirical Correlations¶
Verify that the copula reproduces the specified Pearson correlations.
X_check = joint.sample_joint(50000)
emp_corr = np.corrcoef(X_check.T)
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
pairs = [(3, 4, 'lX vs lY'), (3, 5, 'lX vs L'), (4, 5, 'lY vs L')]
for ax, (i, j, title) in zip(axes, pairs):
ax.scatter(X_check[:, i], X_check[:, j], alpha=0.15, s=2, color='steelblue')
ax.set_title(f'{title}\n'
f'ρ = {emp_corr[i,j]:.3f} (target: {corr_pearson[i,j]:.2f})')
ax.set_xlabel(labels[i])
ax.set_ylabel(labels[j])
plt.suptitle('Empirical Correlations — Gaussian Copula', fontsize=13)
plt.tight_layout()
plt.show()
def cantilever_deflection(x):
"""Maximum vertical tip displacement (1D input)."""
FX, FY, E, lX, lY, L = x
if lX <= 0 or lY <= 0 or E <= 0:
return 1e10 # penalty for unphysical values
term = np.sqrt((FX / lX**2)**2 + (FY / lY**2)**2)
return (4 * L**3) / (E * lX * lY) * term
def cantilever_deflection_batch(X):
"""Vectorised deflection for batch evaluation."""
FX, FY = X[:, 0], X[:, 1]
E = X[:, 2]
lX, lY, L = X[:, 3], X[:, 4], X[:, 5]
# Guard against unphysical values
mask = (lX > 0) & (lY > 0) & (E > 0)
D = np.full(len(X), 1e10)
term = np.sqrt((FX[mask] / lX[mask]**2)**2
+ (FY[mask] / lY[mask]**2)**2)
D[mask] = (4 * L[mask]**3) / (E[mask] * lX[mask] * lY[mask]) * term
return D
t = 0.066 # failure threshold (m)
print(f"Failure threshold: D > {t} m")
Failure threshold: D > 0.066 m
# Quick histogram of deflection values
D_sample = cantilever_deflection_batch(X_check[:20000])
D_sample = D_sample[D_sample < 1e9] # remove penalty values
fig, ax = plt.subplots(figsize=(8, 4))
ax.hist(D_sample, bins=80, density=True, color='steelblue', alpha=0.85)
ax.axvline(t, color='crimson', linestyle='--', linewidth=2, label=f'Threshold t = {t} m')
ax.set_xlabel('Deflection D (m)')
ax.set_ylabel('Density')
ax.set_title('Cantilever Beam Tip Deflection Distribution')
ax.legend()
pf_est = np.mean(D_sample > t)
print(f"Estimated failure probability: {pf_est:.4f}")
plt.tight_layout()
plt.show()
Estimated failure probability: 0.0150
Variance-Based Shapley Effects (on Deflection $D$)¶
First, we compute standard (variance-based) Shapley effects on the continuous deflection $D$. This tells us which inputs drive the magnitude of the deflection.
The exhaustive method is used with $N = 10{,}000$ samples and $B = 200$ bootstrap replications.
eff, sh, var, lower, upper = shapley_effects(
cantilever_deflection,
joint,
N=10000,
method='exhaustive',
B=200,
alpha=0.05,
predict_batch=cantilever_deflection_batch,
random_state=42,
progress=False,
)
results = pd.DataFrame({
'Variable': labels,
'Effect': eff,
'Lower': lower,
'Upper': upper,
})
results
| Variable | Effect | Lower | Upper | |
|---|---|---|---|---|
| 0 | FX | 0.108895 | 0.100801 | 0.119743 |
| 1 | FY | -0.007343 | -0.017977 | 0.005157 |
| 2 | E | 0.075724 | 0.066379 | 0.084395 |
| 3 | lX | 0.205135 | 0.196353 | 0.214096 |
| 4 | lY | 0.363718 | 0.350515 | 0.374136 |
| 5 | L | 0.253870 | 0.243635 | 0.263433 |
Target Shapley Effects (on Failure Indicator $\mathbf{1}_{D>t}$)¶
The paper computes target (reliability-oriented) Shapley effects on the binary failure indicator $\mathbf{1}_{D>t}$. This answers: which inputs are most responsible for pushing the beam into failure?
We define a binary model function and run the same MC Shapley estimation on it.
def failure_indicator(x):
"""Binary failure: 1 if deflection exceeds threshold, else 0."""
D = cantilever_deflection(x)
return 1.0 if D > t else 0.0
def failure_indicator_batch(X):
"""Vectorised failure indicator."""
D = cantilever_deflection_batch(X)
return (D > t).astype(float)
# Estimate failure probability from a large sample
X_pf = joint.sample_joint(200000)
pf_est = failure_indicator_batch(X_pf).mean()
print(f"Estimated failure probability: {pf_est:.4f} (paper: ~0.015)")
print(f"Expected failures in N=10000: {10000 * pf_est:.0f}")
Estimated failure probability: 0.0153 (paper: ~0.015) Expected failures in N=10000: 153
# Target Shapley effects on the binary failure indicator
# Note: with pf ~ 1-7%, enough failure samples exist for reasonable estimation
eff_t, sh_t, var_t, lower_t, upper_t = shapley_effects(
failure_indicator,
joint,
N=100000,
method='exhaustive',
B=200,
alpha=0.05,
predict_batch=failure_indicator_batch,
random_state=42,
progress=False,
)
results_target = pd.DataFrame({
'Variable': labels,
'Target Shapley': eff_t,
'Lower': lower_t,
'Upper': upper_t,
})
results_target
| Variable | Target Shapley | Lower | Upper | |
|---|---|---|---|---|
| 0 | FX | 0.150356 | 0.142793 | 0.160588 |
| 1 | FY | 0.006359 | -0.005752 | 0.019313 |
| 2 | E | 0.093255 | 0.085386 | 0.101609 |
| 3 | lX | 0.281758 | 0.272918 | 0.290232 |
| 4 | lY | 0.261647 | 0.251639 | 0.270475 |
| 5 | L | 0.206626 | 0.198368 | 0.214996 |
RS-HDMR Surrogate Model¶
To demonstrate the surrogate-based workflow, we train an RS-HDMR model on uniformly sampled data covering the practical range of each input variable (approximately $\mu \pm 5\sigma$). The surrogate is then used as the model function for MC Shapley estimation.
This tests whether a surrogate trained on independent uniform samples can recover accurate Shapley effects under the correlated mixed-distribution deployment.
# Generate uniform training data across practical input ranges
N_train = 2048
# Ranges covering ~mean +/- 5 sigma for each variable
ranges = {
'FX': (350, 850), # LogNormal(556.8, CV=0.08)
'FY': (280, 700), # LogNormal(453.6, CV=0.08)
'E': (1.4e11, 2.8e11), # LogNormal(2e11, CV=0.06)
'lX': (0.025, 0.100), # Normal(0.062, CV=0.1)
'lY': (0.040, 0.160), # Normal(0.0987, CV=0.1)
'L': (2.0, 7.0), # Normal(4.29, CV=0.1)
}
# Draw shifted and scrambled Sobol samples
sampler = Sobol(d, scramble=True, seed=123)
samples = sampler.random(N_train)
X_unif = np.empty((N_train, d))
for j, name in enumerate(labels):
lo, hi = ranges[name]
X_unif[:, j] = lo + (hi - lo) * samples[:, j]
Y_train = cantilever_deflection_batch(X_unif)
# Build DataFrame for RS-HDMR
df_train = pd.DataFrame(X_unif, columns=labels)
df_train['Y'] = Y_train
print(f"{len(df_train)} training samples")
print(f"Y range: [{Y_train.min():.4f}, {Y_train.max():.4f}] m")
print(f"Failure fraction (D > {t}): {(Y_train > t).mean():.3f}")
2048 training samples Y range: [0.0008, 4.4623] m Failure fraction (D > 0.066): 0.458
from shapleyx import rshdmr
# Fit RS-HDMR surrogate
model = rshdmr(
df_train,
polys=[10], # PC type expansion up to order 10
n_iter=300,
method='ard_cv',
cv_method='bayesian',
cv_tol=0.005,
)
sob_surr, shap_surr, total_surr = model.run_all()
Found a DataFrame ============================================================ Transforming data to unit hypercube ============================================================ Feature: FX, Min Value: 350.0288, Max Value: 849.8679 Feature: FY, Min Value: 280.0073, Max Value: 699.9427 Feature: E, Min Value: 140060557518.1544, Max Value: 279998504221.4393 Feature: lX, Min Value: 0.0250, Max Value: 0.1000 Feature: lY, Min Value: 0.0400, Max Value: 0.1600 Feature: L, Min Value: 2.0019, Max Value: 6.9994 ============================================================ Building basis functions ============================================================ Total basis functions in basis set : 8007 Total number of features in basis set is 8007 ============================================================ Running regression analysis ============================================================ running ARD Fit Execution Time : 168.633618 -- Model complete ============================================================ RS-HDMR model performance statistics ============================================================ variance of data : 0.137 sum of coefficients^2 : 0.137 variance ratio : 1.000 =============================== mae error on test set : 0.003 mse error on test set : 0.000 explained variance score: 1.000 =============================== slope : 0.9998486027987897 r value : 0.9999366193215327 r^2 : 0.9998732426601757 p value : 0.0 std error : 0.00024888316116948144
============================================================
Running bootstrap resampling 1000 samples for 95.0% CI
============================================================
|████████████████████████████████████████████████████████████████████████████████████████████████████| 100.0%
============================================================
Completed bootstrap resampling
============================================================
============================================================
Completed all analysis
------------------------
It is in your moments of decision that your destiny is
shaped. Tony Robbins
============================================================
# MC Shapley using the surrogate model with the correlated distribution
mc_surrogate = model.get_mc_shapley(
joint=joint,
N=10000,
method='exhaustive',
B=200,
alpha=0.05,
random_state=42,
progress=False,
k_max=None
)
mc_surrogate
| variable | effect | shapley_value | sobol_first | sobol_total | total_variance | lower | upper | sobol_first_lower | sobol_first_upper | sobol_total_lower | sobol_total_upper | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | FX | 0.110962 | 8.878575e-06 | 0.112668 | 0.107799 | 0.00008 | 0.101907 | 0.122367 | 0.093275 | 0.132230 | 0.049837 | 0.156637 |
| 1 | FY | -0.006347 | -5.078927e-07 | -0.000076 | -0.049639 | 0.00008 | -0.017907 | 0.006894 | -0.019465 | 0.021472 | -0.120040 | 0.010283 |
| 2 | E | 0.082529 | 6.603539e-06 | 0.072393 | 0.092801 | 0.00008 | 0.072452 | 0.091342 | 0.050069 | 0.095996 | 0.038219 | 0.142334 |
| 3 | lX | 0.211365 | 1.691226e-05 | 0.207516 | 0.131404 | 0.00008 | 0.201356 | 0.221009 | 0.176207 | 0.243587 | 0.074961 | 0.179069 |
| 4 | lY | 0.359677 | 2.877946e-05 | 0.617119 | 0.028502 | 0.00008 | 0.346509 | 0.370035 | 0.577686 | 0.661258 | -0.044233 | 0.086221 |
| 5 | L | 0.241815 | 1.934874e-05 | 0.242739 | 0.158394 | 0.00008 | 0.230777 | 0.251768 | 0.217016 | 0.264077 | 0.105132 | 0.215704 |
Target Shapley via RS-HDMR Surrogate¶
The RS-HDMR surrogate can also estimate target (reliability-oriented)
Shapley effects on the binary failure indicator $\mathbf{1}_{D>t}$.
Instead of the analytical deflection formula, we use model.predict()
to evaluate the surrogate at each Monte Carlo sample and threshold
the predicted deflection at $t = 0.066$ m.
This tests whether the surrogate can capture not just the bulk sensitivity structure (variance-based Shapley on $D$) but also the tail behaviour relevant to reliability analysis — a much more stringent test of surrogate fidelity.
We use the same $N = 100{,}000$ samples and $B = 200$ bootstrap replications as the analytical target Shapley estimation for a fair comparison.
# --- Surrogate-based failure indicator ---
def failure_indicator_surrogate(x):
"""Binary failure via RS-HDMR surrogate model."""
D = model.predict(x)
return 1.0 if D > t else 0.0
def failure_indicator_surrogate_batch(X):
"""Vectorised failure indicator via surrogate."""
D = model.predict(X)
return (D > t).astype(float)
# Target Shapley effects via the RS-HDMR surrogate model
# Using the same N and B as the analytical target Shapley for fair comparison
eff_t_s, sh_t_s, var_t_s, lower_t_s, upper_t_s = shapley_effects(
failure_indicator_surrogate,
joint,
N=100000,
method='exhaustive',
B=200,
alpha=0.05,
predict_batch=failure_indicator_surrogate_batch,
random_state=42,
progress=True,
)
results_target_surrogate = pd.DataFrame({
'Variable': labels,
'Target Shapley': eff_t_s,
'Lower': lower_t_s,
'Upper': upper_t_s,
})
results_target_surrogate
MC Shapley: 100%|██████████| 12500000/12500000 [03:04<00:00, 67683.58evals/s]
| Variable | Target Shapley | Lower | Upper | |
|---|---|---|---|---|
| 0 | FX | 0.143856 | 0.134221 | 0.154010 |
| 1 | FY | 0.008878 | -0.003017 | 0.020986 |
| 2 | E | 0.101824 | 0.091967 | 0.110492 |
| 3 | lX | 0.274690 | 0.266192 | 0.284683 |
| 4 | lY | 0.262888 | 0.253485 | 0.272981 |
| 5 | L | 0.207864 | 0.199338 | 0.215929 |
Comparison: All Five Estimates¶
- Variance-based Shapley on $D$ — analytical function
- Variance-based Shapley on $D$ — RS-HDMR surrogate
- Target Shapley on $\mathbf{1}_{D>t}$ — analytical function
- Target Shapley on $\mathbf{1}_{D>t}$ — RS-HDMR surrogate
- Target Shapley reference — Demange-Chryst et al. (2022) Table 2
# Reference target Shapley effects from the paper (Table 2)
ref_target_shap = np.array([0.146, 0.001, 0.103, 0.282, 0.254, 0.214])
comparison = pd.DataFrame({
'Variable': labels,
'Var.-based (D, analytical)': eff.round(4),
'Var.-based (D, surrogate)': mc_surrogate['effect'].values.round(4),
'Target (1_{D>t}, analytical)': eff_t.round(4),
'Target (surr.)': eff_t_s.round(4),
'Paper target': ref_target_shap.round(3),
})
comparison
| Variable | Var.-based (D, analytical) | Var.-based (D, surrogate) | Target (1_{D>t}, analytical) | Target (surr.) | Paper target | |
|---|---|---|---|---|---|---|
| 0 | FX | 0.1089 | 0.1110 | 0.1504 | 0.1439 | 0.146 |
| 1 | FY | -0.0073 | -0.0063 | 0.0064 | 0.0089 | 0.001 |
| 2 | E | 0.0757 | 0.0825 | 0.0933 | 0.1018 | 0.103 |
| 3 | lX | 0.2051 | 0.2114 | 0.2818 | 0.2747 | 0.282 |
| 4 | lY | 0.3637 | 0.3597 | 0.2616 | 0.2629 | 0.254 |
| 5 | L | 0.2539 | 0.2418 | 0.2066 | 0.2079 | 0.214 |
fig, ax = plt.subplots(figsize=(14, 7))
bar_width = 0.18
x = np.arange(d)
ax.bar(x - 2.0*bar_width, eff, bar_width,
yerr=[eff - lower, upper - eff],
capsize=3, color='steelblue', alpha=0.85,
label='Var.-based (D, analytical)')
ax.bar(x - 1.0*bar_width, mc_surrogate['effect'].values, bar_width,
yerr=[mc_surrogate['effect'].values - mc_surrogate['lower'].values,
mc_surrogate['upper'].values - mc_surrogate['effect'].values],
capsize=3, color='seagreen', alpha=0.85,
label='Var.-based (D, surrogate)')
ax.bar(x + 0.0*bar_width, eff_t, bar_width,
yerr=[eff_t - lower_t, upper_t - eff_t],
capsize=3, color='darkorange', alpha=0.85,
label='Target (1_{D>t}, analytical)')
ax.bar(x + 1.0*bar_width, eff_t_s, bar_width,
yerr=[eff_t_s - lower_t_s, upper_t_s - eff_t_s],
capsize=3, color='mediumseagreen', alpha=0.85,
label='Target (1_{D>t}, surrogate)')
ax.bar(x + 2.0*bar_width, ref_target_shap, bar_width,
color='crimson', alpha=0.85,
label='Target — paper reference')
ax.set_xlabel('Input Variable')
ax.set_ylabel('Shapley Effect')
ax.set_title('Cantilever Beam Shapley Effects\nAnalytical vs RS-HDMR Surrogate vs Paper Reference')
ax.set_xticks(x)
ax.set_xticklabels(labels)
ax.legend(fontsize=9)
ax.set_ylim(0, None)
plt.tight_layout()
plt.show()
Key Takeaways¶
- Gaussian copula with mixed marginals enables Shapley effect
estimation with correlated LogNormal + Normal inputs — the
GaussianCopulaMixedwrapper handles the transformation between original and latent normal space transparently. - RS-HDMR surrogate faithfully reproduces both variance-based and target Shapley effects — trained on 2,048 uniform Sobol' samples, the surrogate recovers effects nearly identical to the analytical function, despite the distribution shift from independent uniform training to correlated mixed-distribution deployment.
- Target Shapley effects are accurately captured — the surrogate target Shapley estimates (FX=0.144, lX=0.275, lY=0.263, L=0.208) are within ±0.01 of the analytical values and match the Demange-Chryst et al. (2022) reference.
- Dimensional parameters dominate — $l_X$, $l_Y$, and $L$ are the most influential inputs in all five analyses.
- Correlations match the reference — the notebook uses the same Pearson correlations (ρ=-0.55, 0.45) and marginal parameters (mean, CV) as Demange-Chryst et al. (2022).
%load_ext watermark
%watermark -n -u -v -iv -w
Last updated: Mon, 04 May 2026 Python implementation: CPython Python version : 3.12.12 IPython version : 9.13.0 matplotlib: 3.10.8 numpy : 2.3.5 pandas : 2.3.3 scipy : 1.17.1 shapleyx : 0.5.1 Watermark: 2.6.0