From 8bd8545809aedd168dfeef5566bacf2ca0ddc17d Mon Sep 17 00:00:00 2001 From: "Thies, Santo" Date: Sun, 29 Sep 2024 20:16:09 +0200 Subject: [PATCH 01/14] changed stacked_bar_plot to use InteractionValues. --- shapiq/interaction_values.py | 33 ++++---------------- shapiq/plot/stacked_bar.py | 45 ++++++++++++++++----------- tests/tests_plots/test_stacked_bar.py | 28 +++++++---------- 3 files changed, 44 insertions(+), 62 deletions(-) diff --git a/shapiq/interaction_values.py b/shapiq/interaction_values.py index 937c0f2a..33de3498 100644 --- a/shapiq/interaction_values.py +++ b/shapiq/interaction_values.py @@ -611,33 +611,12 @@ def plot_stacked_bar(self, **kwargs) -> tuple[plt.Figure, plt.Axes]: """ from shapiq import stacked_bar_plot - if self.max_order >= 2: - first_order_values = self.get_n_order_values(1) - second_order_values = self.get_n_order_values(2) - ret = stacked_bar_plot( - n_shapley_values_pos={ - 1: np.array([0 if x < 0 else x for x in first_order_values]), - 2: second_order_values.clip(min=0).sum(axis=0), - }, - n_shapley_values_neg={ - 1: np.array([0 if x > 0 else x for x in first_order_values]), - 2: second_order_values.clip(max=0).sum(axis=0), - }, - **kwargs, - ) - return ret - else: - first_order_values = self.get_n_order_values(1) - ret = stacked_bar_plot( - n_shapley_values_pos={ - 1: np.array([0 if x < 0 else x for x in first_order_values]), - }, - n_shapley_values_neg={ - 1: np.array([0 if x > 0 else x for x in first_order_values]), - }, - **kwargs, - ) - return ret + ret = stacked_bar_plot( + self, + **kwargs, + ) + + return ret def plot_force( self, diff --git a/shapiq/plot/stacked_bar.py b/shapiq/plot/stacked_bar.py index 343365ce..fe2e2ba3 100644 --- a/shapiq/plot/stacked_bar.py +++ b/shapiq/plot/stacked_bar.py @@ -11,10 +11,11 @@ __all__ = ["stacked_bar_plot"] +from shapiq.interaction_values import InteractionValues + def stacked_bar_plot( - n_shapley_values_pos: dict, - n_shapley_values_neg: dict, + n_shapley_interaction_values: InteractionValues, feature_names: Optional[list[Any]] = None, n_sii_max_order: Optional[int] = None, title: Optional[str] = None, @@ -35,8 +36,7 @@ def stacked_bar_plot( :align: center Args: - n_shapley_values_pos (dict): The positive n-SII values. - n_shapley_values_neg (dict): The negative n-SII values. + n_shapley_interaction_values(InteractionValues): n-SII values as InteractionValues object feature_names: The feature names used for plotting. If no feature names are provided, the feature indices are used instead. Defaults to ``None``. n_sii_max_order (int): The order of the n-SII values. @@ -54,36 +54,43 @@ def stacked_bar_plot( Example: >>> import numpy as np >>> from shapiq.plot import stacked_bar_plot - >>> n_shapley_values_pos = { - ... 1: np.asarray([1, 0, 1.75]), - ... 2: np.asarray([0.25, 0.5, 0.75]), - ... 3: np.asarray([0.5, 0.25, 0.25]), - ... } - >>> n_shapley_values_neg = { - ... 1: np.asarray([0, -1.5, 0]), - ... 2: np.asarray([-0.25, -0.5, -0.75]), - ... 3: np.asarray([-0.5, -0.25, -0.25]), - ... } + >>> interaction_values = InteractionValues( + ... values=np.array([1, -1.5, 1.75, 0.25, -0.5, 0.75,0.2]), + ... index="SII", + ... min_order=1, + ... max_order=3, + ... n_players=3, + ... baseline_value=0 + ... ) >>> feature_names = ["a", "b", "c"] >>> fig, axes = stacked_bar_plot( + ... n_shapley_interaction_values=interaction_values, ... feature_names=feature_names, - ... n_shapley_values_pos=n_shapley_values_pos, - ... n_shapley_values_neg=n_shapley_values_neg, ... ) >>> plt.show() """ # sanitize inputs if n_sii_max_order is None: - n_sii_max_order = len(n_shapley_values_pos) + n_sii_max_order = n_shapley_interaction_values.max_order fig, axis = plt.subplots() # transform data to make plotting easier values_pos = np.array( - [values for order, values in n_shapley_values_pos.items() if order <= n_sii_max_order] + [ + n_shapley_interaction_values.get_n_order_values(order) + .clip(min=0) + .sum(axis=tuple(range(1, order))) + for order in range(1, n_sii_max_order + 1) + ] ) values_neg = np.array( - [values for order, values in n_shapley_values_neg.items() if order <= n_sii_max_order] + [ + n_shapley_interaction_values.get_n_order_values(order) + .clip(max=0) + .sum(axis=tuple(range(1, order))) + for order in range(1, n_sii_max_order + 1) + ] ) # get the number of features and the feature names n_features = len(values_pos[0]) diff --git a/tests/tests_plots/test_stacked_bar.py b/tests/tests_plots/test_stacked_bar.py index 3c68fc53..a1eca955 100644 --- a/tests/tests_plots/test_stacked_bar.py +++ b/tests/tests_plots/test_stacked_bar.py @@ -3,36 +3,33 @@ import matplotlib.pyplot as plt import numpy as np +from shapiq.interaction_values import InteractionValues from shapiq.plot import stacked_bar_plot def test_stacked_bar_plot(): """Tests whether the stacked bar plot can be created.""" - n_shapley_values_pos = { - 1: np.asarray([1, 0, 1.75]), - 2: np.asarray([0.25, 0.5, 0.75]), - 3: np.asarray([0.5, 0.25, 0.25]), - } - n_shapley_values_neg = { - 1: np.asarray([0, -1.5, 0]), - 2: np.asarray([-0.25, -0.5, -0.75]), - 3: np.asarray([-0.5, -0.25, -0.25]), - } + interaction_values = InteractionValues( + values=np.array([1, -1.5, 1.75, 0.25, -0.5, 0.75, 0.2]), + index="SII", + min_order=1, + max_order=3, + n_players=3, + baseline_value=0, + ) feature_names = ["a", "b", "c"] fig, axes = stacked_bar_plot( + n_shapley_interaction_values=interaction_values, feature_names=feature_names, - n_shapley_values_pos=n_shapley_values_pos, - n_shapley_values_neg=n_shapley_values_neg, ) assert isinstance(fig, plt.Figure) assert isinstance(axes, plt.Axes) plt.close() fig, axes = stacked_bar_plot( + n_shapley_interaction_values=interaction_values, feature_names=feature_names, - n_shapley_values_pos=n_shapley_values_pos, - n_shapley_values_neg=n_shapley_values_neg, n_sii_max_order=2, title="Title", xlabel="X", @@ -43,9 +40,8 @@ def test_stacked_bar_plot(): plt.close() fig, axes = stacked_bar_plot( + n_shapley_interaction_values=interaction_values, feature_names=None, - n_shapley_values_pos=n_shapley_values_pos, - n_shapley_values_neg=n_shapley_values_neg, n_sii_max_order=2, title="Title", xlabel="X", From a2bef057da4c0e8a8d80c4efa5c19d04fae01b60 Mon Sep 17 00:00:00 2001 From: "Thies, Santo" Date: Fri, 4 Oct 2024 19:52:27 +0200 Subject: [PATCH 02/14] added Interactionvalues baselinevalue initialisation test --- tests/test_base_interaction_values.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_base_interaction_values.py b/tests/test_base_interaction_values.py index e8fbd16f..2ced2e25 100644 --- a/tests/test_base_interaction_values.py +++ b/tests/test_base_interaction_values.py @@ -117,6 +117,17 @@ def test_initialization(index, n, min_order, max_order, estimation_budget, estim # test baseline value assert interaction_values.baseline_value == baseline_value + # test baseline value initialization + with pytest.raises(TypeError): + InteractionValues( + values=values, + index=index, + n_players=n, + min_order=min_order, + max_order=max_order, + interaction_lookup=interaction_lookup, + baseline_value="None", + ) # expected behavior of interactions is 0 for emptyset assert interaction_values[()] == 0 From f10c654e0717ce9d30529b439f8c8ca3d5de0a15 Mon Sep 17 00:00:00 2001 From: "Thies, Santo" Date: Fri, 4 Oct 2024 19:56:23 +0200 Subject: [PATCH 03/14] refactored stacked_bar_plot parameter names --- shapiq/plot/stacked_bar.py | 28 +++++++++++++-------------- tests/tests_plots/test_stacked_bar.py | 10 +++++----- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/shapiq/plot/stacked_bar.py b/shapiq/plot/stacked_bar.py index fe2e2ba3..374c60f5 100644 --- a/shapiq/plot/stacked_bar.py +++ b/shapiq/plot/stacked_bar.py @@ -15,9 +15,9 @@ def stacked_bar_plot( - n_shapley_interaction_values: InteractionValues, + interaction_values: InteractionValues, feature_names: Optional[list[Any]] = None, - n_sii_max_order: Optional[int] = None, + max_order: Optional[int] = None, title: Optional[str] = None, xlabel: Optional[str] = None, ylabel: Optional[str] = None, @@ -36,10 +36,10 @@ def stacked_bar_plot( :align: center Args: - n_shapley_interaction_values(InteractionValues): n-SII values as InteractionValues object + interaction_values(InteractionValues): n-SII values as InteractionValues object feature_names: The feature names used for plotting. If no feature names are provided, the feature indices are used instead. Defaults to ``None``. - n_sii_max_order (int): The order of the n-SII values. + max_order (int): The order of the n-SII values. title (str): The title of the plot. xlabel (str): The label of the x-axis. ylabel (str): The label of the y-axis. @@ -64,32 +64,32 @@ def stacked_bar_plot( ... ) >>> feature_names = ["a", "b", "c"] >>> fig, axes = stacked_bar_plot( - ... n_shapley_interaction_values=interaction_values, + ... interaction_values=interaction_values, ... feature_names=feature_names, ... ) >>> plt.show() """ # sanitize inputs - if n_sii_max_order is None: - n_sii_max_order = n_shapley_interaction_values.max_order + if max_order is None: + max_order = interaction_values.max_order fig, axis = plt.subplots() # transform data to make plotting easier values_pos = np.array( [ - n_shapley_interaction_values.get_n_order_values(order) + interaction_values.get_n_order_values(order) .clip(min=0) .sum(axis=tuple(range(1, order))) - for order in range(1, n_sii_max_order + 1) + for order in range(1, max_order + 1) ] ) values_neg = np.array( [ - n_shapley_interaction_values.get_n_order_values(order) + interaction_values.get_n_order_values(order) .clip(max=0) .sum(axis=tuple(range(1, order))) - for order in range(1, n_sii_max_order + 1) + for order in range(1, max_order + 1) ] ) # get the number of features and the feature names @@ -118,11 +118,11 @@ def stacked_bar_plot( # add a legend to the plots legend_elements = [] - for order in range(n_sii_max_order): + for order in range(max_order): legend_elements.append( Patch(facecolor=COLORS_K_SII[order], edgecolor="black", label=f"Order {order + 1}") ) - axis.legend(handles=legend_elements, loc="upper center", ncol=min(n_sii_max_order, 4)) + axis.legend(handles=legend_elements, loc="upper center", ncol=min(max_order, 4)) x_ticks_labels = [feature for feature in feature_names] # might be unnecessary axis.set_xticks(x) @@ -137,7 +137,7 @@ def stacked_bar_plot( # set title and labels if not provided ( - axis.set_title(f"n-SII values up to order ${n_sii_max_order}$") + axis.set_title(f"n-SII values up to order ${max_order}$") if title is None else axis.set_title(title) ) diff --git a/tests/tests_plots/test_stacked_bar.py b/tests/tests_plots/test_stacked_bar.py index a1eca955..bfef22c6 100644 --- a/tests/tests_plots/test_stacked_bar.py +++ b/tests/tests_plots/test_stacked_bar.py @@ -20,7 +20,7 @@ def test_stacked_bar_plot(): ) feature_names = ["a", "b", "c"] fig, axes = stacked_bar_plot( - n_shapley_interaction_values=interaction_values, + interaction_values=interaction_values, feature_names=feature_names, ) assert isinstance(fig, plt.Figure) @@ -28,9 +28,9 @@ def test_stacked_bar_plot(): plt.close() fig, axes = stacked_bar_plot( - n_shapley_interaction_values=interaction_values, + interaction_values=interaction_values, feature_names=feature_names, - n_sii_max_order=2, + max_order=2, title="Title", xlabel="X", ylabel="Y", @@ -40,9 +40,9 @@ def test_stacked_bar_plot(): plt.close() fig, axes = stacked_bar_plot( - n_shapley_interaction_values=interaction_values, + interaction_values=interaction_values, feature_names=None, - n_sii_max_order=2, + max_order=2, title="Title", xlabel="X", ylabel="Y", From ea8c24bf9584ee602daec192a74c127d056ae738 Mon Sep 17 00:00:00 2001 From: "Thies, Santo" Date: Sun, 29 Sep 2024 20:16:09 +0200 Subject: [PATCH 04/14] changed stacked_bar_plot to use InteractionValues. --- shapiq/interaction_values.py | 33 ++++---------------- shapiq/plot/stacked_bar.py | 45 ++++++++++++++++----------- tests/tests_plots/test_stacked_bar.py | 28 +++++++---------- 3 files changed, 44 insertions(+), 62 deletions(-) diff --git a/shapiq/interaction_values.py b/shapiq/interaction_values.py index 937c0f2a..33de3498 100644 --- a/shapiq/interaction_values.py +++ b/shapiq/interaction_values.py @@ -611,33 +611,12 @@ def plot_stacked_bar(self, **kwargs) -> tuple[plt.Figure, plt.Axes]: """ from shapiq import stacked_bar_plot - if self.max_order >= 2: - first_order_values = self.get_n_order_values(1) - second_order_values = self.get_n_order_values(2) - ret = stacked_bar_plot( - n_shapley_values_pos={ - 1: np.array([0 if x < 0 else x for x in first_order_values]), - 2: second_order_values.clip(min=0).sum(axis=0), - }, - n_shapley_values_neg={ - 1: np.array([0 if x > 0 else x for x in first_order_values]), - 2: second_order_values.clip(max=0).sum(axis=0), - }, - **kwargs, - ) - return ret - else: - first_order_values = self.get_n_order_values(1) - ret = stacked_bar_plot( - n_shapley_values_pos={ - 1: np.array([0 if x < 0 else x for x in first_order_values]), - }, - n_shapley_values_neg={ - 1: np.array([0 if x > 0 else x for x in first_order_values]), - }, - **kwargs, - ) - return ret + ret = stacked_bar_plot( + self, + **kwargs, + ) + + return ret def plot_force( self, diff --git a/shapiq/plot/stacked_bar.py b/shapiq/plot/stacked_bar.py index 343365ce..fe2e2ba3 100644 --- a/shapiq/plot/stacked_bar.py +++ b/shapiq/plot/stacked_bar.py @@ -11,10 +11,11 @@ __all__ = ["stacked_bar_plot"] +from shapiq.interaction_values import InteractionValues + def stacked_bar_plot( - n_shapley_values_pos: dict, - n_shapley_values_neg: dict, + n_shapley_interaction_values: InteractionValues, feature_names: Optional[list[Any]] = None, n_sii_max_order: Optional[int] = None, title: Optional[str] = None, @@ -35,8 +36,7 @@ def stacked_bar_plot( :align: center Args: - n_shapley_values_pos (dict): The positive n-SII values. - n_shapley_values_neg (dict): The negative n-SII values. + n_shapley_interaction_values(InteractionValues): n-SII values as InteractionValues object feature_names: The feature names used for plotting. If no feature names are provided, the feature indices are used instead. Defaults to ``None``. n_sii_max_order (int): The order of the n-SII values. @@ -54,36 +54,43 @@ def stacked_bar_plot( Example: >>> import numpy as np >>> from shapiq.plot import stacked_bar_plot - >>> n_shapley_values_pos = { - ... 1: np.asarray([1, 0, 1.75]), - ... 2: np.asarray([0.25, 0.5, 0.75]), - ... 3: np.asarray([0.5, 0.25, 0.25]), - ... } - >>> n_shapley_values_neg = { - ... 1: np.asarray([0, -1.5, 0]), - ... 2: np.asarray([-0.25, -0.5, -0.75]), - ... 3: np.asarray([-0.5, -0.25, -0.25]), - ... } + >>> interaction_values = InteractionValues( + ... values=np.array([1, -1.5, 1.75, 0.25, -0.5, 0.75,0.2]), + ... index="SII", + ... min_order=1, + ... max_order=3, + ... n_players=3, + ... baseline_value=0 + ... ) >>> feature_names = ["a", "b", "c"] >>> fig, axes = stacked_bar_plot( + ... n_shapley_interaction_values=interaction_values, ... feature_names=feature_names, - ... n_shapley_values_pos=n_shapley_values_pos, - ... n_shapley_values_neg=n_shapley_values_neg, ... ) >>> plt.show() """ # sanitize inputs if n_sii_max_order is None: - n_sii_max_order = len(n_shapley_values_pos) + n_sii_max_order = n_shapley_interaction_values.max_order fig, axis = plt.subplots() # transform data to make plotting easier values_pos = np.array( - [values for order, values in n_shapley_values_pos.items() if order <= n_sii_max_order] + [ + n_shapley_interaction_values.get_n_order_values(order) + .clip(min=0) + .sum(axis=tuple(range(1, order))) + for order in range(1, n_sii_max_order + 1) + ] ) values_neg = np.array( - [values for order, values in n_shapley_values_neg.items() if order <= n_sii_max_order] + [ + n_shapley_interaction_values.get_n_order_values(order) + .clip(max=0) + .sum(axis=tuple(range(1, order))) + for order in range(1, n_sii_max_order + 1) + ] ) # get the number of features and the feature names n_features = len(values_pos[0]) diff --git a/tests/tests_plots/test_stacked_bar.py b/tests/tests_plots/test_stacked_bar.py index 3c68fc53..a1eca955 100644 --- a/tests/tests_plots/test_stacked_bar.py +++ b/tests/tests_plots/test_stacked_bar.py @@ -3,36 +3,33 @@ import matplotlib.pyplot as plt import numpy as np +from shapiq.interaction_values import InteractionValues from shapiq.plot import stacked_bar_plot def test_stacked_bar_plot(): """Tests whether the stacked bar plot can be created.""" - n_shapley_values_pos = { - 1: np.asarray([1, 0, 1.75]), - 2: np.asarray([0.25, 0.5, 0.75]), - 3: np.asarray([0.5, 0.25, 0.25]), - } - n_shapley_values_neg = { - 1: np.asarray([0, -1.5, 0]), - 2: np.asarray([-0.25, -0.5, -0.75]), - 3: np.asarray([-0.5, -0.25, -0.25]), - } + interaction_values = InteractionValues( + values=np.array([1, -1.5, 1.75, 0.25, -0.5, 0.75, 0.2]), + index="SII", + min_order=1, + max_order=3, + n_players=3, + baseline_value=0, + ) feature_names = ["a", "b", "c"] fig, axes = stacked_bar_plot( + n_shapley_interaction_values=interaction_values, feature_names=feature_names, - n_shapley_values_pos=n_shapley_values_pos, - n_shapley_values_neg=n_shapley_values_neg, ) assert isinstance(fig, plt.Figure) assert isinstance(axes, plt.Axes) plt.close() fig, axes = stacked_bar_plot( + n_shapley_interaction_values=interaction_values, feature_names=feature_names, - n_shapley_values_pos=n_shapley_values_pos, - n_shapley_values_neg=n_shapley_values_neg, n_sii_max_order=2, title="Title", xlabel="X", @@ -43,9 +40,8 @@ def test_stacked_bar_plot(): plt.close() fig, axes = stacked_bar_plot( + n_shapley_interaction_values=interaction_values, feature_names=None, - n_shapley_values_pos=n_shapley_values_pos, - n_shapley_values_neg=n_shapley_values_neg, n_sii_max_order=2, title="Title", xlabel="X", From 6f01eee13e2ecc9ffe5b1bc8917ba5315c7b1464 Mon Sep 17 00:00:00 2001 From: "Thies, Santo" Date: Fri, 4 Oct 2024 19:52:27 +0200 Subject: [PATCH 05/14] added Interactionvalues baselinevalue initialisation test --- tests/test_base_interaction_values.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_base_interaction_values.py b/tests/test_base_interaction_values.py index e8fbd16f..2ced2e25 100644 --- a/tests/test_base_interaction_values.py +++ b/tests/test_base_interaction_values.py @@ -117,6 +117,17 @@ def test_initialization(index, n, min_order, max_order, estimation_budget, estim # test baseline value assert interaction_values.baseline_value == baseline_value + # test baseline value initialization + with pytest.raises(TypeError): + InteractionValues( + values=values, + index=index, + n_players=n, + min_order=min_order, + max_order=max_order, + interaction_lookup=interaction_lookup, + baseline_value="None", + ) # expected behavior of interactions is 0 for emptyset assert interaction_values[()] == 0 From 890ef20dfc6da24bcffa6f55b801ef0cfba88fe7 Mon Sep 17 00:00:00 2001 From: "Thies, Santo" Date: Fri, 4 Oct 2024 19:56:23 +0200 Subject: [PATCH 06/14] refactored stacked_bar_plot parameter names --- shapiq/plot/stacked_bar.py | 28 +++++++++++++-------------- tests/tests_plots/test_stacked_bar.py | 10 +++++----- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/shapiq/plot/stacked_bar.py b/shapiq/plot/stacked_bar.py index fe2e2ba3..374c60f5 100644 --- a/shapiq/plot/stacked_bar.py +++ b/shapiq/plot/stacked_bar.py @@ -15,9 +15,9 @@ def stacked_bar_plot( - n_shapley_interaction_values: InteractionValues, + interaction_values: InteractionValues, feature_names: Optional[list[Any]] = None, - n_sii_max_order: Optional[int] = None, + max_order: Optional[int] = None, title: Optional[str] = None, xlabel: Optional[str] = None, ylabel: Optional[str] = None, @@ -36,10 +36,10 @@ def stacked_bar_plot( :align: center Args: - n_shapley_interaction_values(InteractionValues): n-SII values as InteractionValues object + interaction_values(InteractionValues): n-SII values as InteractionValues object feature_names: The feature names used for plotting. If no feature names are provided, the feature indices are used instead. Defaults to ``None``. - n_sii_max_order (int): The order of the n-SII values. + max_order (int): The order of the n-SII values. title (str): The title of the plot. xlabel (str): The label of the x-axis. ylabel (str): The label of the y-axis. @@ -64,32 +64,32 @@ def stacked_bar_plot( ... ) >>> feature_names = ["a", "b", "c"] >>> fig, axes = stacked_bar_plot( - ... n_shapley_interaction_values=interaction_values, + ... interaction_values=interaction_values, ... feature_names=feature_names, ... ) >>> plt.show() """ # sanitize inputs - if n_sii_max_order is None: - n_sii_max_order = n_shapley_interaction_values.max_order + if max_order is None: + max_order = interaction_values.max_order fig, axis = plt.subplots() # transform data to make plotting easier values_pos = np.array( [ - n_shapley_interaction_values.get_n_order_values(order) + interaction_values.get_n_order_values(order) .clip(min=0) .sum(axis=tuple(range(1, order))) - for order in range(1, n_sii_max_order + 1) + for order in range(1, max_order + 1) ] ) values_neg = np.array( [ - n_shapley_interaction_values.get_n_order_values(order) + interaction_values.get_n_order_values(order) .clip(max=0) .sum(axis=tuple(range(1, order))) - for order in range(1, n_sii_max_order + 1) + for order in range(1, max_order + 1) ] ) # get the number of features and the feature names @@ -118,11 +118,11 @@ def stacked_bar_plot( # add a legend to the plots legend_elements = [] - for order in range(n_sii_max_order): + for order in range(max_order): legend_elements.append( Patch(facecolor=COLORS_K_SII[order], edgecolor="black", label=f"Order {order + 1}") ) - axis.legend(handles=legend_elements, loc="upper center", ncol=min(n_sii_max_order, 4)) + axis.legend(handles=legend_elements, loc="upper center", ncol=min(max_order, 4)) x_ticks_labels = [feature for feature in feature_names] # might be unnecessary axis.set_xticks(x) @@ -137,7 +137,7 @@ def stacked_bar_plot( # set title and labels if not provided ( - axis.set_title(f"n-SII values up to order ${n_sii_max_order}$") + axis.set_title(f"n-SII values up to order ${max_order}$") if title is None else axis.set_title(title) ) diff --git a/tests/tests_plots/test_stacked_bar.py b/tests/tests_plots/test_stacked_bar.py index a1eca955..bfef22c6 100644 --- a/tests/tests_plots/test_stacked_bar.py +++ b/tests/tests_plots/test_stacked_bar.py @@ -20,7 +20,7 @@ def test_stacked_bar_plot(): ) feature_names = ["a", "b", "c"] fig, axes = stacked_bar_plot( - n_shapley_interaction_values=interaction_values, + interaction_values=interaction_values, feature_names=feature_names, ) assert isinstance(fig, plt.Figure) @@ -28,9 +28,9 @@ def test_stacked_bar_plot(): plt.close() fig, axes = stacked_bar_plot( - n_shapley_interaction_values=interaction_values, + interaction_values=interaction_values, feature_names=feature_names, - n_sii_max_order=2, + max_order=2, title="Title", xlabel="X", ylabel="Y", @@ -40,9 +40,9 @@ def test_stacked_bar_plot(): plt.close() fig, axes = stacked_bar_plot( - n_shapley_interaction_values=interaction_values, + interaction_values=interaction_values, feature_names=None, - n_sii_max_order=2, + max_order=2, title="Title", xlabel="X", ylabel="Y", From 70af585ba09100af94799cf9f3cf2d5bc9d3c10f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:23:20 +0200 Subject: [PATCH 07/14] Bump torchvision from 0.19.0 to 0.19.1 (#236) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d974eeee..97ce1339 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ scipy==1.14.1 shap==0.46.0 tqdm==4.66.5 torch==2.4.0 -torchvision==0.19.0 +torchvision==0.19.1 transformers==4.44.2 xgboost==2.1.1 numpy==1.26.4 From a440b030775b5b38c466b644800b5c4eceef30bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 19:25:49 +0200 Subject: [PATCH 08/14] Bump torch from 2.4.0 to 2.5.0 (#245) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 97ce1339..5dd354cc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ scikit-learn==1.5.2 scipy==1.14.1 shap==0.46.0 tqdm==4.66.5 -torch==2.4.0 +torch==2.5.0 torchvision==0.19.1 transformers==4.44.2 xgboost==2.1.1 From 59214014d4bf9e41f9fdd34026126e18f579eaac Mon Sep 17 00:00:00 2001 From: Maximilian Date: Thu, 17 Oct 2024 19:54:18 +0200 Subject: [PATCH 09/14] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5dd354cc..96b61db2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ scikit-learn==1.5.2 scipy==1.14.1 shap==0.46.0 tqdm==4.66.5 -torch==2.5.0 +torch==2.4.1 torchvision==0.19.1 transformers==4.44.2 xgboost==2.1.1 From 11958f281816072b6383fdc262e45baf5b1db041 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:38:29 +0200 Subject: [PATCH 10/14] Bump transformers from 4.44.2 to 4.45.2 (#247) Bumps [transformers](https://github.com/huggingface/transformers) from 4.44.2 to 4.45.2. - [Release notes](https://github.com/huggingface/transformers/releases) - [Commits](https://github.com/huggingface/transformers/compare/v4.44.2...v4.45.2) --- updated-dependencies: - dependency-name: transformers dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 96b61db2..ab1ce95e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ shap==0.46.0 tqdm==4.66.5 torch==2.4.1 torchvision==0.19.1 -transformers==4.44.2 +transformers==4.45.2 xgboost==2.1.1 numpy==1.26.4 requests==2.32.3 From 42796578f34bafa190a4ea73fecc9cc831b516b4 Mon Sep 17 00:00:00 2001 From: Maximilian Date: Fri, 18 Oct 2024 14:44:51 +0200 Subject: [PATCH 11/14] Update dependabot.yml --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3f2c99bf..fc91699a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,6 +10,10 @@ updates: schedule: interval: "monthly" open-pull-requests-limit: 12 + groups: + pip-all-updates: + patterns: + - "*" - package-ecosystem: "github-actions" directory: "/" schedule: From 5da33bc73b6475aaa94fa6c0191ad57ef20de0a0 Mon Sep 17 00:00:00 2001 From: Theo <49311372+Advueu963@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:03:50 +0200 Subject: [PATCH 12/14] 191 add example notebook for the core (#229) * Notebook init. * Made proposal for core page * Updated Documentation to be justified. * Added core notebook to tutorial section * Core notebook figures * Added image centering --------- Co-authored-by: Maximilian --- docs/source/_static/css/custom.css | 10 + docs/source/conf.py | 3 + docs/source/index.rst | 1 + .../notebooks/_static/elc_paper_game.pdf | Bin 0 -> 15668 bytes docs/source/notebooks/_static/paper_game.pdf | Bin 0 -> 13481 bytes docs/source/notebooks/core.ipynb | 308 ++++++++++++++++++ 6 files changed, 322 insertions(+) create mode 100644 docs/source/_static/css/custom.css create mode 100644 docs/source/notebooks/_static/elc_paper_game.pdf create mode 100644 docs/source/notebooks/_static/paper_game.pdf create mode 100644 docs/source/notebooks/core.ipynb diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css new file mode 100644 index 00000000..2292c8bb --- /dev/null +++ b/docs/source/_static/css/custom.css @@ -0,0 +1,10 @@ +p { + text-align: justify; +} +img { + display: block; + max-width: 100%; + height: auto; + margin: auto; + float: none!important; +} diff --git a/docs/source/conf.py b/docs/source/conf.py index 17d2751a..10435ff4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -64,6 +64,9 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "furo" html_static_path = ["_static"] +html_css_files = [ + "css/custom.css", +] html_favicon = "_static/shapiq.ico" pygments_dark_style = "monokai" html_theme_options = { diff --git a/docs/source/index.rst b/docs/source/index.rst index 7732cc9c..68ade3ad 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -37,6 +37,7 @@ Contents notebooks/conditional_imputer notebooks/parallel_computation notebooks/benchmark_approximators + notebooks/core .. toctree:: :maxdepth: 2 diff --git a/docs/source/notebooks/_static/elc_paper_game.pdf b/docs/source/notebooks/_static/elc_paper_game.pdf new file mode 100644 index 0000000000000000000000000000000000000000..39d710bf54b92589bbee73feed64829506af2155 GIT binary patch literal 15668 zcmb_@2RM~)_`eW2iR^}P?94MAj+H&K5825&Mw!PU(J({EUZq0FOd=I3dkZO2sHBYS zt;GL*PxY;@{C?lA-}Tq^^gQQ&pK-7Exj)Z%`Sn%Q#1P_W2!GKaw6GF_fWo2fwx=Mn zvQU`OSr0rErffsBadCHq!t`w%@m^3QPyq*(mxtip?7@mszco;EcOybkqzW)oJ=;@w zJ0eu_`%hJ0qPii`hKPrvzZ3Lrh(tWW4JrZtguo1)Z0w!g9HE#WPYLdJhIk^>0vJ|B z4PXWDON7EST>%!9e&otOa!u%o-`GL?DFDbf$UOlMaQB^k7!L2{?oF@*D4?Ym6#KoVqMMsL@CyM}`&|*>NoLlw%dPo^rP27K{={J zWvP5>tG4jH_syppV&ktQXu~fa4J)E{Ki+z;;5jUcLAAhel)vla`MBdIurpg{9}GQy z@`x2Sj5b+5^)5O^2mlgk& z!;aZ~h*#uFmmX9G$e*g4&hU8iPS1rqFuOFw1~L;IEv%J+H12+m(@5J+i~M*6Pykz29qkc~6pge4hxtM359~M_3<0_y!yM zmo7CbI3@jU;T7^}$Kdk2)W@>VSuxiz*_KpaalXqKDri8d;|&pyof#Qt)eXXY^~6Iz1o zE+^UheeDi=Z(X}P$?)uPbLyp3bZ)EklE$?6RhNhZyjMMMOZ(04g&VX9oN8F@ShKux zQ&@p<;kDGFj`laWXEasXJ(Qn@5mvP=6|c1a&ZnX4J;-Q2OWv{zs!h+w=d^9qo=Gi5 zbeGKUnL$V~zESo%|Hks$iM_=Q(=h(|;myGfEz3Nud3VpR=NE@u7Yl5rbno56#-GhR zZ{?}dAD-qso%4X*|LQOTi>aa8PBn{rjCf`_a2Q+;-)9ip`Ydh_g1lB2n?~DwL%>g) zPU(uaCy%2Vvz4jeWJpsuzvla=Oe)U&21@Lub;4rDFKZ;_)Cmp5*f{Q&;|bf3Y|$O< zh_J2gNx;R1OhqLIi(UxIJE6XmUi6K*)ef0OYj@Bqs4daT*zY=ykoJM*f?G!n4KmK2 zvyo;mp5NV?Iox3-hX^Rq^4rfK^> znYo20?0w;(ec2~N(RE+M;{+eAm$8v@{e`t_0n)ozY~aE-jzx(qHdYrM6b|6`lM+z1 zIGE6VL~>qJUi4*fU9#$`UrQ5%&K|m|R^Nhm7HPV^LzU&1uP+_XTT~@KJB3<)9y_kT z6&6^INks`?EH-F{wIUp&LatMtL;I=LX5665dzxYX(M~$4^Ul$YrRm8NH}Yfh4@~gz zQC>Hz<}kQ^s+NW;$D~KdB`3Xxy#=eoDnE7cb2`d9yFTvTt`PAyKi=;m@aB}cJie4^o(8nNqTjRwqwdD?`fsR zMB~q7EL4y?Int(SPPzB{-nY!GO+|mwNJu?nT1*l7?Z7(t#Qsw=ht}yiUdGt7{mn|Z z_tQ?}zNq(h8uBts2598jAK@PLpJK;h%OpM-Nnf1zS-g8u^7X<-tz*S43dHD#g>&kf zH@|o{S)T0Tx#%-hT6l=en8|0W&pUT>4At5^@vYRuN?>wpfzWqbxWrvRubMVIR-JNw z{UH*E<@7Lq2D$x|yz#~Qt%`N#YTl2%rQT&n%%eZX2*tVvH)F2b$(3!YJoVlf)=Rk& zd*f`p=P=!7g!D?3^i&b_rTXpnu?I>cZn{Vj4f2NHuO4c=v{?JhXW3zDVyl|@)q2K} z$m_eUTrLdBgcv5mfp(}D&i59%#NDyUiQ>q6;ipIN{0 zIFO}uwRJDYs!@co2eh%tdN9W6j=#WNdXDuoD>`z!Is@E(Y1r8~M=P#Xpf3tGM zKKKmFh3w@!DKoTYhwLNCPrE!!e|Zdro#v>c!%`LFrboVbKIX;`$iAF7JQo*}apzvL z(N~Hj@uKH~CL5(D?JLlXWJlPU)oWZ&st2mKmZoNG9;{dUzdFBhZy+*-tD(>nUk104 zbznF$dR`vsJ;*2A;t~|x9$iOOq%4v%m=Ixwt zmo-dUL)7!yw9ujapfO=YRz76yacE&(af9sO%a}4(;qcb9a}e=|H5(p>{ly69h!oTW>)sVEhf{Z_Uf+s- zmmNTtExpOo_U(A-a<;mTa=hZ0GL<+>hR-`>+OErrDZ~q(1N7L&6Z^?LH|1KE8Yq#W zeLd>0OHdz0j%&`VUN63AQkHmPF~T>1&fp`@SOoQ%w?nQgv-0ko!>+@T7cC*g-hrFq zKG*baHb&CZr;xvPVzA1%vE|owOYEAvZ?R){q*85aOtOF*qNbyd`F#bwv}YCN9_7pk zy(0&FFEFW^P?IGth={NghM*n%iCtgG?4Gyq8`VtSu1>P&wF_L3k+2KXe}xqE1a0!KDe!yvus|I{JdVA_XTh%4+~Ir8 zW~ywEj38mNIY}ewyXdp{`NHbS`TCq1wTJe%BRew1A%YhX$h4ZJ-T{0D#(m05*xJs(m%^Ju3dW4;j{^0#+lkm{&Gk z!^PMJ(!f_ zHF7ctgcbvt;;Neg_V7_x{`Q!SS9nLw(d5+(*M-WgNviy7P_I_cRuEr3p0%BpAgC! zWs)aP1WyI$UVA=?>b)}+kTZX&=tZ{fMULgCyrOm4!y2m8+^kP~+d1xuX0yL|^XLM_ z`xDbarh3J)Q~RBVjs3J(IzDw!Y9Dl!DGIo6W>;rKGsoJ={lYxFgH5_Pe0_AVrxtzu z>>-|dTxjsL+${;q@k5MS_3N5Vl%Jl?eJN^jy|_i@;pAAkgF<%z>PVEte=AhNK+}y3 zv8QT`Wm6&NuHbB1!DQyQt^1b@U=$RuUh&P=s`1G;#LgTsIoCFcZl9Ae8*S`5_I~tn zI3!!R4&qGRMKDIu-Z-2_o`RY;bC6GCm0{J_Y4G$JMF%T}J6o!^Pr!R+$7vqPAMmuh zZFa|28_IH=(zNuk(sOk_dBB@la(5gZmrOPFI?pFj zap^d>m1{y_N!i>|VgXzcnMPf7B@6-=9Qb_OZs^Y5LRYUS_AWp6S5$Wsuor%yuO@t> z2;CHw`6b#(Ab)l%@W%4vC!1u-I!2HF)EN?7>}-2ar2gVP(dh)<6DTcZ_YEOg@^@SE z28lcaPowuSWR~6}3SPGkgSdH`(w57x7q7iAOg%r&$r!KiHKbyS;(r~Ieez2?ygt>m zrJ_XAmYmN4vhW2-23@UVuhKIwS3c~eOl*S&%1d%n zt2ke=3eA4HQt$1GuDXvY6D$Z;&kb47EIV|ySID8oC9m04*cb_sEQq@N)kw{pegX3g z8IbTH1Zl44QzVeJ@j@^qmW|1?PFzNY)s|`R;jnV9B>B>fbDulct~nRy#Cj_7pc`}E zI#xH2`EHRV=~)Wwpg(Yhy3Medzc4IHLc#>Df47{BF*jEY@<}ZjB6P`8K|VaG9q;5! z=kPJWjI~LDO5WRWEBl!b9Z4=Va|H`#W}E0^Bm##soqV{@+HFi`8PRhtevq zrYotFZ3Qfr_f7;2UbJXWBL*|uwZl|Qh*L>n%}t5Xt4?-$A!3#viHCFz55`1RIh@F7 zw?X#0S6_WpV`LNeaw_Y~f$Ge~B8RY&<0wRkWE$fkYvD5l`sL-@QqynZV_i&ay00I~ zE`xW(ZOH96^y>BskK5oqNjPoOo%V^1O|0->Fe8@$12@1n15?mW{sITor3RXOzwL5aascn`xNf^6(+`Y5gm@SjW z%T9-n_qK8Fr}bAT%O)l}zPziu`6ifF^Me?io9xDl`00`Cddnk?B@gD!KOvUy6(}@e zYS~=`QSX^mDM#26`6NBrbT}G&I!wK)y7h6RPpZNa60BBJ67p(9xYHZW>k!I%W}obD zE7BMhr<0+I`k%mcd(~*poT_{*va-C`!58(0-N*P?ZY`E>2A_ZpvM-SlaH;#Se{onY zC@xjfwptkuT|mLS-!c8_G$tr6Y&7Zod7xzPPbT9d+aa?+(@h zH>I{&M-utp{)j4r8vt(wnJvl-wcCqVE0kGToOdrOLqR-cw<$ENy}NsV8xn)S!O`g5 z_q02sGi~9~3T9fdidg+`Sj&T!&SXR+W9AP0fV*Pb_(8${VwRJO*Kz|efbyz*sQCVx zF9SCgvyW7!4qtpD$UkwJeSlT^oiGz@oD6Z0elTu;WuwOE6x=Kn%^4E+rRx{CVX?w+w7NTk^M!;sjU>_J z7G4N#SMc@XbN`Jf$FD#ypisnsKR>{8gqJ}A;m9IJCO4^KLyN})Kj zP~PTGGxKKnRLec;{M2VMLJoI76LI2hY+>A-Eem;bA8)-nt<<%4SA>5_C=mrk=V`QXiBY^J5Jn-?@HYI#I36hrB!Y1bqPhzk;ipc^_hDg)jy`)zwDx_EaVmU za`I&Tv%RO2U08jOec8=_GP_mw7*tYX{x$01=VszrbIraXN(E z+ib!JOLY{+-BhNiR%FIa* zq?$^2slZ~A3k-P;ixeuu$~D3ao> z(3?1edwid+uWJN)91XI^h!!cjXJr_JByhPUB2t$tN5aokKz8l zNWo+;ZiDrL!-dk~)(QM0O96u!Ne<`yaGDJ8;B!T-P95Z!?u05wwx+9EoT^t$(5ZK2 zG$rN&R!1tHtYSQ47TMN9&%E6dp*!DCE`O0sYx4xv4$9jJyrTZ%O#-pjKjFD=rdqHu zBWz`>AJ@jbM}fcMA-}C1FF6dch7BKFi<-L_pQ2XH~ z{82g-S9OZ+7?xeoGb(K75{!|comiRD~Hz+Pq^Qg@!~qwl6(6Zm!1n#wi#Bu zod{xdE&Ss3wP>&F*VjzCKK_%MADP0(l8b8aA6OC^O{wGz9%&)Y$4orrX5F)b32_Fy6tM0r*^El_vt!-fiS_a2>Qz83%plM;}g{%EiPnNpqy5j@n4tU8zw;P^C{0 zbLFT7uwUySAzp{^Q^p#C5d}O~CQ(;}ZV3C|^ko&DZoyax#>E+LYu`HFVA0jZ+I?=Dkr&gJqg<-fV4)g05ycv^*4vZaOOmVz;@U8vU zI`w9lInSROMC|}ok$01;lM01)9A%NGT1tAy&?%4y1DTm5Zxp;$_qlK6z=RMWlm zh!8GOxwSZMFUfC*FJui=nfb-H4s4~4@faGc;qt_7l}>~o?#g7Clgm8KXjb}o9xHk) z&Fcv@SLj1ZROT+b^P0#mHuAtsgP(k&Pa7`>N)E7YNxGW3M!L zARrw2nf%@X(L@Y}32F9>VtQZeK6C5-{KE#3K_ZELG{h|9hJucFWuN+dB$dS$-sPR2 z|KenvVjbF}wQTBpGE_b_Kh0+*Udivhjtox+CnOswBPMd+zhTlX@XOJ5+}yX;)isV1 z`pyl@^c^&~1E$9Q&4d?S%TNQ|L%C(7NCSbq))PnoUfJmV$aB_XbTc8l_g0)$)36hB0O@GDw$pP6EKOV4p9NF>PS?rHJh zX*}}9)|W6=%h~g*w$0)JtVLr_YU#-GZZnndZaMgR1|#G|cDyoDv+mY{pPNsX%dO)R z>+EDGIyj*woZo`uNzX7@^7#|vs?nFfi6|JDDQvX7^r;<2v}$;;exkba*h984%|lD7 ziCp3G--Mszn>%i zJP?E#dfO67CyQ$C1Xn0b>HDdh7y^z&K*i7)6cix|xElhALIEdxupf&$Nh!#G@5*$L zc4ffT=7GSQfuhWhJXYN8L=EQ~Cg@Vxr#&|^&^Qmpyo@-j{BGkM7qPfkhYq4 zd*@b`B}|%!u_OLNp#twY>3huy6&0v&L@TZ-?pdZSVc{`1n_c1sGYacCuK5=+0|t{< zGh>8BPII1mZ4z>V;n*8_@!LqF?;j@>%DtXF%IPD$u!FGwR|JfFpbsEm^e)Cr ztOAfv4pgLd;gkG`M@r&s8ng{&BA@$$k zST#V=)+zas>Z|5PGIxxJ#R&M8+i+RXm=$o8w{=c zm?fT9r^7>D6?w6{ylZo<*ZqTc z*k?!1r1tqVpR|o<@!AzRD3mvLTHL(!oLptW&CjwP)9l_H}*Pq~H{C z__Bj4cYuNj;AQ{Mw^}t;hLM7sR@q-SlXs1mLcYrZDTG;dw|nJ+tLZg`23Ii!y?7lI zF&-3F!9TQnwwTh?tHxCx|Ul5r(ZsTmG z)lT!^dX<9$&7dLnb)no*E{OEX4zliq;t+q~HgfupqA7tr+Y_OOOl$n6^Bq|uwQ{$X zG}c#mdRSF)!V+omLNM<{elx>i>2t{qbZ0MpbhF3s3#-Bs?((m8L*~y%d}3Z5dYN3N zj81XFUf7EgE;!85tA)AhAYq|uU7M(FfA2l zWN?;H)aj}*7YnF*V6}O;oVSuzVI`u$^D6F2=F`(2IbE=if`+gDR1taym?rf%-tf=P zyEWwnffZm!v2OV3zWw}e4+V$hEIDY>gxpZ|%2YRKMK7K33|vZv@3mCdzjA%*md(p2 zla$fRxwyqy`otSkMI)C!?;=+^Mt<3j()2vV7;$Z5D9qAIC~W*b)@XQ zZx@x@XyQ|xv_>))5-pP3@T#%(8 zR*Key4b)3Tr<}fjWh~!6#O#*u+=o}aLV*)<$XbW8$5?ow+%vjZ-G@yjlf5!Yd=wFt zv5QJMkk6bi__Ja}-lXEL>xz9mkaBa}|J)TaxyIq>!Jg)JwkWmxVtGBMlBDS2Ooly4 zo`rFNiA+?=j_M}py*Vvyeo7K`(b&wi$Mqi86zydp**V3yYI^*xaq<(dr9YsL1zjPl zdwV2m`3qIShLz+FdfNe!{mmw^YIG@hm_q5FYfVXufa^2oK)*iNWr6J6(iHqwCB;bh zT$@P^U+-U&Q$AMoG?BX>lID>hwnwFhNO*0ZM!@VzeZmfGcVE(d2CE6o; zBVvNeXhUz(GI35PxFjC&<$zJOOmLLunw9FHIbwQK1zvh^g~nY~V0TDFY5u^IF6&Dj ztB`)F=EZ&5L6V<$5OfEKfw>dj-4}uKDgD_@F2QFA@qGoVMl0}#+w%D@b=iqhw);e{wkuQhz8k)pxMHCPR{kB zj@B~jCfL9kdXbeEA$aZ&jR#-bur%ASjGEr{<}UE}HO#Kh=X>=ba!bC^ON>(NoLYCl z$eiJzuNZJuWiali7-^jLSOo2A-i-q@S@JhB)Hp@HF5zk{1!7iQ*VE^O`+^*ZOZgIRU} zQ-2c;P?&OkctAa~@*Ol8$0rKh4n6Mxix2>A{_mg~qeVx_4d9dXtSdFmTw|WdiBPYt z>458QOm=diMuCrlGUdciQ(R}^;yze0{~$m4imI8sksr>OzV%&LQBynjV20>ikf!h@ zZvDvALvdnww+8`lF#GP^OES#4ayRl8JZ7cbg@yBEmIaS@{L$bBVc6h@N{*JCzN+D@ z$R`)GcOhtYkY)$8g#rS_e@Y{P!@2Y4< zpZ2g&qx)0D_41_GjFO{g@SzPft%Z!!xJw#&)yCW}XVZ`+4BVwu7kyeWapX~-;i{XKCx!g-MGU;Qqz}L zGtY|MqY8hVmV6n)VI0AFL2*7qJ^K900V7jr&osNHM^uDqktY-1&H>Fru~X~yQOD9NYN!V?zeW_Hiz4^JmHi6STbOqYm@ea*pp z(X&|_V=rkL(fH{2ChFqX*h^Vy2FAV*loR%KBAU4u>2A~?q&fCQmicK6KNCk{%UimA znHNuT-ik`c43mG#I_k0aZCp^?O>c8 z@EcO%FHS7P3^WN8ihHWMM3zDvZojol5eXaJ>fh5wO|>slfoMLG5p@$PYEu0%uF$Fe zEea9zc6ZnXk$zXxQ3WHYrk%b-rq8_J66Y(=RGN;2zPq#zMf4j{LWARt?-2(bAN1p2 z?4LSOBA2GvT4dS4&%~zv(uUWO?r0iPyxdDp5;~@4nqm{{W_$?6cm*A*!5vy~E&Caz zB4gAYwP*~1`D+d=hIr|CbRJ^LOU(6TmyXow?kUrvh0#RL6Q1INY_KBsrDY5Sn}KuD zf`UfPH#hoauX_n^eYM7(vG#uO?`&PDGqP^H*6*f(c{%d7I{d@esFJa<^$m)X))wJA z=x_&Ih>-k?`B^7clTI1Hz^mzXYHIJDilus)?A|(|C86Nx-+t=0L5wb*tXd6k&clrm%&;Ngfgome>L7A^zsDOD?9Ct{%Qed3GJLrI7tf zDD#}&?NebhTP3_&ReSx&?V>`vyAKy!t-oY_?L*^pexuWC%#8~Ck}YZ0bUE42In?9) zY^4rZa+V*yGEzGJg`q0m;e2YAKlD%e1(o5 zLYKvRJ>2)yzssvmyv`}*$3XT&%EG$!Ete;mpn5r1`$080lfXr@+ydjxz@Fkii_ zBb}R1JEMAxIcqI1l_zqiE#MOJxIY@o`=yDeR}S`foI;N!my3A?JglgO701^^ID`k+ zI;TF|byaqka(_>rfifFs_lq3EBXDZ*W~QGA7SDcD09E|b&-X1L z(*1Cll8qPsM@h%PK*d-@1%JxM*xS&?%?q6B30_2HCmRCL7^ZFWql}c00JeXCi`Xi7 zFFS&>2hp7XMf|u({?B$2I0g{)uM+yZ+w(KQ^1dA#`kr1ZUDcaB(;sv@!7S zpAvW<9AHi#xSkGN0ViEnhrm?bfE(T5<@H~e=6{kPFcoJ92Rv}~9K3680R_)KygY2| zfNSsos~#@iUQjf!F7(3_+@UacH-K^C8K6XTBH)3t18_$j_}|$F55)kEr3i=x+{cGv zu)vs5Nnq|(hXVK1VcJkBKw0`waQ$Qml>#WnP%LmdXaj}WLSc5mRdt{$9>{>_z&(B^ zD9jo70SxN`g}FjtZotR@FHi)aQ5Z0u7cf2)<_+w?0nN?=m!*KK>M#>$d!iE{G6OvB0}L1nCZ_V@Pz>I?SiwNqMM@&9&{)}BHq;)Ea=$yk|Y8ILL-0weBkF&!2*hb z!~cII$p5t;6aW#?faTD@N<|7K1;wB+5G*j2N&?14N=iUcSU4$30DOYa!TKoR?glAG zNrLtNw*>k590k+`6WGVkB!NNz9s(*r3JoNnBdiou61b9q0UTHY7+exe5=bZ#D**u# z;GV$u5)wcsz>bb2;L(y&00V%(k#g`kaKQ=#e48N&vffjKE5!Fm{y62KfBpJX&pfdCJHLLuOo50v+MI4}gB~??loy;1{4z z{FX@m0Q$twMDmp%J^}i}?+N6K^c&~{^o^ehlpF(>0Bi{G3i`kf2`dHI1oR6a()^x& z`ozy1R0?2s2q_24za^5bzdr}-{F6Yx0Idqd*jwwiUAuuNcN+f+mqzpxX z2oDDZrf1TXIiREt1uRRVX+x3V4hcZhhXO_hO2B7NP~?v=W()-kPI_tsMUnywIC~?1 zj9~{w{ul!f==FO&Qd}S%cLE)eLIq$fQV)(`oFSE*pny=Mk~8q$d@s2G)5Q0ZD-?L| zNl)FNfFXbq1PBGdRu_o|h9lB89l)^%6u@gF>i3xSqk94v#z4aS8Ccfd&B1+mzkCfv8H-&5zsEz|{Avu`)OefWD3ZFv-Kko#^6h z3-v*XONqlpp-x1ihnF-A=K9Ygad(2F5CjImC3|l>;0w6lTY1fYJiO$Gc?@Q8{u5{^Uz6B!zTMZu9s5jav14i^;qO?AEmyaNQ71yK-SI{onnL?a2n z*&U$&&_JXH{z2V-&=6=8DDuB)C@e6WZKI*#;2ZYudT1mFgSOMa7i~LD0NQ1}aMIgW4~z)gXlOVPF}Bl?;H3BOwrDWSZKp{A;dUEM z0^slu8ZgKoG)Zu>{r4CEaQR~lAQ)`dGaQ5YQw|_V{?S$vFv#{XB+-B99u5GkKgN)h z0%y#B^CyY@v#k_hknQ!PB*1y#-)*I&z<{%jh6T{mHX4Aj|BwTV2Iu&Hx5Z-q@B`p| z1(3qO>j6s;0GVy4p#UhmohFIh&NBk|+b$adfdY=+x3xt`0BCR<4UPKUUx)-7XBRwy z^mVhLvmYLqH=r<_yE{1HkphsWn}a(5y+}WR+0%EZM@45VDP>>?Rd0)=(rnX+e}wlC((K6S762 zP)U)bLW#t8pHXkU@_lc=|L;G3w|k#+x3fL>oO_;=w3&{c5?UFDkS^|riYgFj2nG2% z??$MpK}gF3epCoacA`0X`MN_$GbeXyAcO$}D3FE*g6iW64^;VWf}XDr4Z_j~AgxWE zcT-(x5OL{IcRx+vg62e{LbxS>nG=mh4e){R@FN0g;o;=!>EjL&e)I!(Y&`z+e zjvhdTx}OFi4ZHyg+COUYkJoL> zJx?z{D}>bZ0u0chy7;Q}H{VRQ-z{ttd(~;We<-)m zi`P4q-CT7N?fB99+U4-=v9#;PYPWf716f%TJ2RZ*08ua_8I8@Whxq;p|Tr zud@kmm|r+s5yCm>^Jx|%WInKEXtv=~MUC&(&3aVPJtOfqHJG@I53Tj3$la{*Y((F( z6$9X*mPJO)I)U$ zl;_kBa$h)MyHUD_<3p?QsndK}BXVo=#X7}8-@ zEpC$xZ5#FXD!(bIZjn8nmAJn*aBXsU2Sav{sM(1aPV>^%EJ?I8bNCsaGhT8bLZhd} zA~&2F+S<;%Dac7ZiC0Wd(O~tW=&ELgFw9VlMcYY&TvGY^0>>C6Re#l)&bl@0$Mu3Q zXukA|8|KG}X!l|nZhc@Kd%~bDvu6u)z&8DErvev5M{jUS*I9~T5?ZWUmV7X3qlnFj zjSlH#YGp&NCN7XQMSU4Sw$`q|MsqduDwhls z6@JL-=)7n5v+}G%Y}{B|FBwSl=y%im2OJ(SHRMT`tD%}jMiR$AeK7lKow@dc&6VTH z>>e3i+KfW|-6vxin<&BorLsz#PguLz3f4s6Kbbk(Wg;(7O?EISzI58gP7+Ptn3DBX zN<3}SvE!Oq3)x{q=TRtDN=*K%)rI71KK8Z^C+-V(&&o4YIwFyrvEg%DXxUA*Zk7u= z1+UVFGbG)#FNUcL-+k$5ZpzxdEBndU3@&Qf)J)aFq2P4)G19R_Wo_+_%=%{x-DvjQ zgWZEVXpelIn$-dlLA`Ygdqz%w(tFyPvR$7wQ@cf@zF0am@_KLN-PHA(p-%mX@iX69 zZR7mS23os2^hX8)IPo_7lvtl=xuykNz41-xb2EC$IfiF4|N_T+{DXjX0wQWSZhvpl5arOpCs3QNPdsizNiyjMo zP~fT}F&skUGO%mLiW-iX4th=8@MTqM&!4;Cej|bjJ^X6U_?gE!+bwlZMieFos<`Vs za=#d>e2aNjHQvJia+4A7sw=jAhTjFD3*X(O(i5s@vJajLJeAzvD8Gl1!t|Pnm}eW= zJ8WcgyjZ=>H|WqEgLJV3u>*1bcHb5>c|z)Q)#SCyb1tb$=sC(K;F-xBhWosNweux3 zZW$Q)Mn9Wxw1~RFy3VG*q&m4ZI!kqLXvRv)RyV7hO+!A*jXH@U{=Ubkc;7(Fqr!(*1>>KPY1b(<@1 z!0W4=fU>CJz~O6py0Z5d0z9rH32*xHG`ne%ETYD^TYJ1o7!lF=tqh;my=v#^)pcr- zgGUe6$63EVG^-dHL#-#5_YUwfA0dlus=9kk?$#p*L|})ZXAN0y!a(8k7v^-m>9$Rr z6QVnu&y&Oo%5oga_p+5E&#o>JbtXx%68DIyJUH~pZOSQR>SFMLLhUz*Pw!pJz8`$< zKVF(2ets}m%IaKua=t@bV1SqV*>qDQeR)J<$W>LvC|j*C(HNd8tlEo-Se3rhg+WeG6M4G*FFHCQ9y=B6l`l^X@?t+!m&fAY0T<6og*d+Rd-x-SN=o&m}m3-67 z4qZk%$fP{G1|>r#wsI>7OCJxUj9e5L{k)A~^!!EQ&8+luySxSu1y9dSxJ`X)9cXQA z&$=8u=V0GPni*vr3vF4yI!eYSRqR7OIa+XN`+^?N#N>YL;cy@j}dwK526t?B? zBi(0n*XG#2Yb98~{>%@9{KK=MTW{E+qe&RTPj`oI_F>>o$o?M~?Bgsm{UkIn_y1w` z@o{%Jt5IuoLN46m`Kl5+6PwHbWx+c}Nw8o2ZS0hnqwASy$?LBKu3hl6Fs5uP*vGLu zpD3z2D&6`W*A%QQJk9O@dEJdHrX+Kz#Au!LmQ?rGyAEn__-=4sftKIhv>)CHhJgET z9Mvhe6PeH)EjJNq)w?L__ip<;&YTmgR#!!%jd)gR&G?v;M7DTKwQr!)P(`K^0 zCo6KtR$t130-OAs);Xt5-r|)KP@>8_aW9OR^yl2Y?%qc}EMJ#|K|OB(C1GQ6LP4l;_kd{M?^nY zNh;Rn4Q|zCl@O@vY7xDrn75((+3mwjFYVulTbo{18(-@=XccUWEPI)d|xa-YK~-L>g|>K zP@^ZMQGaq`(~g79W4M-&sy4$7?S?Oh@5UhVj*g9 zFzeBdepRREz?K#EA>zsDufnB6Tb}CWrEam@3Gt!LrD*0v;hC+(N<`=3yzm``GuBf) z`xIJtZVoD~nyI<|CQ-gfTWz>^L*G{09-Yol>emMY$+TuDOoJ%Fs^fXWAu6xxOWj^? z+?{elnQUQ%{)Na%gEHY$U2<*@yb2n<<*hIXVqx5|Z?YhOlb0}NQ|v%P_azz ze79`+NkKmUT4hyL0cSpTk?8B#tf2ox0xjHoX7H&X!DC1iwoQQfCKkJTXlHF^X@HUI}3CHia*=M&nVY+-PPu;GzbV__Yo;xR0nKM=F7G1I(i;g5_ z@(Mf3?+f7m{Q08F`)5ffy>>WtoDt3|Lp@BKS6^!p*bx|$I4`*?V2@Kr<{LplrK0th zsm7h-PK91d9AoiX`&@qCzKdakA6x&q1Y4QiRnU~zwQwo+6dWofN>S7kBE zc-CgH<-qBtysv?a1V+_$idVH6A2G6K?vIryG{5py+7#s=`*Gi0wfJ+5cTU=Vw9FH~ zMeu(0-MUN$k*22@)z^4WTzYu_?s*YLbwSVCIAc97DJK7_CnN2_$#T)pLvh~Q#H9_m zb6M`}tUG~Z$7~Iz)Fqvnzj}qmN-&2l(8WYb@}kv+H?zH1yZd}DQU#}2q~7w)Tngv$ zyzeqnEX|GCVETEeq2%T#+c)UX*9tZ76KXbi$zosf>oN}srbrR}1x-X7Iv-jG-sv!- z4BxvGot*42lb&2qtsrr_!L}AnHnn-iO?W?s@gX-al8N{mNO5qCWq#oC~QgL08w-G~gr;Fsu z)=AHkQ`Jg`gcr{}|28`{zrY%|>9FJq(t+>#%cMiZ{I@=0%TVShv#YC|v4L2YKuM!0 zezLM8J2qQWIi1BCif-xXSlf&tpeZOEj^&zhYkZC~DqhpZ=%g0O>^sSBy`ASO8iqG# z1$^Mx!ZLiYsJ}R1)RT;SfR!W9XhbQmt)A;UJC(PoB4hB#Gg;}k$2Rl{=g#>9Ma9+ais5wcHm0(hM zu1GO|KzTn`kIdPfUwF1uc4UinoTEayXa5Z$}=5%*SC~>^8Rdq7;RQt2yXw zA01`YkKO2!E;#Z^i|wspt%q$)>ONg2t%=F&%^_?P&jxotxW;-ZqfJ%L?dm%kMcGF% zT0-o>WWmSR=X>{Pw|~8=AU(ac_b4_%cEaQSo9+igCl?0enT-;zct}jWDDios>{w~B zS=ZThTR^er@$K7Y%=o)**WKk<``JsEEEkygcx+c)8~dI#FM<7rb1Y)#2V{)6tooi6 zZ2NFot*>K7+_r}%z5?&zK+ZDWV{m`rVdy5B1h7b~iJUo`i~LwPyhFV`k78pyQN`I> zDWzZ=^@gd7kx&04FL^vRl}{XV5IKrDcoU=gadTg$pXn_hyE3VZDg=l^V2EPEy!H_Fre zabL=wgM0Z*OG<|=TP18;FY^f>yXv)HtGh{xr=0V+Y+9g%`D~#`(Pm}GWa**l(EjXH zw}ZhH10Gbw!Qv*5hm3@d7Lku%ebm)fd3deGj!wL48cEPO7LsczUPYy zT!(rXHIA$@TCiWe0`uUo*K+1z|Kd%8y@`J;*@o=sFN-~tX8tvr(l>5NJG)3S zBGF$-G5ueY@aJU?y9ex;S2T^%uf6=jfIlq#r9)77i^zTI7A~xKWx7>c-u+~b2f74x zH6Bc_^xDm+JFEBhx;>4(C-JgOlG3V|*44v1{Yaj<+2Y3Yx8c0rMRS4QirKxtJ>}aL z6f$P;nlEM~t+<-{YF%=J^=ftV+eYX^C&#>UWOFJvos00&*d3#jzRCSXY)#Fk4zBUr z@7;&COL*81T=aG+&@*i8s^T;;Nm?_j!{&YiHzBZaq#d{|-?+C)Sn{mEuoYZx436~Q zM~}Lh>IoeQj(4OefleuTC4D*jN?6r# z?N2^K?;>iUA~FSP3+9fYjb9)n@@*D9josozbU9J2i(Y;DE$xCjs|@K|Eg~{dggS1u zRW`P8Y7fqD9(6ToE0)ac>oR?o`E`Te&0SAjx1~1H#2$Vw5>_za(?Jd$!p)b`Bby}YRdLL)-F>F!6?KX+Z6WnGB2-M9h=SGp1W z4F`cUc8m!)VzB&0J9=X10=~NNUny?BZpsfN}tFIsA`ArMbZWs<=w8^0EF&~b!oB9evu#sNKXmQ)#sb=ItgF8yW+ns;9Y zC^hx7aQn#~dD>xlI<0G;XQIJ&x&A!f1B+blGA9cTbh@0GVR!i^srDz9*x#M*rHKB| z&^*#&uQQDvI@P?4uOYv_dGzx=;lyC$rgeC%l&=@Qh#MqHPJWiqt;=gaU59v2s z=8m&+LSg5^6!~xaeercswD?s2mMde!d;j@t9K}zoAUo0FQioN(mr`P{Fxu?QhY~A+ zhg}8(5mCz1tq$rL9~X%ILeEzqX(d1SZ!WXCCma1)fSLbL$S92pMSL!9oZ^1BP;zv- zODkANo5R_(_Nv_ueOoa&b+yxhV_Nh!;hV> zoX@pGZr;fIFzHp1rsToR*BX;=+`xXPIf#!-eBfJ@mml$QVo)xe(43`+f9g)?GaoyZ zlOV6KM{M`g9g+4thR-yVFJiV0+-0(Tc{jC4{pp7-VnLfHS0Hu;5BRTqEX++a;A7z! zc#jImAl|sGM#vqt)4X>n_O`aN;8u?MW`;73(CgBt!(-e-Ga&D^4Um9NX=dzt9nsSa zi7hV-nN~?Z6KGlipA~$*zq!LK6w`C+wWLn`|AB7w8bl@OF(@IBH{@&d2o2>$`pp{B`J z*Bfv5k*rijSon1}Wi)LcEoCfHwW!bB6TADJ3w8Kbm+I;Ukv4nMC0GCRaeFQI%KI6& zr+zjcm<_T?Ii$};Mf4U2Zt!~1>|G}$tCw|of@>xgGL^N^Z+H*&ePor5Ka^Ue;CIIB zOQ#Wvi}|}YU&iop&37DO_ggKdhk3_ey$Tu$bM!sHv$b#SgG31-(eR)%c8fz#X74Wg zA60EHUw>u8hoOBLPl6hEIVY_PWH{b0S1__i*|ul>=D}oa_+zPB*$x}oB1uNCfZpSl&Sde{`uyu9t`%CTu$Biy5PH&z4vawS@mm-hP zOp-(HO_8>-7~Fg?t)A<^(bqn%1ZjC)Wb#$%nGVFKL$Po8X9gapm637j9;CzUSouN` z(JmvxDL;9>itPS6>>Q^zjmauntaK;Tk>}WR0}5#57UHPM2-6HY*^uf*XM*{6$np_aupz z3B86n@NT&5R=e<-;+FE7xy4JYPxN!&GU7H=2$(bn^LuE#E!E4Z=-4x=Xqco^BakF& zcF`q9b<%Xx1>dLT0#+F{{yVve`^x+JmN2PI!(>R>? z?oUE-zk4D-!&iTC>|hDk%}}9r{NyXRRiba0C@ZYIf+x@qnLLvEpcjDc5#33lu z=h=roXlvqp73ZZsekOk!B}LWNGN>gw%CdZ%HhVqwDKBw&A2q6;t*MCjJ>}@uf=Vli z#~(5=B{%Wz%nvB}wZnPC{!QUq>hdqLH_qNDEqVPwG(pUIon($qHKw6{R5-0|;;P%u z!)cz?3Hu{YD?CwZv}U1*45++6Hj{He>Dua;yP0Xn5TaJG0*AFeW$VWu`XXd$4RyZX zVBi-QYnB)q6!vs02Ps4`$Xt9Hy9v!V`cy=>Un@*HDTCBdL7RU5wWBn9p*N0LwORh{ zPFow7M;apUQ|>Eb-UeGw)25y@MzG^P9 zYnoYCa~{{E*$!pLor4s2RK89u@~C@`MTb9Ui9W2*<83{xX$cv)nBjARJ_S#U_4;S9 zJxqRbmBX!=dp=ICf3)ExdcFJ29%}d6ar+YWOs%G3yLxFpLF30xlI~nvGBL{61J#Mp zh@N%2(@7sIVJz-3<;zK6Vl zwHP$x4ZWs5wcKj%M!Pk!#;B$rJBc$|coSaP&d= z=#Q`n`fsAY>IpSmb*MGu53`6x&AeW@oP(wpcSlbwmPi`iVGm|>iHqv!5Gg!WchoWK zRl_4`%ROKD8#JYf4>BFO^79^v>L&&}s|eYNT^BhqR607xb0^8|P{)1k3TcnfkYWqZ z{tNzm^`dx|F}bnR=PTFVK6lBz7nd}ED@zKz#aR{7Zqc)Nv8b|TmgVj~<%Jc5xuXB| zSD%hZTuc%i<1^sXkyIRVziIBK)`hCpxK1mv8oQ&{QzSAcDai#AuPro!GnG$Q^!Gg6 zjT=e3t`r!0>qaH=a#D4yTTDcaXT~jtQ)&$4p3VYuvY=Q;cfQ4@tNqLx$8P#?54e}(_tS*JRx?26e;J#pbN-#c5}BMfi%eV*doOqHtNQQKCn;`47|bUkO7QZ`)gTRL5~ zddJ)X!#RD+v_JWS{+&NdH&%LL8B*IRkou!#Vs5TurJzII?PRsr!pSEPKI#JkX=D$l z0H88RW2YZ&3?2_|t003*hZ^V-;OR&64S>);GSL3H{la?y=Wd&mH^>vyUcU6w{QtNc z!&{g*(E>c_31rGB6l{^e-#;xlkqjVb2J+fK?ifAG4T0440jXec*4wXayPqHgQpeNH zjS6zs;3T!35F88)^mB3n`EvkOKd-%k5DwVRU}}Ibg!J_Rh|~6g7R@7o3fgWUxefg5 z8AOE$K(Vv{Ss;-QBBBvUeMki`$P9wrSqn&o03od)5(tqyK}cr^=>qcFz|l+v6*yfy zAfzW~fqlFnq&I{H$#qDui63a;5J3S4Qo{kKQ3b045wHXV36Fw^K+qT>9>S7P^nwR?!u{}gECIb#V~Ozi|EnN= z_G7_VxWH@tEO;y$Cu7zjhcBR~P_2ofvtU=a{q zA_5I2ssIE~pbgh>Klp}D0D49Q^Wil~DkM-Z!Qh%s43>z1YZwWbD7X-S!ohR#fbgX? zz*w*^y^X~IMySvWo=BqCBw#%W08T(-0D(*U;=sOmpvzbiJP!xqL9GIq1=sX~fyWW( zOn_^6f4b0M1_B%aV-fJ4crb^~l%L4KHC=z;opA623T6?EHcT2^gF=@Jyxmek-~oyB znJ|BUH2{K3OJD%Sa1V?X!2G8g{1n&E8i)+s_LyIpar6+;3T2IXE0zQy+wx5@U0KP^dWjy1ZbH-K(ch01%!cpW&pE- zfRI580gh+P53}VA0fEzdT_6nIkih3P=EolNGavI~8@d?)XCQsF2hfnEwLD>KLWg+) zU0!N=Lx3nc%on^jKU%=70M({L{UGq>FSTIX1Ec{SU>g8J4|MPk;{;nhfcTOf0Zf1%;}$uS+cZCsryJHPgmHN0Iv)z z01(nA`|bsnkxZGgwL3Ai?XUcNLhPiH6ytE{4oQiMEcG{3;jNTl~a zUCO=z?s5nuaB*Dsx`1@|-)H%`xC1ci`sl#_Ent(*W(*301BVd~ zO~QgN5DF-aED9wn_Z#c>2T_z?34&M5pJbI~X){4)J}J(3Er#Q%n&L6~Vd428jhuM7Vkhr+-dUJg_Fa~v>1 zf6#`7t;n*uSR#A|z`uWF1f&kUg5@wQ>_;yfM*vZ~WzT^a4ZnCrkzPhs;T6*tq F_#fu)`a=K! literal 0 HcmV?d00001 diff --git a/docs/source/notebooks/core.ipynb b/docs/source/notebooks/core.ipynb new file mode 100644 index 00000000..70d8ecec --- /dev/null +++ b/docs/source/notebooks/core.ipynb @@ -0,0 +1,308 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# The Core: A different view on explanation\n", + "\n", + "On this page another game theoretic concept *the core* is introduced and how it can be applied for explanation.\n", + "Mainly applied in the field of economics the core is a newly introduced concept in the field of explanation for AI researchers." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "## Example: Writing a Paper\n", + "Let us consider three AI researchers Alice, Bob and Charlie which work together on a paper.\n", + "The paper is about a new method to explain AI models and would win the best paper award at the next conference.\n", + "Winning the best paper award would grant them a \\$500 price.\n", + "Despite their long friendship they would like to have a fair share of the prize money.\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "As they are AI researchers they are familiar with the concept of *Shapley values*, being a fair way to distribute the prize money.\n", + "They have previously recorded their best paper award prizes:\n", + "\n", + "\n", + "| Authos | Award |\n", + "|-----------------|----------|\n", + "| | \\$ $0$ |\n", + "| Alice | \\$ $0$ |\n", + "| Bob | \\$ $0$ |\n", + "| Charlie | \\$ $0$ |\n", + "| Alice, Bob | \\$ $500$ |\n", + "| Alice, Charlie | \\$ $400$ |\n", + "| Bob, Charlie | \\$ $350$ |\n", + "| Alice, Bob, Charlie | \\$ $500$ |\n", + "\n", + "Based on these values they would like to distribute the prize money in a fair way via the Shapley values.\n", + "Running the calculations they get the following values:\n", + "\n", + "| Authos | Shapley Value |\n", + "|-----------------|---------------|\n", + "| Alice | \\$ $200$ |\n", + "| Bob | \\$ $175$ |\n", + "| Charlie | \\$ $125$ |\n", + "\n", + "An inherent assumption when computing the Shapley value is that Alice, Bob and Charlie really work together.\n", + "Yet it might be the case that Alice and Bob realize they would get a higher payoff if they would exclude Charlie, namely \\$250 each.\n", + "The concept of the core is now introduced to prevent such behavior." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "## The Core\n", + "The main idea of the core is to distribute the money in such a way, that there is no incentive for anybody to leave the group.\n", + "In our example the core would be the set of all possible distributions of the prize money, where Alice, Bob and Charlie will work together.\n", + "The underlying concept capturing \"leave the group\" is *stability*.\n", + "We say a payoff distribution $\\psi(N,\\nu)$ of a game $(N,\\nu)$ is stable iff.\n", + "\n", + "$$\n", + "\\forall S\\subseteq N. \\sum_{i \\in S} \\psi(S,\\nu)_i \\geq v(S)\n", + "$$\n", + "\n", + "where $\\psi(S,\\nu)_i$ is the payoff of player $i$ in the coalition $S$.\n", + "Stability induces no need to leave the group as each player gets what he would get in a smaller\n", + "group.\n", + "The core is then defined as the set of all stable payoff distributions.\n", + "\n", + "$$\n", + " \\mathcal{C} := \\{ \\psi(N,\\nu) | \\forall S\\subseteq N. \\sum_{i \\in S} \\psi(N,\\nu)_i \\geq v(S) \\land \\sum_{i \\in N} \\psi(N,\\nu)_i = \\nu(N) \\}\n", + "$$\n", + "\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "## The Core of Writing a Paper\n", + "Let us now compute the core for our example.\n", + "To do so we must find at least one payoff distribution, which is stable.\n", + "Let us abbreviate the authors payoffs as $a$,$b$ and $c$.\n", + "\n", + "$$\n", + " (I): a + b \\geq 500 \\\\\n", + " (II): a + c \\geq 400 \\\\\n", + " (III): b + c \\geq 350 \\\\\n", + " (IV): a + b + c = 500\n", + "$$\n", + "\n", + "From (I) and (IV) we get $c \\leq 0$ from which $c = 0$ must follow, due to $c \\geq \\nu(\\{c\\})=0$.\n", + "Now we can substitute $c=0$ into (II) and (III) to get $a \\geq 400$ and $b \\geq 350$.\n", + "Thus $a + b \\geq 750$ which is a contradiction to (IV), showing the **core is empty**.\n", + "This can be seen visually in the following plot.\n", + "\n", + ".. image:: _static/paper_game.pdf\n", + "\n", + "Notice that the line of equality (I) corresponds exactly to those payoff distributions which have $c=0$.\n", + "Due to that (I), (II) and (III) do not enclose an area, which is why the core is empty." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "## The least-Core\n", + "Due to the core being empty, we introduce a related concept **the least-core**.\n", + "Firstly lets define the **e-core** to be the payoff distributions, which are stable up to a given $e$.\n", + "\n", + "$$\n", + " \\mathcal{C_e} := \\{ \\psi(N,\\nu) | \\forall S\\subseteq N. \\sum_{i \\in S} \\psi(N,\\nu)_i + e \\geq v(S) \\land \\sum_{i \\in N} \\psi(N,\\nu)_i = \\nu(N) \\}\n", + "$$\n", + "\n", + "Then the least-core is the minimal $e$ such that $\\mathcal{C_e}$ is not empty.\n", + "We can think of this subsidy $e$ as an external payment for cooperating.\n", + "In our example the professor may encourage the Alice and Bob to include Charlie in their work by giving everybody an incentive \\$$e$.\n", + "In comparison to the Shapley value the (least-)core may contain multiple payoff distributions.\n", + "Often we are interested in a single payoff distribution, for which we choose the **egalitarian-least-core**.\n", + "Given the least-core $C_{e^*}$ the egalitarian-least-core is $x \\in C_{e^*}$ with minimal $||x||_2$.\n", + "It is important to note that the least-core **always exists** whereas the core is potentially empty.\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "markdown", + "source": [ + "## The least-Core of Writing a Paper\n", + "Computing the least-core for our example can become quite tedious as solving linear inequalities is required.\n", + "To simplify the process we use the *ExactComputer* from the *shapiq* package.\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 4, + "outputs": [], + "source": [ + "import numpy as np\n", + "from shapiq.exact import ExactComputer\n", + "from shapiq.games.base import Game\n", + "\n", + "\n", + "# Define the PaperGame as described above\n", + "class PaperGame(Game):\n", + "\n", + " def __init__(self) -> None:\n", + " super().__init__(n_players=3, normalize=True, normalization_value=0)\n", + "\n", + " def value_function(self, coalitions: np.ndarray) -> np.ndarray:\n", + " coalition_values = {\n", + " (): 0,\n", + " (0,): 0,\n", + " (1,): 0,\n", + " (2,): 0,\n", + " (0, 1): 500,\n", + " (0, 2): 400,\n", + " (1, 2): 350,\n", + " (0, 1, 2): 500,\n", + " }\n", + "\n", + " values = np.array([coalition_values[tuple(np.where(x)[0])] for x in coalitions])\n", + "\n", + " return values\n", + "\n", + "\n", + "paper_game = PaperGame()\n", + "\n", + "# Initialize the ExactComputer with the PaperGame\n", + "exact_computer = ExactComputer(n_players=3, game_fun=paper_game)\n", + "# Compute the egalitarian least core abbreviated to \"ELC\"\n", + "egalitarian_least_core = exact_computer(\"ELC\")" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-10-04T17:04:48.123115Z", + "start_time": "2024-10-04T17:04:48.116174Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "The egalitarian least core values can then be viewed with `.dict_values`." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 5, + "outputs": [ + { + "data": { + "text/plain": "{(0,): 233.3333333333333, (1,): 183.33333333333337, (2,): 83.33333333333334}" + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "egalitarian_least_core.dict_values" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-10-04T17:04:48.128619Z", + "start_time": "2024-10-04T17:04:48.123472Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "These values then correspond to the following payoff distribution:\n", + "\n", + "| Authos | egalitarian least-core |\n", + "|-----------------|---------------|\n", + "| Alice | \\$ $233.\\bar{3}$ |\n", + "| Bob | \\$ $183.\\bar{3}$ |\n", + "| Charlie | \\$ $83.\\bar{3}$ |\n", + "\n", + "The minimal $e$ is stored in `exact_computer._elc_stability_subsidy`." + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 6, + "outputs": [ + { + "data": { + "text/plain": "83.33333333333334" + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "exact_computer._elc_stability_subsidy" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2024-10-04T17:04:48.128991Z", + "start_time": "2024-10-04T17:04:48.128403Z" + } + } + }, + { + "cell_type": "markdown", + "source": [ + "Visualizing the egalitarian least-core in the plot we get the following:\n", + "\n", + ".. image:: _static/elc_paper_game.pdf" + ], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} From 9af7140300f47d8db8b08e86da34e66cd12136cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:04:52 +0200 Subject: [PATCH 13/14] Bump the pip-all-updates group with 6 updates (#249) Bumps the pip-all-updates group with 6 updates: | Package | From | To | | --- | --- | --- | | [black](https://github.com/psf/black) | `24.8.0` | `24.10.0` | | [coverage](https://github.com/nedbat/coveragepy) | `7.6.1` | `7.6.3` | | [networkx](https://github.com/networkx/networkx) | `3.3.0` | `3.4.1` | | [ruff](https://github.com/astral-sh/ruff) | `0.6.9` | `0.7.0` | | [torch](https://github.com/pytorch/pytorch) | `2.4.1` | `2.5.0` | | [torchvision](https://github.com/pytorch/vision) | `0.19.1` | `0.20.0` | Updates `black` from 24.8.0 to 24.10.0 - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/compare/24.8.0...24.10.0) Updates `coverage` from 7.6.1 to 7.6.3 - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.6.1...7.6.3) Updates `networkx` from 3.3.0 to 3.4.1 - [Release notes](https://github.com/networkx/networkx/releases) - [Commits](https://github.com/networkx/networkx/compare/networkx-3.3...networkx-3.4.1) Updates `ruff` from 0.6.9 to 0.7.0 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.6.9...0.7.0) Updates `torch` from 2.4.1 to 2.5.0 - [Release notes](https://github.com/pytorch/pytorch/releases) - [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md) - [Commits](https://github.com/pytorch/pytorch/compare/v2.4.1...v2.5.0) Updates `torchvision` from 0.19.1 to 0.20.0 - [Release notes](https://github.com/pytorch/vision/releases) - [Commits](https://github.com/pytorch/vision/compare/v0.19.1...v0.20.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all-updates - dependency-name: coverage dependency-type: direct:production update-type: version-update:semver-patch dependency-group: pip-all-updates - dependency-name: networkx dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all-updates - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all-updates - dependency-name: torch dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all-updates - dependency-name: torchvision dependency-type: direct:production update-type: version-update:semver-minor dependency-group: pip-all-updates ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Maximilian --- requirements.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index ab1ce95e..d59840a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,20 @@ # this requirements.txt file is maintained and bumbped by dependabot # this file is used to see weather new versions of the libraries are available and break shapiq -black==24.8.0 +black==24.10.0 colour==0.1.5 -coverage==7.6.1 +coverage==7.6.3 matplotlib==3.9.2 -networkx==3.3.0 +networkx==3.4.1 pandas==2.2.3 pytest==8.3.3 -ruff==0.6.9 +ruff==0.7.0 scikit-image==0.24.0 scikit-learn==1.5.2 scipy==1.14.1 shap==0.46.0 tqdm==4.66.5 -torch==2.4.1 -torchvision==0.19.1 +torch==2.5.0 +torchvision==0.20.0 transformers==4.45.2 xgboost==2.1.1 numpy==1.26.4 From 00df57595d54571e15690f30797a5acef40da821 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:05:22 +0200 Subject: [PATCH 14/14] Bump pypa/gh-action-pypi-publish from 1.10.2 to 1.10.3 (#248) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.2 to 1.10.3. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/897895f1e160c830e369f9779632ebc134688e1b...f7600683efdcb7656dec5b29656edb7bc586e597) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Maximilian --- .github/workflows/python-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index ac01d811..8bae4070 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -33,7 +33,7 @@ jobs: - name: Build package run: python -m build - name: Publish package - uses: pypa/gh-action-pypi-publish@897895f1e160c830e369f9779632ebc134688e1b + uses: pypa/gh-action-pypi-publish@f7600683efdcb7656dec5b29656edb7bc586e597 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }}