Skip to content

decompose_shap

Approximate-mode channel decomposition (approximate TRIAD, spec section 12).

Per-feature SHAP attributions phi from the deployed model are reallocated into I/D/M with the same routing as exact mode: the support weight uses the per-instance variance of phi across bag replicas instead of per-bin sampling variance. D is the residual phi - I - M, so C1 holds exactly.

ShapChannels dataclass

ShapChannels(
    I: ndarray, D: ndarray, M: ndarray, w: ndarray
)

Dense (n_samples, n_features) channel matrices.

decompose_shap

decompose_shap(
    data: ShapData, tau2: ndarray
) -> ShapChannels

Reallocate deployed-model attributions into I/D/M channels.

Parameters:

Name Type Description Default
data ShapData

Approximate-mode contract (attributions, replica variance, masks).

required
tau2 ndarray

Per-feature signal variance, shape (n_features,), from :func:triadxai.shrinkage.estimate_tau2_shap.

required
Source code in src/triadxai/decompose_shap.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def decompose_shap(data: ShapData, tau2: np.ndarray) -> ShapChannels:
    """Reallocate deployed-model attributions into I/D/M channels.

    Parameters
    ----------
    data
        Approximate-mode contract (attributions, replica variance, masks).
    tau2
        Per-feature signal variance, shape (n_features,), from
        :func:`triadxai.shrinkage.estimate_tau2_shap`.
    """
    if tau2.shape != (data.n_features,):
        raise ValueError(f"tau2 must have shape ({data.n_features},); got {tau2.shape}")
    denom = tau2[None, :] + data.v
    w = np.divide(
        np.broadcast_to(tau2[None, :], denom.shape),
        denom,
        out=np.zeros_like(denom),
        where=denom > 0,
    )
    supported = w * data.phi
    I = np.where(data.missing, 0.0, supported)  # noqa: E741
    M = np.where(data.missing, supported, 0.0)
    D = data.phi - I - M
    return ShapChannels(I=I, D=D, M=M, w=w)