> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synthefy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Explainability

> Explain Nori's predictions — SHAP / Shapley values, feature interactions, partial dependence, and feature selection.

Understand *why* Nori made a prediction: which features drove it, how they
interact, and how the model responds across a feature's range. Because
`NoriRegressor` is a scikit-learn estimator, it plugs directly into
[shapiq](https://github.com/mmschlk/shapiq) — a fast SHAP implementation with
native Shapley-interaction support — and the rest of the sklearn interpretability
ecosystem. The helpers in `synthefy_nori.interpretability` are thin convenience
wrappers, so you can also use the underlying tools directly.

<Icon icon="github" /> [Runnable example](https://github.com/Synthefy/synthefy-nori/blob/main/examples/interpretability_regression.py)

## Install

```bash theme={null}
pip install "synthefy-nori[interpretability]"   # pulls in shapiq
```

Nori is **regression-only**, so every method below explains the regression
predictive mean.

## SHAP / Shapley values & interactions

`get_nori_imputation_explainer` builds a `shapiq.TabularExplainer` that removes
features by **imputation** against a background set. The training context stays
fixed across coalitions, so each coalition is a single `predict` call — the cost
is set by `budget`.

```python theme={null}
from synthefy_nori import NoriRegressor
from synthefy_nori.interpretability.shapiq import get_nori_imputation_explainer

model = NoriRegressor(model="nori-30m").fit(X_train, y_train)

# index="k-SII", max_order=2 captures pairwise interactions;
# use index="SV", max_order=1 for plain Shapley values.
explainer = get_nori_imputation_explainer(model, X_train, index="k-SII", max_order=2)
sv = explainer.explain(X_test[:1], budget=128)
print(sv)
sv.plot_waterfall()   # additive contribution waterfall
```

<Frame caption="SHAP waterfall — each feature's additive push from the baseline to one prediction (sklearn diabetes).">
  <img src="https://mintcdn.com/synthefy/TZmP-8nR-MmlZxHf/assets/nori-shap-waterfall.png?fit=max&auto=format&n=TZmP-8nR-MmlZxHf&q=85&s=10d7ac27ddc3ddf0317ef7a50f6346bb" alt="SHAP waterfall plot for a single Nori prediction" width="990" height="971" data-path="assets/nori-shap-waterfall.png" />
</Frame>

Key parameters of `get_nori_imputation_explainer`:

* **`index`** — `"SV"` for plain Shapley values, or `"k-SII"` for k-Shapley
  interactions (`max_order=1` reduces it to standard Shapley values).
* **`max_order`** — maximum interaction order (`1` = single-feature attributions,
  `2` = pairwise interactions).
* **`imputer`** — `"baseline"` (one forward per coalition; recommended) or
  `"marginal"` / `"conditional"` (multi-sample, much slower).

<Note>
  **Budget** trades accuracy for cost. Start at `128` and raise only if the
  explanation looks noisy: `<10` features → 64–128; 10–20 → 128–512;
  20+ → 512–2048.
</Note>

With `index="k-SII", max_order=2` the explainer also surfaces **pairwise
interactions**, which you can view as a network:

```python theme={null}
kv = explainer.explain(X_test[:1], budget=256)
kv.plot_network()   # interaction graph
```

<Frame caption="k-SII interaction network — node = a feature's main effect, edge = the strength/sign of each pairwise interaction.">
  <img src="https://mintcdn.com/synthefy/TZmP-8nR-MmlZxHf/assets/nori-shap-interactions.png?fit=max&auto=format&n=TZmP-8nR-MmlZxHf&q=85&s=80ec052805cbd81cba982bec0d65b64d" alt="SHAP k-SII interaction network" width="797" height="750" data-path="assets/nori-shap-interactions.png" />
</Frame>

## Partial dependence / ICE

A global view of how the prediction shifts across a feature's range:

```python theme={null}
from synthefy_nori.interpretability.pdp import partial_dependence_plots

# 1-D effects for features 0 and 2; use kind="individual" or "both" for ICE.
partial_dependence_plots(model, X_test, features=[0, 2], kind="average")
```

Pass `(i, j)` tuples in `features` for 2-D interaction surfaces. Returns an
sklearn `PartialDependenceDisplay`.

<Frame caption="Partial dependence — average effect of bmi and bp on the predicted target (sklearn diabetes).">
  <img src="https://mintcdn.com/synthefy/TZmP-8nR-MmlZxHf/assets/nori-pdp.png?fit=max&auto=format&n=TZmP-8nR-MmlZxHf&q=85&s=33bf96452d8794114dc10b4550393549" alt="Partial dependence plots for bmi and bp" width="903" height="402" data-path="assets/nori-pdp.png" />
</Frame>

## Feature selection

Find a minimal feature subset that preserves cross-validated performance
(sequential selection):

```python theme={null}
from synthefy_nori.interpretability.feature_selection import feature_selection

res = feature_selection(model, X_train, y_train, n_features_to_select=5, cv=3)
print(res.selected_indices, res.selected_score_mean)
```

`n_features_to_select` accepts an int, a fraction, or `"auto"` (with `tol`). The
result also reports baseline-vs-selected CV scores.

<Note>
  Sequential selection re-fits Nori in-context on every CV split, so keep it to a
  few thousand samples and a modest feature count.
</Note>

## Notes

* These run on the **local Python package** (`synthefy-nori`), not the hosted API.
* TabPFN's fast Shapley path reuses a KV-cache across coalitions; Nori's public
  package runs one forward per coalition — correct and budget-controlled, just
  not cache-accelerated.
