• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mmschlk / shapiq / 18499875864

14 Oct 2025 02:26PM UTC coverage: 92.799% (-0.7%) from 93.522%
18499875864

push

github

web-flow
Enhance type safety and fix bugs across the codebase (#430)

* First Pyright cleanup

* TypeChecked game

* fixed introduced bugs in game and interaction_values

* Pyright Save Sampling

* TypeSafe Approximator

* Typechecked Datasets

* Explainer folder typechecked

* GameTheory Typechecked

* Imputer Typechecked

* Plot Typechecked

* Added static typechecking to pre-commit

* Refactoring

* Add pyright change to CHANGELOG

* Activate code quality show diff

* changed uv sync in pre-commit hook

* made fixtures local import

* Introduced Generic TypeVar in Approximator, reducing ignores

* Introduced Generic Types for Explainer. Approximator, Imputer and ExactComputer can either exist or not, depending on dynamic Type

* Bug fix caused through refactoring

* updated overrides

* tightened CoalitionMatrix to accept only bool arrays

* Remove Python reinstallation step in CI workflow

Removed the step to reinstall Python on Windows due to issues with tkinter. The linked GitHub issue was solved. Doing this as a first try.

* Add Python reinstallation and Tkinter installation steps

Reinstall Python and install Tkinter for Windows tests. prior commit did not help

* Fix command for installing Tkinter in workflow

* Update Windows workflow to install Tkinter via Chocolatey

* Remove Tkinter installation step from Windows workflow and adjust matplotlib usage for headless environments

* adapted some pyright types

* removed generics from explainer again

* tightened index type check

* made n_players None at assignment again

* moved comments

---------

Co-authored-by: Maximilian <maximilian.muschalik@gmail.com>

304 of 360 new or added lines in 51 files covered. (84.44%)

12 existing lines in 9 files now uncovered.

4987 of 5374 relevant lines covered (92.8%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.57
/src/shapiq/plot/upset.py
1
"""This module contains the upset plot."""
2

3
from __future__ import annotations
1✔
4

5
from typing import TYPE_CHECKING
1✔
6

7
import matplotlib.pyplot as plt
1✔
8

9
from ._config import BLUE, RED
1✔
10

11
if TYPE_CHECKING:
1✔
12
    from collections.abc import Sequence
×
13

NEW
14
    from matplotlib.figure import Figure
×
15

UNCOV
16
    from shapiq.interaction_values import InteractionValues
×
17

18

19
def upset_plot(
1✔
20
    interaction_values: InteractionValues,
21
    *,
22
    n_interactions: int = 20,
23
    feature_names: Sequence[str] | None = None,
24
    color_matrix: bool = False,
25
    all_features: bool = True,
26
    figsize: tuple[float, float] | None = None,
27
    show: bool = False,
28
) -> Figure | None:
29
    """Plots the upset plot.
30

31
    UpSet plots[1]_ can be used to visualize the interactions between features. The plot consists of
32
    two parts: the upper part shows the interaction values as bars, and the lower part shows the
33
    interactions as a matrix. Originally, the UpSet plot was introduced by Lex et al. (2014)[1]_.
34
    For a more detailed explanation about the plots, see the references or the original
35
    [documentation](https://upset.app/).
36

37
    An example of this plot is shown below.
38

39
    .. image:: /_static/images/upset_plot.png
40
        :width: 600
41
        :align: center
42

43
    Args:
44
        interaction_values: The interaction values as an ``InteractionValues`` object.
45
        feature_names: The names of the features. Defaults to ``None``. If ``None``, the features
46
            will be named with their index.
47
        n_interactions: The number of top interactions to plot. Defaults to ``20``. Note this number
48
            is completely arbitrary and can be adjusted to the user's needs.
49
        color_matrix: Whether to color the matrix (red for positive values, blue for negative) or
50
            not (black). Defaults to ``False``.
51
        all_features: Whether to plot all ``n_players`` features or only the features that are
52
            present in the top interactions. Defaults to ``True``.
53
        figsize: The size of the figure. Defaults to ``None``. If ``None``, the size will be set
54
            automatically depending on the number of features.
55
        show: Whether to show the plot. Defaults to ``False``.
56

57
    Returns:
58
        If ``show`` is ``True``, the function returns ``None``. Otherwise, it returns a tuple with
59
        the figure and the axis of the plot.
60

61
    References:
62
        .. [1] Alexander Lex, Nils Gehlenborg, Hendrik Strobelt, Romain Vuillemot, Hanspeter Pfister. UpSet: Visualization of Intersecting Sets IEEE Transactions on Visualization and Computer Graphics (InfoVis), 20(12): 1983--1992, doi:10.1109/TVCG.2014.2346248, 2014.
63

64
    """
65
    # prepare data ---------------------------------------------------------------------------------
66
    values = interaction_values.values
1✔
67
    values_ids: dict[int, tuple[int, ...]] = {
1✔
68
        v: k for k, v in interaction_values.interaction_lookup.items()
69
    }
70
    values_abs = abs(values)
1✔
71
    idx = values_abs.argsort()[::-1]
1✔
72
    idx = idx[:n_interactions] if n_interactions > 0 else idx
1✔
73
    values = values[idx]
1✔
74
    interactions: list[tuple[int, ...]] = [values_ids[i] for i in idx]
1✔
75

76
    # prepare feature names ------------------------------------------------------------------------
77
    if all_features:
1✔
78
        features = set(range(interaction_values.n_players))
1✔
79
    else:
80
        features = {feature for interaction in interactions for feature in interaction}
1✔
81
    n_features = len(features)
1✔
82
    feature_pos = {feature: n_features - 1 - i for i, feature in enumerate(features)}
1✔
83
    if feature_names is None:
1✔
84
        feature_names = [f"Feature {feature}" for feature in features]
1✔
85
    else:
86
        feature_names = [feature_names[feature] for feature in features]
1✔
87

88
    # create figure --------------------------------------------------------------------------------
89
    height_upper, height_lower = 5, n_features * 0.75
1✔
90
    height = height_upper + height_lower
1✔
91
    ratio = [height_upper, height_lower]
1✔
92
    if figsize is None:
1✔
93
        figsize = (10, height)
1✔
94
    else:
95
        if figsize[1] is None:
×
96
            figsize = (figsize[0], height)
×
97
        if figsize[0] is None:
×
98
            figsize = (10, figsize[1])
×
99

100
    fig, ax = plt.subplots(2, 1, figsize=figsize, gridspec_kw={"height_ratios": ratio}, sharex=True)
1✔
101

102
    # plot lower part of the upset plot
103
    for x_pos, interaction in enumerate(interactions):
1✔
104
        color = RED.hex if values[x_pos] >= 0 else BLUE.hex
1✔
105

106
        # plot upper part
107
        bar = ax[0].bar(x_pos, values[x_pos], color=color)
1✔
108
        label = [f"{values[x_pos]:.2f}"]
1✔
109
        ax[0].bar_label(bar, label, label_type="edge", color="black", fontsize=12, padding=3)
1✔
110

111
        # plot lower part
112
        # plot the matrix in the background
113
        ax[1].plot(
1✔
114
            [x_pos for _ in range(n_features)],
115
            list(range(n_features)),
116
            color="lightgray",
117
            marker="o",
118
            markersize=15,
119
            linewidth=0,
120
        )
121
        # add the interaction to the matrix
122
        y_pos = [feature_pos[feature] for feature in interaction]
1✔
123
        ax[1].plot(
1✔
124
            [x_pos for _ in range(len(interaction))],
125
            y_pos,
126
            color="black" if not color_matrix else color,
127
            marker="o",
128
            markersize=15,
129
            linewidth=1.5,
130
        )
131

132
    # beautify upper plot --------------------------------------------------------------------------
133
    min_max = (min(values), max(values))
1✔
134
    delta = (min_max[1] - min_max[0]) * 0.1
1✔
135
    ax[0].set_ylim(min_max[0] - delta, min_max[1] + delta)
1✔
136
    ax[0].set_ylabel("Interaction Value")
1✔
137
    ax[0].spines["top"].set_visible(False)
1✔
138
    ax[0].spines["right"].set_visible(False)
1✔
139
    ax[0].spines["bottom"].set_visible(False)
1✔
140
    ax[0].axhline(0, color="black", linewidth=0.5)  # add line at 0
1✔
141

142
    # beautify lower plot --------------------------------------------------------------------------
143
    ax[1].set_ylim(-1, n_features)
1✔
144
    ax[1].yaxis.set_ticks(range(n_features))
1✔
145
    ax[1].set_yticklabels(reversed(feature_names))
1✔
146
    ax[1].tick_params(axis="y", length=0)  # remove y-ticks
1✔
147
    ax[1].set_xticks([])  # remove x-axis
1✔
148
    ax[1].spines["top"].set_visible(False)
1✔
149
    ax[1].spines["right"].set_visible(False)
1✔
150
    ax[1].spines["bottom"].set_visible(False)
1✔
151
    ax[1].spines["left"].set_visible(False)
1✔
152
    # background shading
153
    for i in range(n_features):
1✔
154
        if i % 2 == 0:
1✔
155
            ax[1].axhspan(i - 0.5, i + 0.5, color="lightgray", alpha=0.25, zorder=0, lw=0)
1✔
156

157
    # adjust whitespace
158
    plt.subplots_adjust(hspace=0.0)
1✔
159

160
    if not show:
1✔
161
        return fig
1✔
162
    plt.show()
×
163
    return None
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc