Getting started¶
Install¶
pip install triadxai # core: numpy, pandas, scipy, lightgbm
pip install triadxai[viz] # + matplotlib waterfalls
pip install triadxai[ebm] # + InterpretML EBM adapter
First decomposition: exact mode¶
fit_lgbm_gam fits a LightGBM booster constrained so every tree splits on a
single feature — an additive model — plus K bootstrap replicas that provide
per-bin epistemic variance:
import numpy as np
from triadxai import TriadExplainer, fit_lgbm_gam
from triadxai.synthetic import make_missingness
data = make_missingness(n=5000, mechanism="MNAR", missing_effect=1.0, seed=0)
result = fit_lgbm_gam(data.X, data.y, n_bags=8, seed=0)
explainer = TriadExplainer(result)
exp = explainer.explain(data.X.head(100))
exp.channels # long frame: (instance, feature) x [I, D, M, w, oov]
exp.epistemic_score() # ES, ES_signed, KR per instance
exp.reasons(k=4) # ranked, group-tagged adverse-action reasons
exp.plot_waterfall(0) # three-channel waterfall (D hatched)
TriadExplainer infers the mode from the model it is given — an additive booster
with training data resolves to exact mode. How it works walks
the whole pipeline.
Read the result¶
| Field | Meaning |
|---|---|
channels |
long frame, one row per (instance, feature): signed I, D, M, support weight w, out-of-view flag oov |
term_channels |
the same channels per model term (exact mode; None in approximate mode) |
score, intercept |
reconciled raw score and the model intercept |
mode, approximate |
resolved mode: "exact" / "shap", and whether results are approximate |
epistemic_score() |
per-instance ES, ES_signed and knowledge ratio KR |
reasons(k=...) |
ranked ReasonCode lists — Group A (derogatory) vs Group B (insufficient information) |
plot_waterfall(i) |
one instance's decomposition as stacked I/D/M bars (triadxai[viz]) |
Thin-file rows (many missing values) show low knowledge ratio KR and
Group B ("insufficient information") reasons; rows with well-supported
derogatory signal show Group A reasons carried by the I channel.
The audit payload required by spec §4.4 is available as
explainer.shrinkage (per-term τ², σ², k) plus the per-feature w column in
the channels frame.
Approximate mode: any LightGBM booster¶
For an unconstrained (interacting) booster, TRIAD reallocates native TreeSHAP attributions instead. Provide bag replicas for the variance signal:
import lightgbm as lgb
from triadxai.bagging import fit_bagged
def fit_fn(X, y, seed):
return lgb.train(
{"objective": "binary", "verbosity": -1, "seed": seed},
lgb.Dataset(X, label=y), num_boost_round=300,
)
booster = fit_fn(data.X, data.y, 0)
bags = fit_bagged(fit_fn, data.X, data.y, n_bags=8, seed=0)
explainer = TriadExplainer(booster, bag_boosters=bags, X_ref=data.X)
exp = explainer.explain(data.X.head(100))
exp.approximate # True — waterfalls carry an "approximate" badge
See approximate mode for what "approximate" gives up relative to exact mode.
EBM (optional extra)¶
from interpret.glassbox import ExplainableBoostingClassifier
ebm = ExplainableBoostingClassifier(interactions=2, outer_bags=8)
ebm.fit(X_train, y_train)
exp = TriadExplainer(ebm).explain(X_test) # exact mode; outer-bag SDs as v
Where next¶
- How it works — the pipeline from bin tensors to reconciled channels.
- Concepts — one page per stage: channels, shrinkage, epistemic variance, approximate mode, reason codes, adapters.
- Synthetic credit walkthrough — the runnable end-to-end notebook.
- API reference.