← All posts
Machine Learning · TreeSHAP · Part 5 of 5

Use TreeExplainer and Verify the Result

We now have two expected explanations for Maya's prediction of 90. Using stored branch counts gives $(24\tfrac13,3,10\tfrac23)$; replacing hidden inputs from the ten customer rows gives $(23,3,12)$. Both start at 52. We will ask TreeExplainer to reproduce these results on the same tree.

Run the calculation on our existing tree

Save maya_tree_shap.py, tree_shap_lab.py, and requirements.txt in one directory. Run:

python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python maya_tree_shap.py

The script describes the existing seven tree nodes, their branch counts, and their predictions through SHAP's custom-tree interface. It also contains Maya's row and the ten reference rows. The tree is not refitted, so every branch still matches the preceding chapters.

Calculate the path-dependent result

The column order is A: customer group, B: plan, C: usage. Maya is [1, 1, 1]. Import the prepared tree and ask for the stored-count calculation:

import numpy as np
import shap
from maya_tree_shap import model, maya, reference, predict

path_explainer = shap.TreeExplainer(
    model,
    feature_perturbation="tree_path_dependent",
    model_output="raw",
)
path_result = path_explainer(maya, approximate=False)

print(path_result.base_values)  # [52.]
print(path_result.values)       # [[24.33333333, 3., 10.66666667]]

The contribution array has one row for Maya and three columns for A, B, and C. The baseline has one entry. For this spending tree, raw means the dollar prediction, so the baseline and contributions are also in dollars.

Change only the hidden-input rule

Keep the same tree and Maya. Supply the ten customer rows to obtain the replacement calculation from the previous chapter:

reference_explainer = shap.TreeExplainer(
    model,
    data=reference,
    feature_perturbation="interventional",
    model_output="raw",
)
reference_result = reference_explainer(maya, approximate=False)

print(reference_result.base_values)  # [52.]
print(reference_result.values)       # [[23., 3., 12.]]

The TreeExplainer settings explicitly select the two averaging rules. We use approximate=False because the target is the full Shapley calculation derived in the articles.

Check the baseline and the total

With nothing revealed, the expected baseline is:

$$\begin{aligned} \phi_0&=\frac3{10}(10)+\frac1{10}(30)\\ &\quad+\frac2{10}(50)+\frac4{10}(90)\\ &=52. \end{aligned}$$

With every input revealed, the tree follows Returning → High and predicts 90. Each result must connect those endpoints:

$$\phi_0+\phi_A+\phi_B+\phi_C=90.$$
for result in (path_result, reference_result):
    np.testing.assert_allclose(result.base_values, [52.])
    np.testing.assert_allclose(
        result.base_values + result.values.sum(axis=1),
        predict(maya),
        atol=2e-6, rtol=0,
    )

The sum is over the three feature columns. Keep each result with its own baseline; changing the reference population can change both.

Check each feature against the eight group values

A correct total does not prove a correct allocation. Giving all 38 to A would also reconstruct 90. To verify TreeSHAP, calculate the eight group values independently and apply the Shapley weights.

For any feature j, the groups of the other two features have weights 1/3 for neither, 1/6 for each single feature, and 1/3 for both. Multiply each weight by $v(S\cup\{j\})-v(S)$ and add. The expected results are:

TermStored countsReplacement
Baseline5252
A24⅓23
B33
C10⅔12
Total9090
from maya_tree_shap import exact_values

for rule, result in [
    ("path", path_result),
    ("replacement", reference_result),
]:
    baseline, expected, groups = exact_values(maya[0], rule)
    np.testing.assert_allclose(
        result.base_values[0], float(baseline), atol=1e-12,
    )
    np.testing.assert_allclose(
        result.values[0], np.asarray(expected, dtype=float),
        atol=2e-6, rtol=0,
    )

The independent calculation follows branches or replaces columns directly for each known group. It does not use TreeExplainer's output to construct the expected values. The downloadable script repeats the check for all eight binary input rows and verifies that the loaded tree predicts the same outputs as our original tree.

B's contribution of 3 is a useful check: plan is absent from Maya's final path, but it changes predictions at other leaves while her customer group is hidden. A calculation that inspects only Returning → High would miss that contribution.

Read the result in the terms we calculated

Under the stored-count rule, the tree's starting average is 52. Maya's Returning status receives 24⅓ of the increase, her Premium plan receives 3, and her High usage receives 10⅔. Those three contributions account for the increase of 38 to the prediction of 90.

Under replacement from our ten customers, the same increase is divided as 23, 3, and 12. These are the two Shapley averages whose group values we defined. TreeSHAP computes them efficiently, and the checks above verify the individual values as well as their sum.

The recorded results contain the group values, library outputs, numerical differences, and tested package versions.