shap-svg 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -13
- package/dist/react.cjs +498 -493
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +43 -16
- package/dist/react.d.ts +43 -16
- package/dist/react.js +252 -244
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,23 +26,107 @@ affiliated with the SHAP authors.
|
|
|
26
26
|
|
|
27
27
|
| Component | SHAP counterpart | Shows |
|
|
28
28
|
| --- | --- | --- |
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
29
|
+
| `Plots.bar` | `shap.plots.bar` | mean(\|SHAP value\|) per feature across samples |
|
|
30
|
+
| `Plots.beeswarm` | `shap.plots.beeswarm` | one dot per sample per feature, coloured by feature value |
|
|
31
|
+
| `Plots.heatmap` | `shap.plots.heatmap` | samples × features coloured by SHAP value, with the f(x) line above |
|
|
32
|
+
| `Plots.waterfall` | `shap.plots.waterfall` | how one sample's prediction is built from E[f(X)] to f(x) |
|
|
33
33
|
|
|
34
34
|
Every chart is a pure component: all state that changes what is drawn arrives through props, so the
|
|
35
35
|
host application owns its own controls. The only internal state is hover highlighting.
|
|
36
36
|
|
|
37
37
|
## Usage
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
`shap-svg` draws SHAP values; it does not compute them. The values come from
|
|
40
|
+
[`shap`](https://github.com/shap/shap) in Python, travel through your server as JSON, and are handed to
|
|
41
|
+
a component in the browser:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
Python: shap computes the values → server returns them as JSON → browser fetches → <Plots.waterfall explanation={…} />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 1. Compute SHAP values in Python and serialise them
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import numpy as np
|
|
51
|
+
import shap
|
|
52
|
+
|
|
53
|
+
explainer = shap.TreeExplainer(model) # any shap explainer works
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def to_payload(explanation: shap.Explanation, sample_ids=None) -> dict:
|
|
57
|
+
"""Serialise a shap.Explanation into the JSON shap-svg reads."""
|
|
58
|
+
payload = {
|
|
59
|
+
"contract_version": 1,
|
|
60
|
+
"values": np.asarray(explanation.values, dtype=float).tolist(),
|
|
61
|
+
"base_values": np.asarray(explanation.base_values, dtype=float).tolist(),
|
|
62
|
+
"data": np.asarray(explanation.data, dtype=float).tolist(),
|
|
63
|
+
"feature_names": [str(name) for name in explanation.feature_names],
|
|
64
|
+
}
|
|
65
|
+
if sample_ids is not None:
|
|
66
|
+
payload["sample_ids"] = [str(sample_id) for sample_id in sample_ids]
|
|
67
|
+
return payload
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- **Pass the explanation as it comes.** A scikit-learn binary classifier gives `values` shaped
|
|
71
|
+
`(samples, features, 2)` and `base_values` shaped `(samples, 2)`; `shap-svg` reads class 1 by default
|
|
72
|
+
(`classIndex` on every component). There is no need to pick a class in Python.
|
|
73
|
+
- **Send only finite numbers.** Fill or drop missing feature values first. Flask's `jsonify` writes
|
|
74
|
+
`NaN` unquoted, which is not valid JSON, and `parseExplanation` rejects it; serialising with
|
|
75
|
+
`json.dumps(payload, allow_nan=False)` makes the mistake fail on the server instead.
|
|
76
|
+
- **`sample_ids` are optional** but give each Sample a stable key: the heatmap hands it back from
|
|
77
|
+
`onSampleClick`. Add `sample_labels` for the names a person should read.
|
|
78
|
+
|
|
79
|
+
### 2. Return the payload from your server
|
|
41
80
|
|
|
42
|
-
|
|
43
|
-
|
|
81
|
+
```python
|
|
82
|
+
from flask import Flask, jsonify
|
|
83
|
+
|
|
84
|
+
app = Flask(__name__)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.post("/api/explain")
|
|
88
|
+
def explain():
|
|
89
|
+
X = rows_to_explain() # a pandas DataFrame, e.g. built from the request body
|
|
90
|
+
explanation = explainer(X)
|
|
91
|
+
return jsonify(to_payload(explanation, sample_ids=X.index))
|
|
44
92
|
```
|
|
45
93
|
|
|
94
|
+
Any framework works — the contract is only the JSON above. Serve it gzipped if you can: the payload is
|
|
95
|
+
mostly repeated digits and compresses well.
|
|
96
|
+
|
|
97
|
+
### 3. Fetch it in the browser and hand it to a component
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
import { useEffect, useState } from "react";
|
|
101
|
+
import type { Explanation } from "shap-svg";
|
|
102
|
+
import { Plots } from "shap-svg/react";
|
|
103
|
+
|
|
104
|
+
export function ExplanationView() {
|
|
105
|
+
const [explanation, setExplanation] = useState<Explanation>();
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
fetch("/api/explain", { method: "POST" })
|
|
109
|
+
.then((response) => response.json())
|
|
110
|
+
.then(setExplanation);
|
|
111
|
+
}, []);
|
|
112
|
+
|
|
113
|
+
if (!explanation) return <p>Loading…</p>;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<>
|
|
117
|
+
<Plots.beeswarm explanation={explanation} maxDisplay={15} groupByGenus rowSort="name" />
|
|
118
|
+
<Plots.waterfall explanation={explanation} sampleIndex={0} decimals="percent" />
|
|
119
|
+
</>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The charts are named the way `shap` names them in Python: `shap.plots.bar` becomes `<Plots.bar />`.
|
|
125
|
+
`Plots` brings all four charts into your bundle, even if a page draws one — about 30 KB minified.
|
|
126
|
+
|
|
127
|
+
From here every control — how many features, grouping, sorting, precision — is a prop. Changing one
|
|
128
|
+
redraws from the payload already in memory; nothing goes back to the server.
|
|
129
|
+
|
|
46
130
|
The framework-free core — parsing, ordering, collapsing, layout and colour — has no React import and
|
|
47
131
|
can drive any renderer:
|
|
48
132
|
|
|
@@ -94,11 +178,11 @@ Per chart:
|
|
|
94
178
|
|
|
95
179
|
| Chart | Prop | Default | |
|
|
96
180
|
| --- | --- | --- | --- |
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
181
|
+
| `Plots.beeswarm`, `Plots.heatmap` | `rowSort` | `"importance"` | `"importance"`, `"name"` or `"featureValue"`; reorders the rows shown, never which rows are shown |
|
|
182
|
+
| `Plots.beeswarm` | `seed`, `dotRadius` | `0`, `3` | jitter is seeded, so a chart is identical on every render |
|
|
183
|
+
| `Plots.heatmap` | `onSampleClick` | — | called with the column's `sample_ids` entry |
|
|
184
|
+
| `Plots.waterfall` | `sampleIndex` | `0` | which sample to explain |
|
|
185
|
+
| `Plots.waterfall` | `decimals` | `2` | `2`, `3`, `4` or `"percent"`; display only |
|
|
102
186
|
|
|
103
187
|
## Faithful to SHAP where it matters
|
|
104
188
|
|