Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Development

- adds the `LinearGaussianSCM` and `ConfoundedChainSCM` synthetic games in `shapiq_games.synthetic`, which evaluate the marginal, the conditional, and the causal (Heskes et al., 2020) value function in closed form on one and the same linear-Gaussian data-generating process. The model is linear and the features are Gaussian, so every conditional expectation involved is itself linear: the games are exact references for benchmarking approximators and for comparing the three semantics against each other, with no sampling and no random seed. [#600](https://github.com/mmschlk/shapiq/pull/600)
- fixes the `"CHII"` estimates of `SHAPIQ` and `SVARMIQ`, which were wrong even with a budget of all `2**n` coalitions: the Monte Carlo approximators weighted the discrete derivatives with `s / t`, the coefficient of the Moebius representation of the chaining interaction index, instead of its cardinal-index weight `s * (s + t - 1)! * (n - s - t)! / n!`. [#643](https://github.com/mmschlk/shapiq/pull/643)
- adds GraphSHAP-IQ in `shapiq.graph` for exact Shapley values and interactions of the nodes of graph neural networks (Muschalik et al., 2025). `GraphExplainer` explains `torch_geometric` models with a graph-level output, and `shapiq.Explainer` picks it for models with message passing layers. As for trees, a graph is converted into an internal representation, the `GraphModel` (edges, node features, and the radius of the receptive fields), and the new `GraphBaselineImputer`, a `BaselineImputer` whose players are the nodes, gives absent nodes the baseline node features. `GraphSHAPIQ` computes the values of any game that is local on a graph without `torch`; with `max_interaction_size`, it approximates them with fewer evaluations while keeping them efficient. `torch_geometric` graphs and models need the new extra `shapiq[graph]`. [#550](https://github.com/mmschlk/shapiq/pull/550)
- adds graph games to `shapiq_games` and `shapiq_benchmark`: `shapiq_games.GraphGame`, the `GraphComputer` of their exact values with GraphSHAP-IQ (chosen by `default_computer` for games that are local on the graph), and `GraphSetup`, which trains a GCN, GIN, or GAT on a TUDataset graph classification dataset (`load_graph_dataset`, e.g. `"MUTAG"` or `"PROTEINS"`). Their exact values are feasible for graphs far beyond the 25 players of brute force. [#550](https://github.com/mmschlk/shapiq/pull/550)
Expand Down
2 changes: 2 additions & 0 deletions docs/source/api_reference_games.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ Games whose interaction values are known in closed form.
UnanimityGame
SOUM
RandomTableGame
LinearGaussianSCM
ConfoundedChainSCM

.. _games-model-specific:

Expand Down
1 change: 1 addition & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@
"technicalities": (
"plot_imputers.py",
"plot_conditional_imputer.py",
"plot_causal_shapley_values.py",
"plot_budgets_and_seeds.py",
"plot_parallel_computation.py",
),
Expand Down
8 changes: 8 additions & 0 deletions docs/source/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,14 @@ @inproceedings{Herbinger.2022
pages = {10209--10233},
url = {https://proceedings.mlr.press/v151/herbinger22a.html}
}
@inproceedings{Heskes.2020,
title = {{Causal {Shapley} Values: Exploiting Causal Knowledge to Explain Individual Predictions of Complex Models}},
author = {Tom Heskes and Evi Sijben and Ioan Gabriel Bucur and Tom Claassen},
year = {2020},
booktitle = {Advances in Neural Information Processing Systems (NeurIPS)},
volume = {33},
pages = {4778--4789}
}
@inproceedings{Bordt.2023,
title = {{From {Shapley} values to generalized additive models and back}},
author = {Sebastian Bordt and Ulrike {von Luxburg}},
Expand Down
124 changes: 124 additions & 0 deletions examples/technicalities/plot_causal_shapley_values.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""
Causal Shapley Values: The Same Feature, +3.00 and -2.33
=========================================================

This example contrasts the three value functions a Shapley explanation can be built
on, using the :class:`~shapiq_games.ConfoundedChainSCM` game: the
**marginal** one, the **conditional** one, and the **causal** one of
Heskes et al. (2020).

All three decompose the very same prediction of the very same model at the very same
point. They disagree on the sign of one feature, because they answer three different
questions:

- **marginal**: what does knowing ``X3 = 1`` add, if I know nothing else?
- **conditional**: what does knowing ``X3 = 1`` add, given everything else I know?
- **causal**: what happens if I *set* ``X3 = 1``, following the causal structure?

The data-generating process puts an unobserved common cause behind the first two
features and makes the third a descendant of both::

X1 = U + noise
X2 = U + noise # X1 and X2 share a hidden cause
X3 = 2*X1 + X2 + noise # X3 is downstream of both
f = X1 + 2*X2 + 3*X3

Because the model is linear and the features are Gaussian, every conditional
expectation involved is linear and all three value functions are available in closed
form. Three features means eight coalitions, enumerated exactly, so the Shapley values
below carry no approximation error and no random seed.
"""

from __future__ import annotations

import matplotlib.pyplot as plt
import numpy as np

import shapiq
from shapiq_games import ConfoundedChainSCM

FEATURE_NAMES = ["X1", "X2", "X3"]
MODES = ["marginal", "conditional", "causal"]

# %%
# The Causal Chain Graph
# ----------------------
# The causal structure enters through two arguments, exactly as in the R package
# ``shapr``: a ``causal_ordering`` of feature groups and one ``confounding`` flag per
# group. Here the first two features form one confounded group and the third follows.

game = ConfoundedChainSCM(mode="causal")
print(f"causal ordering: {game.causal_ordering}")
print(f"confounding: {game.confounding}")
print(f"f(x*) = {game.coefficients @ game.x_explain:.2f}")

# %%
# Coalition Values
# ----------------
# Two sanity checks are visible directly in the table. Intervening on ``X3``, which has
# no descendants, cannot differ from perturbing it, so the marginal and the causal
# column agree on ``{X3}``. Fixing every ancestor leaves nothing for an intervention to
# cut, so the conditional and the causal column agree on ``{X1,X2}``.

games = {mode: ConfoundedChainSCM(mode=mode) for mode in MODES}
coalitions = np.array(
[[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1], [1, 1, 1]]
).astype(bool)

labels = [
"{}" if not coalition.any() else "{" + ",".join(np.array(FEATURE_NAMES)[coalition]) + "}"
for coalition in coalitions
]
values = {mode: games[mode](coalitions) for mode in MODES}

print(f"\n{'S':<12}{'marginal':>12}{'conditional':>13}{'causal':>10}")
for i, label in enumerate(labels):
row = "".join(
f"{values[mode][i]:>12.4f}" if mode != "conditional" else f"{values[mode][i]:>13.4f}"
for mode in MODES
)
print(f"{label:<12}{row}")

# %%
# Exact Shapley Values
# --------------------
# With three players all eight coalitions are enumerated, so these are exact.

attributions = {}
for mode in MODES:
exact_computer = shapiq.ExactComputer(n_players=3, game=games[mode])
sv = exact_computer(index="SV", order=1)
attributions[mode] = np.array([sv[(player,)] for player in range(3)])

print(f"\n{'':<14}{'X1':>8}{'X2':>8}{'X3':>8}{'sum':>8}")
for mode in MODES:
phi = attributions[mode]
print(f"{mode:<14}" + "".join(f"{value:>8.2f}" for value in phi) + f"{phi.sum():>8.2f}")

# %%
# Why the Third Feature Flips
# ---------------------------
# The marginal value function compares ``X3 = 1`` against its unconditional mean of
# zero, and the model weights it by 3, so it looks like the strongest positive
# contributor. The other two compare it against what its parents predict for it,
# ``2*1 + 1*1 = 3``. Observed against expected, this point's ``X3`` came in two units
# low and drags the prediction down.
#
# Conditional and causal then part ways on ``X2``. Intervening on ``X1`` says nothing
# about ``X2``, because their dependence runs through the hidden common cause and the
# intervention cuts that path. Conditioning on ``X1`` does say something. Observational
# attribution therefore lets credit leak between the two; causal attribution does not.

fig, ax = plt.subplots(figsize=(6, 4))
positions = np.arange(3)
width = 0.26
for offset, mode in zip((-width, 0.0, width), MODES, strict=True):
ax.bar(positions + offset, attributions[mode], width, label=mode)
ax.axhline(0.0, color="black", linewidth=0.8)
ax.set_xticks(positions)
ax.set_xticklabels(FEATURE_NAMES)
ax.set_ylabel("Shapley value")
ax.set_title("One prediction, three value functions")
ax.legend()
fig.tight_layout()
plt.show()
14 changes: 12 additions & 2 deletions src/shapiq_games/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
Game families:

- synthetic games with analytic values: :class:`DummyGame`, :class:`UnanimityGame`,
:class:`SOUM`, :class:`RandomTableGame`
:class:`SOUM`, :class:`RandomTableGame`, :class:`LinearGaussianSCM`,
:class:`ConfoundedChainSCM`
- model-specific games with exact explainers: :class:`PathDependentTreeGame`,
:class:`InterventionalTreeGame`, :class:`KNNGame`, :class:`WeightedKNNGame`,
:class:`ThresholdNNGame`, :class:`ProductKernelGame`, :class:`GraphGame` (whose players are
Expand All @@ -46,7 +47,14 @@
from .language import SentimentAnalysis
from .local_xai import TabularLocalExplanation
from .nn import KNNGame, ThresholdNNGame, WeightedKNNGame
from .synthetic import SOUM, DummyGame, RandomTableGame, UnanimityGame
from .synthetic import (
SOUM,
ConfoundedChainSCM,
DummyGame,
LinearGaussianSCM,
RandomTableGame,
UnanimityGame,
)
from .tree import InterventionalTreeGame, PathDependentTreeGame
from .uncertainty import TabularUncertaintyExplanation
from .unsupervised import UnsupervisedData
Expand All @@ -55,7 +63,9 @@

__all__ = [
# synthetic
"ConfoundedChainSCM",
"DummyGame",
"LinearGaussianSCM",
"RandomTableGame",
"SOUM",
"UnanimityGame",
Expand Down
10 changes: 9 additions & 1 deletion src/shapiq_games/synthetic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
"""Synthetic games with analytic or fully known values."""

from .dummy import DummyGame
from .linear_gaussian_scm import ConfoundedChainSCM, LinearGaussianSCM
from .random_table import RandomTableGame
from .soum import SOUM, UnanimityGame

__all__ = ["SOUM", "DummyGame", "RandomTableGame", "UnanimityGame"]
__all__ = [
"SOUM",
"ConfoundedChainSCM",
"DummyGame",
"LinearGaussianSCM",
"RandomTableGame",
"UnanimityGame",
]
Loading