From 1c279839ff195ac328fe7eb8a5376c8577e80311 Mon Sep 17 00:00:00 2001 From: Guillaume VIGNAL Date: Fri, 13 Sep 2024 16:26:34 +0200 Subject: [PATCH 1/3] Add feature importance local and cumulative --- setup.py | 2 +- shapash/backend/base_backend.py | 8 +- shapash/explainer/multi_decorator.py | 10 +- shapash/explainer/smart_explainer.py | 27 +- shapash/explainer/smart_plotter.py | 1213 ++++++++++++----- shapash/explainer/smart_state.py | 8 +- shapash/manipulation/filters.py | 6 +- shapash/manipulation/summarize.py | 8 +- shapash/style/colors.json | 24 +- shapash/style/style_utils.py | 3 + shapash/webapp/smart_app.py | 2 +- .../explainer/test_smart_plotter.py | 283 +++- .../unit_tests/webapp/utils/test_callbacks.py | 34 +- 13 files changed, 1223 insertions(+), 405 deletions(-) diff --git a/setup.py b/setup.py index 53f525a3..a12cfaed 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ version=version_d["__version__"], python_requires=">3.8, <3.13", url="https://github.com/MAIF/shapash", - author="Yann Golhen, Sebastien Bidault, Yann Lagre, Maxime Gendre", + author="Yann Golhen, Sebastien Bidault, Yann Lagre, Maxime Gendre, Thomas Bouché, Maxime Lecardonnel, Guillaume Vignal", author_email="yann.golhen@maif.fr", description="Shapash is a Python library which aims to make machine learning interpretable and understandable by everyone.", long_description=long_description, diff --git a/shapash/backend/base_backend.py b/shapash/backend/base_backend.py index 3e5206b3..821c4a4d 100644 --- a/shapash/backend/base_backend.py +++ b/shapash/backend/base_backend.py @@ -107,7 +107,11 @@ def get_local_contributions( return local_contributions def get_global_features_importance( - self, contributions: pd.DataFrame, explain_data: Optional[dict] = None, subset: Optional[List[int]] = None + self, + contributions: pd.DataFrame, + explain_data: Optional[dict] = None, + subset: Optional[List[int]] = None, + norm: int = 1, ) -> Union[pd.Series, List[pd.Series]]: """Get global contributions using the explainer data computed in the `run_explainer` method. @@ -132,7 +136,7 @@ def get_global_features_importance( contributions = [c.loc[subset] for c in contributions] else: contributions = contributions.loc[subset] - return state.compute_features_import(contributions) + return state.compute_features_import(contributions, norm) def format_and_aggregate_local_contributions( self, diff --git a/shapash/explainer/multi_decorator.py b/shapash/explainer/multi_decorator.py index f734d10f..ffeeca49 100644 --- a/shapash/explainer/multi_decorator.py +++ b/shapash/explainer/multi_decorator.py @@ -226,23 +226,23 @@ def summarize(self, s_contribs, var_dicts, xs_sorted, masks, columns_dict, featu arg_tup = list(zip(s_contribs, var_dicts, xs_sorted, masks)) return self.delegate("summarize", arg_tup, columns_dict, features_dict) - def compute_features_import(self, contributions): + def compute_features_import(self, contributions, norm=1): """ Compute a relative features importance, sum of absolute values - ​​of the contributions for each - features importance compute in base 100 + ​​of the contributions for each + features importance compute in base 100 Parameters ---------- contributions : list - list of pandas.DataFrames containing contributions + list of pandas.DataFrames containing contributions Returns ------- list list of features importance pandas.series """ - return self.delegate("compute_features_import", contributions) + return self.delegate("compute_features_import", contributions, norm) def compute_grouped_contributions(self, contributions, features_groups): """ diff --git a/shapash/explainer/smart_explainer.py b/shapash/explainer/smart_explainer.py index 8d3143e7..465ce1dc 100644 --- a/shapash/explainer/smart_explainer.py +++ b/shapash/explainer/smart_explainer.py @@ -1,6 +1,7 @@ """ Smart explainer module """ + import copy import logging import shutil @@ -217,13 +218,12 @@ def __init__( self.backend_kwargs = backend_kwargs self.features_dict = dict() if features_dict is None else copy.deepcopy(features_dict) self.label_dict = label_dict - self.plot = SmartPlotter(self) self.title_story = title_story if title_story is not None else "" self.palette_name = palette_name if palette_name else "default" self.colors_dict = copy.deepcopy(select_palette(colors_loading(), self.palette_name)) if colors_dict is not None: self.colors_dict.update(colors_dict) - self.plot.define_style_attributes(colors_dict=self.colors_dict) + self.plot = SmartPlotter(self, self.colors_dict) self._case, self._classes = check_model(self.model) self.postprocessing = postprocessing @@ -359,7 +359,7 @@ def _compile_features_groups(self, features_groups): Performs required computations for groups of features. """ if self.backend.support_groups is False: - raise AssertionError(f"Selected backend ({self.backend.name}) " f"does not support groups of features.") + raise AssertionError(f"Selected backend ({self.backend.name}) does not support groups of features.") # Compute contributions for groups of features self.contributions_groups = self.state.compute_grouped_contributions(self.contributions, features_groups) self.features_imp_groups = None @@ -931,7 +931,7 @@ def to_pandas( return pd.concat([y_pred, summary], axis=1) - def compute_features_import(self, force=False): + def compute_features_import(self, force=False, local=False): """ Compute a relative features importance, sum of absolute values of the contributions for each. @@ -949,11 +949,26 @@ def compute_features_import(self, force=False): index of the serie = contributions.columns """ self.features_imp = self.backend.get_global_features_importance( - contributions=self.contributions, explain_data=self.explain_data, subset=None + contributions=self.contributions, explain_data=self.explain_data, subset=None, norm=1 ) + if local: + self.features_imp_local_lev1 = self.backend.get_global_features_importance( + contributions=self.contributions, explain_data=self.explain_data, subset=None, norm=3 + ) + self.features_imp_local_lev2 = self.backend.get_global_features_importance( + contributions=self.contributions, explain_data=self.explain_data, subset=None, norm=7 + ) + if self.features_groups is not None and self.features_imp_groups is None: - self.features_imp_groups = self.state.compute_features_import(self.contributions_groups) + self.features_imp_groups = self.state.compute_features_import(self.contributions_groups, norm=1) + if local: + self.features_imp_groups_local_lev1 = self.state.compute_features_import( + self.contributions_groups, norm=3 + ) + self.features_imp_groups_local_lev2 = self.state.compute_features_import( + self.contributions_groups, norm=7 + ) def compute_features_stability(self, selection): """ diff --git a/shapash/explainer/smart_plotter.py b/shapash/explainer/smart_plotter.py index 3497ecb0..bf38e538 100644 --- a/shapash/explainer/smart_plotter.py +++ b/shapash/explainer/smart_plotter.py @@ -12,6 +12,7 @@ import pandas as pd import plotly.express as px import scipy.cluster.hierarchy as sch +from matplotlib.colors import LinearSegmentedColormap from plotly import graph_objs as go from plotly.offline import plot from plotly.subplots import make_subplots @@ -19,7 +20,7 @@ from shapash.manipulation.select_lines import select_lines from shapash.manipulation.summarize import compute_corr, project_feature_values_1d -from shapash.style.style_utils import colors_loading, define_style, select_palette +from shapash.style.style_utils import colors_loading, define_style, get_pyplot_color, select_palette from shapash.utils.utils import ( add_line_break, add_text, @@ -46,13 +47,16 @@ class SmartPlotter: >>> xpl.plot.my_plot_method(param=value) """ - def __init__(self, explainer): - self.explainer = explainer - self._palette_name = list(colors_loading().keys())[0] - self._style_dict = define_style(select_palette(colors_loading(), self._palette_name)) - self.round_digit = None - self.last_stability_selection = False - self.last_compacity_selection = False + def __init__(self, explainer, colors_dict=None): + self._explainer = explainer + if colors_dict: + self._style_dict = define_style(colors_dict) + else: + palette_name = list(colors_loading().keys())[0] + self._style_dict = define_style(select_palette(colors_loading(), palette_name)) + self._round_digit = None + self._last_stability_selection = False + self._last_compacity_selection = False def define_style_attributes(self, colors_dict): """ @@ -64,7 +68,7 @@ def define_style_attributes(self, colors_dict): """ self._style_dict = define_style(colors_dict) - def tuning_colorscale(self, values, keep_90_pct=False): + def _tuning_colorscale(self, values, keep_90_pct=False): """ Adjusts the color scale based on the distribution of points. @@ -127,15 +131,15 @@ def tuning_colorscale(self, values, keep_90_pct=False): return color_scale, cmin, cmax - def tuning_round_digit(self): + def _tuning_round_digit(self): """ adapts the display of the number of digit to the distribution of points """ quantile = [0.25, 0.75] - desc_df = self.explainer.y_pred.describe(percentiles=quantile) + desc_df = self._explainer.y_pred.describe(percentiles=quantile) perc1, perc2 = list(desc_df.loc[[str(int(p * 100)) + "%" for p in quantile]].values) p_diff = perc2 - perc1 - self.round_digit = compute_digit_number(p_diff) + self._round_digit = compute_digit_number(p_diff) def _update_contributions_fig( self, @@ -192,7 +196,6 @@ def _update_contributions_fig( title = f"{truncate_str(feature_name)} - Feature Contribution" # Add subtitle and / or addnote if subtitle or addnote: - # title += f"
{add_text([subtitle, addnote], sep=' - ')}
" if subtitle and addnote: title += "
" + subtitle + " - " + addnote + "" elif subtitle: @@ -206,10 +209,10 @@ def _update_contributions_fig( dict_xaxis["text"] = truncate_str(feature_name, 110) dict_yaxis["text"] = "Contribution" - if self.explainer._case == "regression": + if self._explainer._case == "regression": colorpoints = pred colorbar_title = "Predicted" - elif self.explainer._case == "classification": + elif self._explainer._case == "classification": colorpoints = proba_values colorbar_title = "Predicted Proba" @@ -224,7 +227,7 @@ def _update_contributions_fig( fig.layout.coloraxis.cmax = cmax elif fig.data[0].type != "violin": - if self.explainer._case == "classification" and pred is not None: + if self._explainer._case == "classification" and pred is not None: fig.data[-1].marker.color = pred.iloc[:, 0].apply( lambda x: ( self._style_dict["violin_area_classif"][1] @@ -256,7 +259,7 @@ def _update_contributions_fig( if file_name: plot(fig, filename=file_name, auto_open=auto_open) - def plot_scatter( + def _plot_scatter( self, feature_values, contributions, @@ -325,7 +328,7 @@ def plot_scatter( proba_values = proba_values.loc[feature_values.index] # add break line to X label if necessary - max_len_by_row = max([round(50 / self.explainer.features_desc[feature_values.columns.values[0]]), 8]) + max_len_by_row = max([round(50 / self._explainer.features_desc[feature_values.columns.values[0]]), 8]) feature_values.iloc[:, 0] = feature_values.iloc[:, 0].apply( add_line_break, args=( @@ -551,7 +554,7 @@ def _create_jittered_points( return jittered_points - def prepare_hover_text(self, feature_values, pred, feature_name): + def _prepare_hover_text(self, feature_values, pred, feature_name): """ Prepares the hover text for a Plotly plot based on feature values and predictions. @@ -668,7 +671,7 @@ def _add_violin_and_scatter( self._add_scatter_trace(fig, x, y, c, marker, hovertext, hovertemplate, customdata, secondary_y) - def plot_violin( + def _plot_violin( self, feature_values, contributions, @@ -731,7 +734,7 @@ def plot_violin( column_name = feature_values.columns[0] feature_values = feature_values.sort_values(by=column_name) - max_len_by_row = max([round(50 / self.explainer.features_desc[feature_values.columns.values[0]]), 8]) + max_len_by_row = max([round(50 / self._explainer.features_desc[feature_values.columns.values[0]]), 8]) feature_values.iloc[:, 0] = feature_values.iloc[:, 0].apply( add_line_break, args=( @@ -745,7 +748,7 @@ def plot_violin( if proba_values is not None: proba_values = proba_values.loc[feature_values.index] - hv_text_df, hovertemplate = self.prepare_hover_text(feature_values, pred, feature_name) + hv_text_df, hovertemplate = self._prepare_hover_text(feature_values, pred, feature_name) feature_values_counts = feature_values.value_counts() xs = feature_values_counts.index.get_level_values(0).sort_values() @@ -753,9 +756,9 @@ def plot_violin( y_upper = (feature_values_counts.loc[xs] / feature_values_counts.sum()).values.flatten() y_upper_max = y_upper.max() - if self.explainer._case == "classification": + if self._explainer._case == "classification": colorpoints = proba_values - elif self.explainer._case == "regression": + elif self._explainer._case == "regression": colorpoints = pred else: colorpoints = None @@ -778,7 +781,7 @@ def plot_violin( ) ) - if pred is not None and self.explainer._case == "classification": + if pred is not None and self._explainer._case == "classification": # Negative case feature_cond_neg = (pred.iloc[:, 0] != col_modality) & (feature_values.iloc[:, 0] == c) self._add_violin_and_scatter( @@ -892,7 +895,7 @@ def plot_violin( return fig - def plot_features_import( + def _plot_features_import( self, feature_imp1, feature_imp2=None, @@ -955,9 +958,9 @@ def plot_features_import( ( self._style_dict["featureimp_groups"][0] if ( - self.explainer.features_groups is not None - and self.explainer.inv_features_dict.get(f.replace("", "").replace("", "")) - in self.explainer.features_groups.keys() + self._explainer.features_groups is not None + and self._explainer.inv_features_dict.get(f.replace("", "").replace("", "")) + in self._explainer.features_groups.keys() ) else dict_style_bar1["color"] ) @@ -977,7 +980,7 @@ def plot_features_import( margin={"l": 160, "r": 0, "t": topmargin, "b": 50}, ) # To change ticktext when the x label size is upper than 30 and zoom is False - if (type(feature_imp1.index[0]) == str) & (not zoom): + if (isinstance(feature_imp1.index[0], str)) & (not zoom): # change index to abc...abc if its length is upper than 30 index_val = [y.replace(y[24 : len(y) - 3], "...") if len(y) > 30 else y for y in feature_imp1.index] else: @@ -1014,7 +1017,325 @@ def plot_features_import( plot(fig, filename=file_name, auto_open=auto_open) return fig - def plot_bar_chart( + def _plot_local_features_import( + self, + feat_imp, + title="Features Importance Global-Local", + addnote=None, + subtitle=None, + width=900, + height=500, + file_name=None, + auto_open=False, + zoom=False, + ): + """ + Plot features importance computed with the prediction set. + Parameters + ---------- + feat_imp : dict of pd.Series + Feature importance computed with every rows :global, semi-local and local + title : str + Title of the plot, default set to 'Features Importance' + addnote : String (default: None) + Specify a note to display + subtitle : String (default: None) + Subtitle to display + width : Int (default: 900) + Plotly figure - layout width + height : Int (default: 500) + Plotly figure - layout height + file_name: string (optional) + Specify the save path of html files. If it is not provided, no file will be saved. + auto_open: bool (default=False) + open automatically the plot + zoom: bool (default=False) + graph is currently zoomed + """ + dict_t = copy.deepcopy(self._style_dict["dict_title"]) + topmargin = 80 + # Add subtitle and / or addnote + if subtitle or addnote: + if subtitle and addnote: + title += "
" + subtitle + " - " + addnote + "" + elif subtitle: + title += "
" + subtitle + "" + else: + title += "
" + addnote + "" + topmargin = topmargin + 15 + dict_t.update(text=title) + dict_xaxis = copy.deepcopy(self._style_dict["dict_xaxis"]) + dict_xaxis.update(text="Mean absolute Contribution") + dict_yaxis = copy.deepcopy(self._style_dict["dict_yaxis"]) + dict_yaxis.update(text=None) + dict_style_bar = {} + for type_feat, i in zip(["global", "semi-local", "local"], [1, 3, 4]): + dict_style_bar[type_feat] = self._style_dict["dict_featimp_colors"][i] + dict_yaxis["text"] = None + + # Change bar color for groups of features + marker_color = [ + ( + self._style_dict["featureimp_groups"][0] + if ( + self._explainer.features_groups is not None + and self._explainer.inv_features_dict.get(f.replace("", "").replace("", "")) + in self._explainer.features_groups.keys() + ) + else dict_style_bar["global"]["color"] + ) + for f in feat_imp["global"].index + ] + + layout = go.Layout( + barmode="group", + template="none", + autosize=False, + width=width, + height=height, + title=dict_t, + xaxis_title=dict_xaxis, + yaxis_title=dict_yaxis, + hovermode="closest", + margin={"l": 160, "r": 0, "t": topmargin, "b": 50}, + ) + + data = [] + for type_feat in ["local", "semi-local", "global"]: + feature_imp = feat_imp[type_feat] + style_bar = dict_style_bar[type_feat] + + data.append( + go.Bar( + x=feature_imp.round(4), + y=feature_imp.index, + orientation="h", + name=type_feat.capitalize(), + marker=style_bar, + marker_color=marker_color if type_feat == "global" else style_bar["color"], + hovertemplate="Feature: %{customdata}
Contribution: %{x:.4f}", + customdata=feature_imp.index, + ) + ) + + fig = go.Figure(data=data, layout=layout) + + # Update ticktext + # To change ticktext when the x label size is upper than 30 and zoom is False + if (isinstance(feat_imp["global"].index[0], str)) & (not zoom): + # change index to abc...abc if its length is upper than 30 + index_val = [y.replace(y[24 : len(y) - 3], "...") if len(y) > 30 else y for y in feat_imp["global"].index] + else: + index_val = feat_imp["global"].index + fig.update_yaxes(ticktext=index_val, tickvals=feat_imp["global"].index, tickmode="array", dtick=1) + fig.update_yaxes(automargin=True) + if file_name: + plot(fig, filename=file_name, auto_open=auto_open) + return fig + + def _plot_feature_contributions_cumulative( + self, + feature_imp1, + contributions_case, + title="Feature Contributions Cumulative Plot", + addnote=None, + subtitle=None, + width=900, + height=500, + normalize_by_nb_samples=False, + degree="slider", + file_name=None, + auto_open=False, + zoom=False, + ): + """ + Generates a cumulative plot of feature contributions with a slider to adjust the degree. + + Parameters: + - feature_imp1: DataFrame of feature importances. + - title (str): The title of the plot. + - width (int): The width of the plot in pixels. + - height (int): The height of the plot in pixels. + - normalize_by_nb_samples (bool): Whether to normalize the contributions by the number of samples. + - degree (str or float): The degree of normalization to apply. Use 'slider' for interactive degree control. + - file_name (str, optional): Specify the save path of HTML files. If not provided, no file will be saved. + - auto_open (bool): Whether to automatically open the plot. + + Returns: + - fig (plotly.graph_objs._figure.Figure): The generated cumulative plot figure. + """ + # Number of features + num_features = len(feature_imp1) + + # Generate color scale + col_scale = get_pyplot_color(colors=self._style_dict["feature_contributions_cumulative"]) + cmap = LinearSegmentedColormap.from_list("feature_contributions_cumulative", col_scale, N=256) + colors = [cmap(i / num_features) for i in range(num_features)] + colors_hex = ["#{:02x}{:02x}{:02x}".format(int(r * 255), int(g * 255), int(b * 255)) for r, g, b, _ in colors] + + # Initialize data for storing the series + data = [] + serie_tot = None + + lst_feat = list(feature_imp1.index)[::-1] + lst_feat = [f.replace("", "").replace("", "") for f in lst_feat] + + # Process each feature's contributions and compute cumulative sums + for name in lst_feat: + serie = ( + contributions_case[self._explainer.inv_features_dict.get(name)] + .abs() + .sort_values(ascending=False) + .cumsum() + .reset_index(drop=True) + ) + data.append(serie) + + # Accumulate the total series for normalization + if serie_tot is None: + serie_tot = serie.copy() + else: + serie_tot += serie + + # Create the Plotly traces for each series + dict_t = copy.deepcopy(self._style_dict["dict_title"]) + topmargin = 80 + # Add subtitle and / or addnote + if subtitle or addnote: + if subtitle and addnote: + title += "
" + subtitle + " - " + addnote + "" + elif subtitle: + title += "
" + subtitle + "" + else: + title += "
" + addnote + "" + topmargin = topmargin + 15 + dict_t.update(text=title) + + if (isinstance(lst_feat[0], str)) & (not zoom): + # change index to abc...abc if its length is upper than 30 + index_val = [y.replace(y[24 : len(y) - 3], "...") if len(y) > 30 else y for y in lst_feat] + else: + index_val = lst_feat + + figs = [] + for i, serie in enumerate(data): + serie_values = serie.copy() + + # Optionally normalize by the number of samples + if normalize_by_nb_samples: + serie_values /= pd.Series(range(1, len(serie_values) + 1)) + + # Apply initial degree-based normalization + if degree not in [0, "slider"]: + serie_values /= serie_tot ** degree + + # Append the trace for the current series + figs.append( + go.Scatter( + x=serie.index, + y=serie_values, + mode="lines", + name=index_val[i], + hoverinfo="text", # Use 'text' to refer to custom hovertext + text=lst_feat[i], # Set custom text for hover + line=dict(color=colors_hex[i], width=3), + hoverlabel=dict( + font_size=12, # Optional: adjust font size for better readability + ), + ) + ) + + # Define layout with a clean white background and title + layout = go.Layout( + title=dict_t, + xaxis=dict(visible=False), + yaxis=dict(visible=False, autorange=True), + plot_bgcolor="white", + paper_bgcolor="white", + width=width, + height=height, + margin={"l": 10, "r": 0, "t": topmargin, "b": 10}, + ) + + # Create the initial figure with the data and layout + fig = go.Figure(data=figs, layout=layout) + + # Create a list of frames with updated data for each degree value + if degree == "slider": + frames = [] + degree_range = np.round(np.arange(0, 1.1, 0.1), 1) + + for deg in degree_range: + new_figs = [] + max_y = 0 # Track max value for y-axis rescaling + for i, serie in enumerate(data): + serie_values = serie.copy() + + if normalize_by_nb_samples: + serie_values /= pd.Series(range(1, len(serie_values) + 1)) + + # Apply degree-based normalization + if deg != 0: + serie_values /= serie_tot ** (-deg) + + max_y = max(max_y, serie_values.max()) + + new_figs.append( + go.Scatter( + x=serie.index, + y=serie_values, + mode="lines", + hoverinfo="text", # Use 'text' to refer to custom hovertext + text=lst_feat[i], # Set custom text for hover + line=dict(color=colors_hex[i], width=3), + hoverlabel=dict( + font_size=12, # Optional: adjust font size for better readability + ), + ) + ) + + # Layout for this degree value, adjusting y-axis range + frame_layout = go.Layout( + yaxis=dict(visible=False, autorange=True), + plot_bgcolor="white", + paper_bgcolor="white", + width=width, + height=height, + ) + + # Append each frame with its own layout + frames.append(go.Frame(data=new_figs, name=f"degree_{deg}", layout=frame_layout)) + + # Add slider to control the degree parameter + sliders = [ + { + "currentvalue": {"prefix": "Degree: "}, + "pad": {"b": 10}, + "steps": [ + { + "args": [ + [f"degree_{deg}"], + {"frame": {"duration": 300, "redraw": True}, "mode": "immediate"}, + ], + "label": str(deg), + "method": "animate", + } + for deg in degree_range + ], + } + ] + + # Add frames and sliders to the figure + fig.update(frames=frames) + fig.update_layout(sliders=sliders) + + # Optionally save the plot to a file + if file_name: + plot(fig, filename=file_name, auto_open=auto_open) + + return fig + + def _plot_bar_chart( self, index_value, var_dict, @@ -1097,18 +1418,18 @@ def plot_bar_chart( # If bar is a group of features, hovertext includes the values of the features of the group # And color changes if ( - self.explainer.features_groups is not None - and self.explainer.inv_features_dict.get(expl[0]) in self.explainer.features_groups.keys() + self._explainer.features_groups is not None + and self._explainer.inv_features_dict.get(expl[0]) in self._explainer.features_groups.keys() and len(index_value) > 0 ): - group_name = self.explainer.inv_features_dict.get(expl[0]) - feat_groups_values = self.explainer.x_init[self.explainer.features_groups[group_name]].loc[ + group_name = self._explainer.inv_features_dict.get(expl[0]) + feat_groups_values = self._explainer.x_init[self._explainer.features_groups[group_name]].loc[ index_value[0] ] hoverlabel = "
".join( [ "{} :{}".format( - add_line_break(self.explainer.features_dict.get(f_name, f_name), 40, maxlen=120), + add_line_break(self._explainer.features_dict.get(f_name, f_name), 40, maxlen=120), add_line_break(f_value, 40, maxlen=160), ) for f_name, f_value in feat_groups_values.to_dict().items() @@ -1129,12 +1450,12 @@ def plot_bar_chart( else: trunc_new_value = trunc_value if len(contrib) <= yaxis_max_label and ( - self.explainer.features_groups is None + self._explainer.features_groups is None # We don't want to display label values for t-sne projected values of groups of features. or ( - self.explainer.features_groups is not None - and self.explainer.inv_features_dict.get(expl[0]) - not in self.explainer.features_groups.keys() + self._explainer.features_groups is not None + and self._explainer.inv_features_dict.get(expl[0]) + not in self._explainer.features_groups.keys() ) ): # ylabel is based on trunc_new_value @@ -1175,7 +1496,6 @@ def plot_bar_chart( fig = go.Figure(data=[x[-1] for x in bars], layout=layout) fig.update_yaxes(dtick=1) fig.update_yaxes(automargin=True) - # fig.update_xaxes(automargin=True) if file_name: plot(fig, filename=file_name, auto_open=auto_open) @@ -1196,7 +1516,7 @@ def plot_bar_chart( ) return fig - def get_selection(self, line, var_dict, x_val, contrib): + def _get_selection(self, line, var_dict, x_val, contrib): """ An auxiliary function to select the row of interest. Parameters @@ -1222,7 +1542,7 @@ def get_selection(self, line, var_dict, x_val, contrib): return var_dict, x_val, contrib - def apply_mask_one_line(self, line, var_dict, x_val, contrib, label=None): + def _apply_mask_one_line(self, line, var_dict, x_val, contrib, label=None): """ An auxiliary function to select the mask to apply before plotting local explanation. @@ -1245,11 +1565,11 @@ def apply_mask_one_line(self, line, var_dict, x_val, contrib, label=None): Masked input lists. """ mask = np.array([True] * len(contrib)) - if hasattr(self.explainer, "mask"): - if isinstance(self.explainer.mask, list): - mask = self.explainer.mask[label].loc[line[0], :].values + if hasattr(self._explainer, "mask"): + if isinstance(self._explainer.mask, list): + mask = self._explainer.mask[label].loc[line[0], :].values else: - mask = self.explainer.mask.loc[line[0], :].values + mask = self._explainer.mask.loc[line[0], :].values contrib = contrib[mask] x_val = x_val[mask] @@ -1257,7 +1577,7 @@ def apply_mask_one_line(self, line, var_dict, x_val, contrib, label=None): return var_dict.tolist(), x_val.tolist(), contrib.tolist() - def check_masked_contributions(self, line, var_dict, x_val, contrib, label=None): + def _check_masked_contributions(self, line, var_dict, x_val, contrib, label=None): """ Check for masked contributions and update features_values and contrib to take the sum of masked contributions into account. @@ -1277,17 +1597,17 @@ def check_masked_contributions(self, line, var_dict, x_val, contrib, label=None) numpy arrays Input arrays updated with masked contributions. """ - if hasattr(self.explainer, "masked_contributions"): - if isinstance(self.explainer.masked_contributions, list): - ext_contrib = self.explainer.masked_contributions[label].loc[line[0], :].values + if hasattr(self._explainer, "masked_contributions"): + if isinstance(self._explainer.masked_contributions, list): + ext_contrib = self._explainer.masked_contributions[label].loc[line[0], :].values else: - ext_contrib = self.explainer.masked_contributions.loc[line[0], :].values + ext_contrib = self._explainer.masked_contributions.loc[line[0], :].values ext_var_dict = ["Hidden Negative Contributions", "Hidden Positive Contributions"] ext_x = ["", ""] ext_contrib = ext_contrib.tolist() - exclusion = np.where(np.array(ext_contrib) == 0)[0].tolist() + exclusion = np.flatnonzero(np.array(ext_contrib) == 0).tolist() exclusion.sort(reverse=True) for ind in exclusion: del ext_var_dict[ind] @@ -1300,7 +1620,7 @@ def check_masked_contributions(self, line, var_dict, x_val, contrib, label=None) return var_dict, x_val, contrib - def local_pred(self, index, label=None): + def _local_pred(self, index, label=None): """ compute a local pred to display in local_plot Parameters @@ -1312,16 +1632,16 @@ def local_pred(self, index, label=None): ------- float: Predict or predict_proba value """ - if self.explainer._case == "classification": - if self.explainer.proba_values is not None: - value = self.explainer.proba_values.iloc[:, [label]].loc[index].values[0] + if self._explainer._case == "classification": + if self._explainer.proba_values is not None: + value = self._explainer.proba_values.iloc[:, [label]].loc[index].values[0] else: value = None - elif self.explainer._case == "regression": - if self.explainer.y_pred is not None: - value = self.explainer.y_pred.loc[index] + elif self._explainer._case == "regression": + if self._explainer.y_pred is not None: + value = self._explainer.y_pred.loc[index] else: - value = self.explainer.model.predict(self.explainer.x_encoded.loc[[index]])[0] + value = self._explainer.model.predict(self._explainer.x_encoded.loc[[index]])[0] if isinstance(value, pd.Series): value = value.values[0] @@ -1394,21 +1714,23 @@ def local_plot( -------- >>> xpl.plot.local_plot(row_num=0) """ - display_groups = True if (display_groups is not False and self.explainer.features_groups is not None) else False + display_groups = ( + True if (display_groups is not False and self._explainer.features_groups is not None) else False + ) if display_groups: - data = self.explainer.data_groups + data = self._explainer.data_groups else: - data = self.explainer.data + data = self._explainer.data if index is not None: - if index in self.explainer.x_init.index: + if index in self._explainer.x_init.index: line = [index] else: line = [] elif row_num is not None: - line = [self.explainer.x_init.index[row_num]] + line = [self._explainer.x_init.index[row_num]] elif query is not None: - line = select_lines(self.explainer.x_init, query) + line = select_lines(self._explainer.x_init, query) else: line = [] @@ -1424,76 +1746,76 @@ def local_plot( else: # apply filter if the method have not yet been asked in order to limit the number of feature to display if ( - not hasattr(self.explainer, "mask_params") # If the filter method has not been called yet + not hasattr(self._explainer, "mask_params") # If the filter method has not been called yet # Or if the already computed mask was not updated with current display_groups parameter or ( isinstance(data["contrib_sorted"], pd.DataFrame) - and len(data["contrib_sorted"].columns) != len(self.explainer.mask.columns) + and len(data["contrib_sorted"].columns) != len(self._explainer.mask.columns) ) or ( isinstance(data["contrib_sorted"], list) - and len(data["contrib_sorted"][0].columns) != len(self.explainer.mask[0].columns) + and len(data["contrib_sorted"][0].columns) != len(self._explainer.mask[0].columns) ) ): - self.explainer.filter(max_contrib=20, display_groups=display_groups) + self._explainer.filter(max_contrib=20, display_groups=display_groups) - if self.explainer._case == "classification": + if self._explainer._case == "classification": if label is None: label = -1 - label_num, _, label_value = self.explainer.check_label_name(label) + label_num, _, label_value = self._explainer.check_label_name(label) contrib = data["contrib_sorted"][label_num] x_val = data["x_sorted"][label_num] var_dict = data["var_dict"][label_num] if show_predict is True: - pred = self.local_pred(line[0], label_num) + pred = self._local_pred(line[0], label_num) if pred is None: subtitle = f"Response: {label_value} - No proba available" else: subtitle = f"Response: {label_value} - Proba: {pred:.4f}" - elif self.explainer._case == "regression": + elif self._explainer._case == "regression": contrib = data["contrib_sorted"] x_val = data["x_sorted"] var_dict = data["var_dict"] label_num = None if show_predict is True: - pred_value = self.local_pred(line[0]) - if self.explainer.y_pred is not None: - if self.round_digit is None: - self.tuning_round_digit() - digit = self.round_digit + pred_value = self._local_pred(line[0]) + if self._explainer.y_pred is not None: + if self._round_digit is None: + self._tuning_round_digit() + digit = self._round_digit else: digit = compute_digit_number(pred_value) subtitle = f"Predict: {round(pred_value, digit)}" - var_dict, x_val, contrib = self.get_selection(line, var_dict, x_val, contrib) - var_dict, x_val, contrib = self.apply_mask_one_line(line, var_dict, x_val, contrib, label=label_num) + var_dict, x_val, contrib = self._get_selection(line, var_dict, x_val, contrib) + var_dict, x_val, contrib = self._apply_mask_one_line(line, var_dict, x_val, contrib, label=label_num) # use label of each column if display_groups: - var_dict = [self.explainer.features_dict[self.explainer.x_init_groups.columns[x]] for x in var_dict] + var_dict = [self._explainer.features_dict[self._explainer.x_init_groups.columns[x]] for x in var_dict] else: - var_dict = [self.explainer.features_dict[self.explainer.columns_dict[x]] for x in var_dict] + var_dict = [self._explainer.features_dict[self._explainer.columns_dict[x]] for x in var_dict] if show_masked: - var_dict, x_val, contrib = self.check_masked_contributions( + var_dict, x_val, contrib = self._check_masked_contributions( line, var_dict, x_val, contrib, label=label_num ) # Filtering all negative or positive contrib if specify in mask exclusion = [] - if hasattr(self.explainer, "mask_params"): - if self.explainer.mask_params["positive"] is True: - exclusion = np.where(np.array(contrib) < 0)[0].tolist() - elif self.explainer.mask_params["positive"] is False: - exclusion = np.where(np.array(contrib) > 0)[0].tolist() + if hasattr(self._explainer, "mask_params"): + positive = self._explainer.mask_params.get("positive") + if positive is not None: + exclusion = np.flatnonzero(np.array(contrib) < 0 if positive else np.array(contrib) > 0).tolist() + exclusion.sort(reverse=True) for expl in exclusion: del var_dict[expl] del x_val[expl] del contrib[expl] - fig = self.plot_bar_chart( + fig = self._plot_bar_chart( line, var_dict, x_val, contrib, yaxis_max_label, subtitle, width, height, file_name, auto_open, zoom ) return fig @@ -1559,33 +1881,33 @@ def contribution_plot( >>> xpl.plot.contribution_plot(0) """ - if self.explainer._case == "classification": - label_num, _, label_value = self.explainer.check_label_name(label) + if self._explainer._case == "classification": + label_num, _, label_value = self._explainer.check_label_name(label) if not isinstance(col, (str, int)): raise ValueError("parameter col must be string or int.") - if hasattr(self.explainer, "inv_features_dict"): - col = self.explainer.inv_features_dict.get(col, col) - col_is_group = self.explainer.features_groups and col in self.explainer.features_groups.keys() + if hasattr(self._explainer, "inv_features_dict"): + col = self._explainer.inv_features_dict.get(col, col) + col_is_group = self._explainer.features_groups and col in self._explainer.features_groups.keys() # Case where col is a group of features if col_is_group: - contributions = self.explainer.contributions_groups - col_label = self.explainer.features_dict[col] - col_name = self.explainer.features_groups[col] # Here col_name is actually a list of features - col_value_count = self.explainer.features_desc[col] + contributions = self._explainer.contributions_groups + col_label = self._explainer.features_dict[col] + col_name = self._explainer.features_groups[col] # Here col_name is actually a list of features + col_value_count = self._explainer.features_desc[col] else: - contributions = self.explainer.contributions - col_id = self.explainer.check_features_name([col])[0] - col_name = self.explainer.columns_dict[col_id] - col_value_count = self.explainer.features_desc[col_name] + contributions = self._explainer.contributions + col_id = self._explainer.check_features_name([col])[0] + col_name = self._explainer.columns_dict[col_id] + col_value_count = self._explainer.features_desc[col_name] - if self.explainer.features_dict: - col_label = self.explainer.features_dict[col_name] + if self._explainer.features_dict: + col_label = self._explainer.features_dict[col_name] else: col_label = col_name - list_ind, addnote = self.explainer.plot._subset_sampling( + list_ind, addnote = self._explainer.plot._subset_sampling( selection, max_points, None if col_is_group else col, col_value_count ) @@ -1597,57 +1919,58 @@ def contribution_plot( cmax = None # Classification Case - if self.explainer._case == "classification": + if self._explainer._case == "classification": subcontrib = contributions[label_num] - if self.explainer.y_pred is not None: - col_value = self.explainer._classes[label_num] + if self._explainer.y_pred is not None: + col_value = self._explainer._classes[label_num] subtitle = f"Response: {label_value}" # predict proba Color scale - if proba and self.explainer.proba_values is not None: - proba_values = self.explainer.proba_values.iloc[:, [label_num]] + if proba and self._explainer.proba_values is not None: + proba_values = self._explainer.proba_values.iloc[:, [label_num]] # Proba subset: proba_values = proba_values.loc[list_ind, :] - col_scale, cmin, cmax = self.tuning_colorscale(proba_values, keep_90_pct=True) - elif self.explainer.y_pred is not None: - pred_values = self.explainer.y_pred.iloc[:, [label_num]] + col_scale, cmin, cmax = self._tuning_colorscale(proba_values, keep_90_pct=True) + elif self._explainer.y_pred is not None: + pred_values = self._explainer.y_pred.iloc[:, [label_num]] # Prediction subset: pred_values = pred_values.loc[list_ind, :] - col_scale, cmin, cmax = self.tuning_colorscale(pred_values, keep_90_pct=True) + col_scale, cmin, cmax = self._tuning_colorscale(pred_values, keep_90_pct=True) # Regression Case - color scale - elif self.explainer._case == "regression": + elif self._explainer._case == "regression": subcontrib = contributions - if self.explainer.y_pred is not None: - col_scale, cmin, cmax = self.tuning_colorscale(self.explainer.y_pred.loc[list_ind], keep_90_pct=True) + if self._explainer.y_pred is not None: + col_scale, cmin, cmax = self._tuning_colorscale(self._explainer.y_pred.loc[list_ind], keep_90_pct=True) # Subset - if self.explainer.postprocessing_modifications: - feature_values = self.explainer.x_contrib_plot.loc[list_ind, col_name] + if self._explainer.postprocessing_modifications: + feature_values = self._explainer.x_contrib_plot.loc[list_ind, col_name] else: - feature_values = self.explainer.x_init.loc[list_ind, col_name] + feature_values = self._explainer.x_init.loc[list_ind, col_name] if col_is_group: feature_values = project_feature_values_1d( feature_values, col, - self.explainer.x_init, - self.explainer.x_encoded, - self.explainer.preprocessing, - features_dict=self.explainer.features_dict, + self._explainer.x_init, + self._explainer.x_encoded, + self._explainer.preprocessing, + features_dict=self._explainer.features_dict, ) contrib = subcontrib.loc[list_ind, col].to_frame() - if self.explainer.features_imp is None: - self.explainer.compute_features_import() + if self._explainer.features_imp is None: + self._explainer.compute_features_import() features_imp = ( - self.explainer.features_imp - if isinstance(self.explainer.features_imp, pd.Series) - else self.explainer.features_imp[0] + self._explainer.features_imp + if isinstance(self._explainer.features_imp, pd.Series) + else self._explainer.features_imp[0] ) top_features_of_group = ( - features_imp.loc[self.explainer.features_groups[col]].sort_values(ascending=False)[:4].index + features_imp.loc[self._explainer.features_groups[col]].sort_values(ascending=False)[:4].index ) # Displaying top 4 features metadata = { - self.explainer.features_dict[f_name]: self.explainer.x_init[f_name] for f_name in top_features_of_group + self._explainer.features_dict[f_name]: self._explainer.x_init[f_name] + for f_name in top_features_of_group } text_group = "Features values were projected on the x axis using t-SNE" # if group don't show addnote, if not, it's too long @@ -1660,23 +1983,23 @@ def contribution_plot( metadata = None feature_values = feature_values.to_frame() - if self.explainer.y_pred is not None: - y_pred = self.explainer.y_pred.loc[list_ind] + if self._explainer.y_pred is not None: + y_pred = self._explainer.y_pred.loc[list_ind] # Add labels if exist - if self.explainer._case == "classification" and self.explainer.label_dict is not None: - y_pred = y_pred.map(lambda x: self.explainer.label_dict[x]) - col_value = self.explainer.label_dict[col_value] + if self._explainer._case == "classification" and self._explainer.label_dict is not None: + y_pred = y_pred.map(lambda x: self._explainer.label_dict[x]) + col_value = self._explainer.label_dict[col_value] # round predict - elif self.explainer._case == "regression": - if self.round_digit is None: - self.tuning_round_digit() - y_pred = y_pred.map(lambda x: round(x, self.round_digit)) + elif self._explainer._case == "regression": + if self._round_digit is None: + self._tuning_round_digit() + y_pred = y_pred.map(lambda x: round(x, self._round_digit)) else: y_pred = None # selecting the best plot : Scatter, Violin? if col_value_count > violin_maxf: - fig = self.plot_scatter( + fig = self._plot_scatter( feature_values, contrib, col_label, @@ -1696,7 +2019,7 @@ def contribution_plot( zoom, ) else: - fig = self.plot_violin( + fig = self._plot_violin( feature_values, contrib, col_label, @@ -1719,6 +2042,7 @@ def contribution_plot( def features_importance( self, + mode="global", max_features=20, page="top", selection=None, @@ -1731,54 +2055,70 @@ def features_importance( file_name=None, auto_open=False, zoom=False, + normalize_by_nb_samples=False, + degree="slider", ): """ - features_importance display a plotly features importance plot. - in Multiclass Case, this features_importance focus on a label value. - User specifies the label value using label parameter. - the selection parameter allows the user to compare a subset to the global features - importance - features_importance tutorial offers several examples - (please check tutorial part of this doc) + Display a Plotly feature importance plot. + + This method generates a feature importance plot for both classification and regression models. + For multiclass classification, the plot will focus on the specified `label`. + Parameters ---------- - max_features: int (optional, default 20) - this argument limit the number of hbar in features importance plot - if max_features is 20, plot selects the 20 most important features - page: int, str (optional, default "top") - enable to select the features to plot between "top", "worse" or the number of the page - selection: list (optional, default None) - This argument allows to represent the importance calculated with a subset. - Subset features importance is compared to global in the plot - Argument must contains list of index, subset of the input DataFrame that we want to plot - label: integer or string (default -1) - If the label is of string type, check if it can be changed to integer to select the - good dataframe object. - group_name : str (optional, default None) - Allows to display the features importance of the variables that are grouped together - inside a group of features. - This parameter is only available if the SmartExplainer object has been compiled using - the features_groups optional parameter and should correspond to a key of - features_groups dictionary. - display_groups : bool (default True) - If groups of features are declared in SmartExplainer object, this parameter allows to - specify whether or not to display them. - force: bool (optional, default False) - force == True, force the compute features importance if it's already done - width : Int (default: 900) - Plotly figure - layout width - height : Int (default: 500) - Plotly figure - layout height - file_name: string (optional) - File name to use to save the plotly bar chart. If None the bar chart will not be saved. - auto_open: Boolean (optional) - Indicate whether to open the bar plot or not. - zoom: bool (default=False) - graph is currently zoomed + mode : str, optional, default: 'global' + Defines the type of plot to display. + - 'global': Displays the feature importance plot from a global perspective. + - 'global-local': Shows the global feature importance plot with local importance indicators. + - 'cumulative': Shows the cumulative sum of feature contributions, ordered by descending importance. + max_features : int, optional, default: 20 + Limits the number of features to display in the plot. + For example, `max_features=20` will display the 20 most important features. + page : int or str, optional, default: 'top' + Allows the user to select which set of features to display. + - 'top': Shows the most important features. + - 'worst': Shows the least important features. + - Page number (integer) allows navigation between different sets of features. + selection : list, optional, default: None + Specifies a subset of features to compare to the global feature importance. + This is only applicable when `mode` is set to 'global'. If provided, the list must contain + indices corresponding to the subset of features to be displayed. + label : int or str, optional, default: -1 + Specifies the label for which to display feature importance in multiclass classification. + If a string label is provided, it will be converted to an integer if applicable. + group_name : str, optional, default: None + Displays feature importance for a specific group of features. + This is only available if the `SmartExplainer` object has been compiled with feature groups. + The group name must correspond to a key in the `features_groups` dictionary. + display_groups : bool, optional, default: True + If feature groups are declared in the `SmartExplainer` object, this parameter specifies + whether or not to display them in the plot. + force : bool, optional, default: False + If `True`, forces recomputation of feature importance, even if it has already been computed. + width : int, optional, default: 900 + The width of the Plotly figure layout. + height : int, optional, default: 500 + The height of the Plotly figure layout. + file_name : str, optional + The name of the file to save the Plotly bar chart. + If `None`, the chart will not be saved. + auto_open : bool, optional + If `True`, automatically opens the generated plot. + zoom : bool, optional, default: False + Indicates whether the graph is currently zoomed in. + normalize_by_nb_samples : bool, optional, default: False + Normalizes feature importance by the number of samples. + This is only applicable when `mode` is set to 'cumulative'. + degree : int, optional, default: 0 + Degree of adjustment to apply to the cumulative feature contributions curve. + This is only applicable when `mode` is set to 'cumulative'. + Returns ------- - Plotly Figure Object - Example + plotly.graph_objs._figure.Figure + The generated Plotly figure object containing the feature importance plot. + + Examples -------- >>> xpl.plot.features_importance() """ @@ -1800,97 +2140,246 @@ def get_feature_importance_page(features_importance, page, max_features): else: raise ValueError("Invalid value for page. It must be 'top', 'worst', or an integer.") - self.explainer.compute_features_import(force=force) - subtitle = None - title = "Features Importance" - display_groups = self.explainer.features_groups is not None and display_groups + # Compute the feature importance based on mode + self._explainer.compute_features_import(force=force, local=(mode == "global-local")) + + # Determine title based on the mode + titles = { + "global": "Feature Importance", + "global-local": "Global and Local Feature Importance", + "cumulative": "Cumulative Feature Contribution Curve", + } + title = titles.get(mode, "Feature Importance") + + # Check if feature groups should be displayed + display_groups = self._explainer.features_groups is not None and display_groups + + # Handle feature groups and group-specific cases + local_imp_lev1, local_imp_lev2 = None, None if display_groups: if group_name: # Case where we have groups of features and we want to display only features inside a group - if group_name not in self.explainer.features_groups.keys(): + if group_name not in self._explainer.features_groups.keys(): raise ValueError( f"group_name parameter : {group_name} is not in features_groups keys. " - f"Possible values are : {list(self.explainer.features_groups.keys())}" + f"Possible values are : {list(self._explainer.features_groups.keys())}" ) - title += f" - {truncate_str(self.explainer.features_dict.get(group_name), 20)}" - if isinstance(self.explainer.features_imp, list): + title += f" - {truncate_str(self._explainer.features_dict.get(group_name), 20)}" + if isinstance(self._explainer.features_imp, list): features_importance = [ - label_feat_imp.loc[label_feat_imp.index.isin(self.explainer.features_groups[group_name])] - for label_feat_imp in self.explainer.features_imp + label_feat_imp.loc[label_feat_imp.index.isin(self._explainer.features_groups[group_name])] + for label_feat_imp in self._explainer.features_imp ] + if mode == "global-local": + local_imp_lev1 = [ + label_feat_imp.loc[label_feat_imp.index.isin(self._explainer.features_groups[group_name])] + for label_feat_imp in self._explainer.features_imp_local_lev1 + ] + local_imp_lev2 = [ + label_feat_imp.loc[label_feat_imp.index.isin(self._explainer.features_groups[group_name])] + for label_feat_imp in self._explainer.features_imp_local_lev2 + ] else: - features_importance = self.explainer.features_imp.loc[ - self.explainer.features_imp.index.isin(self.explainer.features_groups[group_name]) - ] - contributions = self.explainer.contributions + index = self._explainer.features_imp.index.isin(self._explainer.features_groups[group_name]) + features_importance = self._explainer.features_imp.loc[index] + if mode == "global-local": + local_imp_lev1 = self._explainer.features_imp_local_lev1.loc[index] + local_imp_lev2 = self._explainer.features_imp_local_lev2.loc[index] + contributions = self._explainer.contributions else: - features_importance = self.explainer.features_imp_groups - contributions = self.explainer.contributions_groups + features_importance = self._explainer.features_imp_groups + if mode == "global-local": + local_imp_lev1 = self._explainer.features_imp_groups_local_lev1 + local_imp_lev2 = self._explainer.features_imp_groups_local_lev2 + contributions = self._explainer.contributions_groups else: - features_importance = self.explainer.features_imp - contributions = self.explainer.contributions - - # classification - if self.explainer._case == "classification": - label_num, _, label_value = self.explainer.check_label_name(label) - global_feat_imp = get_feature_importance_page(features_importance[label_num], page, max_features) - if selection is not None: - subset_feat_imp = self.explainer.backend.get_global_features_importance( - contributions=contributions[label_num], explain_data=self.explainer.explain_data, subset=selection - ) - else: - subset_feat_imp = None + features_importance = self._explainer.features_imp + if mode == "global-local": + local_imp_lev1 = self._explainer.features_imp_local_lev1 + local_imp_lev2 = self._explainer.features_imp_local_lev2 + contributions = self._explainer.contributions + + subtitle = "" + + # Classification case + if self._explainer._case == "classification": + label_num, _, label_value = self._explainer.check_label_name(label) + features_importance_case = features_importance[label_num] + contributions_case = contributions[label_num] subtitle = f"Response: {label_value}" - # regression - elif self.explainer._case == "regression": - global_feat_imp = get_feature_importance_page(features_importance, page, max_features) - if selection is not None: - subset_feat_imp = self.explainer.backend.get_global_features_importance( - contributions=contributions, explain_data=self.explainer.explain_data, subset=selection - ) - else: - subset_feat_imp = None - addnote = "" + # Regression case + elif self._explainer._case == "regression": + label_num = None + features_importance_case = features_importance + contributions_case = contributions + else: + raise ValueError("Invalid case. Case must be either 'classification' or 'regression'.") + + global_feat_imp = get_feature_importance_page(features_importance_case, page, max_features) + + if mode == "global-local": + local_imp_lev1, local_imp_lev2 = self._get_local_feature_importance( + global_feat_imp.index, local_imp_lev1, local_imp_lev2, label_num + ) + subset_feat_imp = self._get_subset_importance(contributions_case, selection) if subset_feat_imp is not None: subset_feat_imp = subset_feat_imp.reindex(global_feat_imp.index) - subset_feat_imp.index = subset_feat_imp.index.map(self.explainer.features_dict) + subset_feat_imp.index = subset_feat_imp.index.map(self._explainer.features_dict) if subset_feat_imp.dropna().shape[0] == 0: raise ValueError("selection argument doesn't return any row") + + addnote = self._build_additional_notes(subset_feat_imp, selection, max_features) + + # Map feature names + global_feat_imp.index = global_feat_imp.index.map(self._explainer.features_dict) + if mode == "global-local": + local_imp_lev1.index = local_imp_lev1.index.map(self._explainer.features_dict) + local_imp_lev2.index = local_imp_lev2.index.map(self._explainer.features_dict) + + # Format indices if display_groups is enabled + if display_groups: + global_feat_imp, local_imp_lev1, local_imp_lev2, subset_feat_imp = self._apply_bold_formatting( + global_feat_imp, local_imp_lev1, local_imp_lev2, subset_feat_imp, mode + ) + + # Generate and return the plot + return self._generate_feature_importance_plot( + mode, + global_feat_imp, + contributions_case, + local_imp_lev1, + local_imp_lev2, + subset_feat_imp, + title, + addnote, + subtitle, + width, + height, + file_name, + auto_open, + zoom, + normalize_by_nb_samples, + degree, + ) + + def _get_group_feature_importance(self, group_name): + """Retrieve the feature importance for a specific group of features.""" + if isinstance(self._explainer.features_imp, list): + return [ + label_feat_imp.loc[label_feat_imp.index.isin(self._explainer.features_groups[group_name])] + for label_feat_imp in self._explainer.features_imp + ] + return self._explainer.features_imp.loc[ + self._explainer.features_imp.index.isin(self._explainer.features_groups[group_name]) + ] + + def _get_local_feature_importance(self, indices, local_imp_lev1, local_imp_lev2, label_num=None): + """Retrieve local feature importance for global-local mode.""" + if label_num is not None: + local_imp_lev1 = local_imp_lev1[label_num].loc[indices] + local_imp_lev2 = local_imp_lev2[label_num].loc[indices] + else: + local_imp_lev1 = local_imp_lev1.loc[indices] + local_imp_lev2 = local_imp_lev2.loc[indices] + return local_imp_lev1, local_imp_lev2 + + def _get_subset_importance(self, contributions, selection): + """Retrieve feature importance for a subset of features, if specified.""" + if selection is not None: + return self._explainer.backend.get_global_features_importance( + contributions=contributions, explain_data=self._explainer.explain_data, subset=selection + ) + return None + + def _build_additional_notes(self, subset_feat_imp, selection, max_features): + """Generate additional notes to display in the plot.""" + addnote = "" + if subset_feat_imp is not None: subset_len = len(selection) - total_len = self.explainer.x_init.shape[0] + total_len = self._explainer.x_init.shape[0] addnote = add_text( [addnote, f"Subset length: {subset_len} ({int(np.round(100 * subset_len / total_len))}%)"], sep=" - " ) - if self.explainer.x_init.shape[1] >= max_features: - addnote = add_text([addnote, f"Total number of features: {int(self.explainer.x_init.shape[1])}"], sep=" - ") + if self._explainer.x_init.shape[1] >= max_features: + addnote = add_text( + [addnote, f"Total number of features: {int(self._explainer.x_init.shape[1])}"], sep=" - " + ) + return addnote - global_feat_imp.index = global_feat_imp.index.map(self.explainer.features_dict) - if display_groups: - # Bold font for groups of features - global_feat_imp.index = [ - ( - "" + str(f) - if self.explainer.inv_features_dict.get(f) in self.explainer.features_groups.keys() - else str(f) - ) - for f in global_feat_imp.index - ] - if subset_feat_imp is not None: - subset_feat_imp.index = [ - ( - "" + str(f) - if self.explainer.inv_features_dict.get(f) in self.explainer.features_groups.keys() - else str(f) - ) - for f in subset_feat_imp.index - ] + def _apply_bold_formatting(self, global_feat_imp, local_imp_lev1, local_imp_lev2, subset_feat_imp, mode): + """Apply bold formatting to feature names for feature groups.""" - fig = self.plot_features_import( - global_feat_imp, subset_feat_imp, title, addnote, subtitle, width, height, file_name, auto_open, zoom - ) - return fig + def bold_feature_name(index): + feature_name = str(index) + if self._explainer.inv_features_dict.get(index) in self._explainer.features_groups: + return f"{feature_name}" + return feature_name + + global_feat_imp.index = [bold_feature_name(f) for f in global_feat_imp.index] + + if mode == "global-local": + local_imp_lev1.index = [bold_feature_name(f) for f in global_feat_imp.index] + local_imp_lev2.index = [bold_feature_name(f) for f in global_feat_imp.index] + if subset_feat_imp is not None: + subset_feat_imp.index = [bold_feature_name(f) for f in subset_feat_imp.index] + return global_feat_imp, local_imp_lev1, local_imp_lev2, subset_feat_imp - def plot_line_comparison( + def _generate_feature_importance_plot( + self, + mode, + global_feat_imp, + contributions_case, + local_imp_lev1=None, + local_imp_lev2=None, + subset_feat_imp=None, + title="", + addnote="", + subtitle="", + width=900, + height=500, + file_name=None, + auto_open=False, + zoom=False, + normalize_by_nb_samples=False, + degree="slider", + ): + """Generate the feature importance plot based on the mode.""" + if mode == "global": + return self._plot_features_import( + global_feat_imp, subset_feat_imp, title, addnote, subtitle, width, height, file_name, auto_open, zoom + ) + elif mode == "global-local": + feat_imp = {"global": global_feat_imp, "semi-local": local_imp_lev1, "local": local_imp_lev2} + return self._plot_local_features_import( + feat_imp, + title, + addnote, + subtitle, + width, + height, + file_name, + auto_open, + zoom, + ) + elif mode == "cumulative": + return self._plot_feature_contributions_cumulative( + global_feat_imp, + contributions_case, + title, + addnote, + subtitle, + width, + height, + normalize_by_nb_samples, + degree, + file_name, + auto_open, + zoom, + ) + else: + raise ValueError("Invalid value for mode. It must be 'global', 'global-local', or 'cumulative'.") + + def _plot_line_comparison( self, index, feature_values, @@ -2073,14 +2562,14 @@ def compare_plot( line_reference = [] if index is not None: for ident in index: - if ident in self.explainer.x_init.index: + if ident in self._explainer.x_init.index: line_reference.append(ident) elif row_num is not None: line_reference = [ - self.explainer.x_init.index.values[row_nb_reference] + self._explainer.x_init.index.values[row_nb_reference] for row_nb_reference in row_num - if self.explainer.x_init.index.values[row_nb_reference] in self.explainer.x_init.index + if self._explainer.x_init.index.values[row_nb_reference] in self._explainer.x_init.index ] subtitle = "" @@ -2088,15 +2577,15 @@ def compare_plot( raise ValueError("No matching entry for index") # Classification case - if self.explainer._case == "classification": + if self._explainer._case == "classification": if label is None: label = -1 - label_num, _, label_value = self.explainer.check_label_name(label) - contrib = self.explainer.contributions[label_num] + label_num, _, label_value = self._explainer.check_label_name(label) + contrib = self._explainer.contributions[label_num] if show_predict: - preds = [self.local_pred(line, label_num) for line in line_reference] + preds = [self._local_pred(line, label_num) for line in line_reference] subtitle = ( f"Response: {label_value} - " + "Probas: " @@ -2106,11 +2595,11 @@ def compare_plot( ) # Regression case - elif self.explainer._case == "regression": - contrib = self.explainer.contributions + elif self._explainer._case == "regression": + contrib = self._explainer.contributions if show_predict: - preds = [self.local_pred(line) for line in line_reference] + preds = [self._local_pred(line) for line in line_reference] subtitle = "Predictions: " + " ; ".join( [str(id) + ": " + str(round(pred, 2)) + "" for id, pred in zip(line_reference, preds)] ) @@ -2122,13 +2611,13 @@ def compare_plot( # Well labels if available feature_values = [0] * len(contrib.columns) - if hasattr(self.explainer, "columns_dict"): + if hasattr(self._explainer, "columns_dict"): for i, name in enumerate(contrib.columns): - feature_name = self.explainer.features_dict[name] + feature_name = self._explainer.features_dict[name] feature_values[i] = feature_name - preds = [self.explainer.x_init.loc[id] for id in line_reference] - dict_features = self.explainer.inv_features_dict + preds = [self._explainer.x_init.loc[id] for id in line_reference] + dict_features = self._explainer.inv_features_dict iteration_list = list(zip(new_contrib, feature_values)) iteration_list.sort(key=lambda x: maximum_difference_sort_value(x), reverse=True) @@ -2136,7 +2625,7 @@ def compare_plot( iteration_list = iteration_list[::-1] new_contrib, feature_values = list(zip(*iteration_list)) - fig = self.plot_line_comparison( + fig = self._plot_line_comparison( line_reference, feature_values, new_contrib, @@ -2348,10 +2837,10 @@ def _select_indices_interactions_plot(self, selection, max_points): # interaction_selection attribute is used to store already computed indices of interaction_values if hasattr(self, "interaction_selection"): list_ind = self.interaction_selection - elif self.explainer.x_init.shape[0] <= max_points: - list_ind = self.explainer.x_init.index.tolist() + elif self._explainer.x_init.shape[0] <= max_points: + list_ind = self._explainer.x_init.index.tolist() else: - list_ind = random.sample(self.explainer.x_init.index.tolist(), max_points) + list_ind = random.sample(self._explainer.x_init.index.tolist(), max_points) addnote = "Length of random Subset : " elif isinstance(selection, list): if len(selection) <= max_points: @@ -2364,7 +2853,7 @@ def _select_indices_interactions_plot(self, selection, max_points): list_ind = random.sample(selection, max_points) addnote = "Length of random Subset : " else: - ValueError("parameter selection must be a list") + raise ValueError("parameter selection must be a list") self.interaction_selection = list_ind return list_ind, addnote @@ -2421,36 +2910,36 @@ def interactions_plot( if not (isinstance(col1, (str, int)) or isinstance(col2, (str, int))): raise ValueError("parameters col1 and col2 must be string or int.") - col_id1 = self.explainer.check_features_name([col1])[0] - col_name1 = self.explainer.columns_dict[col_id1] + col_id1 = self._explainer.check_features_name([col1])[0] + col_name1 = self._explainer.columns_dict[col_id1] - col_id2 = self.explainer.check_features_name([col2])[0] - col_name2 = self.explainer.columns_dict[col_id2] + col_id2 = self._explainer.check_features_name([col2])[0] + col_name2 = self._explainer.columns_dict[col_id2] - col_value_count1 = self.explainer.features_desc[col_name1] + col_value_count1 = self._explainer.features_desc[col_name1] list_ind, addnote = self._select_indices_interactions_plot(selection=selection, max_points=max_points) if addnote is not None: addnote = add_text( - [addnote, f"{len(list_ind)} ({int(np.round(100 * len(list_ind) / self.explainer.x_init.shape[0]))}%)"], + [addnote, f"{len(list_ind)} ({int(np.round(100 * len(list_ind) / self._explainer.x_init.shape[0]))}%)"], sep="", ) # Subset - if self.explainer.postprocessing_modifications: - feature_values1 = self.explainer.x_contrib_plot.loc[list_ind, col_name1].to_frame() - feature_values2 = self.explainer.x_contrib_plot.loc[list_ind, col_name2].to_frame() + if self._explainer.postprocessing_modifications: + feature_values1 = self._explainer.x_contrib_plot.loc[list_ind, col_name1].to_frame() + feature_values2 = self._explainer.x_contrib_plot.loc[list_ind, col_name2].to_frame() else: - feature_values1 = self.explainer.x_init.loc[list_ind, col_name1].to_frame() - feature_values2 = self.explainer.x_init.loc[list_ind, col_name2].to_frame() + feature_values1 = self._explainer.x_init.loc[list_ind, col_name1].to_frame() + feature_values2 = self._explainer.x_init.loc[list_ind, col_name2].to_frame() - interaction_values = self.explainer.get_interaction_values(selection=list_ind)[:, col_id1, col_id2] + interaction_values = self._explainer.get_interaction_values(selection=list_ind)[:, col_id1, col_id2] if col_id1 != col_id2: interaction_values = interaction_values * 2 # add break line to X label if necessary - max_len_by_row = max([round(50 / self.explainer.features_desc[feature_values1.columns.values[0]]), 8]) + max_len_by_row = max([round(50 / self._explainer.features_desc[feature_values1.columns.values[0]]), 8]) feature_values1.iloc[:, 0] = feature_values1.iloc[:, 0].apply( add_line_break, args=( @@ -2542,7 +3031,7 @@ def top_interactions_plot( list_ind, addnote = self._select_indices_interactions_plot(selection=selection, max_points=max_points) - interaction_values = self.explainer.get_interaction_values(selection=list_ind) + interaction_values = self._explainer.get_interaction_values(selection=list_ind) sorted_top_features_indices = compute_sorted_variables_interactions_list_indices(interaction_values) @@ -2553,8 +3042,8 @@ def top_interactions_plot( id0, id1 = ids fig_one_interaction = self.interactions_plot( - col1=self.explainer.columns_dict[id0], - col2=self.explainer.columns_dict[id1], + col1=self._explainer.columns_dict[id0], + col2=self._explainer.columns_dict[id1], selection=selection, violin_maxf=violin_maxf, max_points=max_points, @@ -2581,7 +3070,7 @@ def generate_title_dict(col_name1, col_name2, addnote): fig.layout.coloraxis.colorscale = self._style_dict["interactions_col_scale"] fig.update_layout( - xaxis_title=self.explainer.columns_dict[sorted_top_features_indices[0][0]], + xaxis_title=self._explainer.columns_dict[sorted_top_features_indices[0][0]], yaxis_title="Shap interaction value", updatemenus=[ dict( @@ -2589,7 +3078,7 @@ def generate_title_dict(col_name1, col_name2, addnote): buttons=list( [ dict( - label=f"{self.explainer.columns_dict[i]} - {self.explainer.columns_dict[j]}", + label=f"{self._explainer.columns_dict[i]} - {self._explainer.columns_dict[j]}", method="update", args=[ { @@ -2602,17 +3091,17 @@ def generate_title_dict(col_name1, col_name2, addnote): { "xaxis": { "title": { - **{"text": self.explainer.columns_dict[i]}, + **{"text": self._explainer.columns_dict[i]}, **self._style_dict["dict_xaxis"], } }, - "legend": {"title": {"text": self.explainer.columns_dict[j]}}, + "legend": {"title": {"text": self._explainer.columns_dict[j]}}, "coloraxis": { - "colorbar": {"title": {"text": self.explainer.columns_dict[j]}}, + "colorbar": {"title": {"text": self._explainer.columns_dict[j]}}, "colorscale": fig.layout.coloraxis.colorscale, }, "title": generate_title_dict( - self.explainer.columns_dict[i], self.explainer.columns_dict[j], addnote + self._explainer.columns_dict[i], self._explainer.columns_dict[j], addnote ), }, ], @@ -2644,8 +3133,8 @@ def generate_title_dict(col_name1, col_name2, addnote): self._update_interactions_fig( fig=fig, - col_name1=self.explainer.columns_dict[sorted_top_features_indices[0][0]], - col_name2=self.explainer.columns_dict[sorted_top_features_indices[0][1]], + col_name1=self._explainer.columns_dict[sorted_top_features_indices[0][0]], + col_name2=self._explainer.columns_dict[sorted_top_features_indices[0][1]], addnote=addnote, width=width, height=height, @@ -2759,7 +3248,7 @@ def cluster_corr(corr, degree, inplace=False): if df is None: # Use x_init by default - df = self.explainer.x_init.copy() + df = self._explainer.x_init.copy() if optimized: categorical_columns = df.select_dtypes(include=["object", "category"]).columns @@ -2809,8 +3298,8 @@ def cluster_corr(corr, degree, inplace=False): coloraxis="coloraxis", text=[ [ - f"Feature 1: {self.explainer.features_dict.get(y, y)}
" - f"Feature 2: {self.explainer.features_dict.get(x, x)}" + f"Feature 1: {self._explainer.features_dict.get(y, y)}
" + f"Feature 2: {self._explainer.features_dict.get(x, x)}" for x in list_features ] for y in list_features @@ -2839,8 +3328,8 @@ def cluster_corr(corr, degree, inplace=False): coloraxis="coloraxis", text=[ [ - f"Feature 1: {self.explainer.features_dict.get(y, y)}
" - f"Feature 2: {self.explainer.features_dict.get(x, x)}" + f"Feature 1: {self._explainer.features_dict.get(y, y)}
" + f"Feature 2: {self._explainer.features_dict.get(x, x)}" for x in list_features ] for y in list_features @@ -2872,7 +3361,7 @@ def cluster_corr(corr, degree, inplace=False): return fig - def plot_amplitude_vs_stability(self, mean_variability, mean_amplitude, column_names, file_name, auto_open): + def _plot_amplitude_vs_stability(self, mean_variability, mean_amplitude, column_names, file_name, auto_open): """ Intermediate function used to display the stability plot when plot_type is "none" Parameters @@ -2897,7 +3386,7 @@ def plot_amplitude_vs_stability(self, mean_variability, mean_amplitude, column_n + "
(standard deviation / mean)
" ) yaxis_title = "Importance
(Average contributions)
" - col_scale, _, _ = self.tuning_colorscale(pd.DataFrame(mean_amplitude)) + col_scale, _, _ = self._tuning_colorscale(pd.DataFrame(mean_amplitude)) hv_text = [ f"Feature: {col}
Importance: {y}
Variability: {x}" for col, x, y in zip(column_names, mean_variability, mean_amplitude) @@ -2934,7 +3423,7 @@ def plot_amplitude_vs_stability(self, mean_variability, mean_amplitude, column_n ) return fig - def plot_stability_distribution( + def _plot_stability_distribution( self, variability, plot_type, mean_amplitude, dataset, column_names, file_name, auto_open ): """ @@ -2971,7 +3460,7 @@ def plot_stability_distribution( var_df = var_df[column_names[mean_amplitude.argsort()]] # Add colorscale - col_scale, _, _ = self.tuning_colorscale(pd.DataFrame(mean_amplitude)) + col_scale, _, _ = self._tuning_colorscale(pd.DataFrame(mean_amplitude)) color_list = mean_amplitude_normalized.tolist() color_list.sort() color_list = [next(pair[1] for pair in col_scale if x <= pair[0]) for x in color_list] @@ -3154,17 +3643,17 @@ def local_neighbors_plot(self, index, max_features=10, file_name=None, auto_open fig The figure that will be displayed """ - assert index in self.explainer.x_init.index, "index must exist in pandas dataframe" + assert index in self._explainer.x_init.index, "index must exist in pandas dataframe" - self.explainer.compute_features_stability([index]) + self._explainer.compute_features_stability([index]) - column_names = np.array([self.explainer.features_dict.get(x) for x in self.explainer.x_init.columns]) + column_names = np.array([self._explainer.features_dict.get(x) for x in self._explainer.x_init.columns]) def ordinal(n): return "%d%s" % (n, "tsnrhtdd"[(math.floor(n / 10) % 10 != 1) * (n % 10 < 4) * n % 10 :: 4]) # Compute explanations for instance and neighbors - g = self.explainer.local_neighbors["norm_shap"] + g = self._explainer.local_neighbors["norm_shap"] # Reorder indices based on absolute values of the 1st row (i.e. the instance) in descending order inds = np.flip(np.abs(g[0, :]).argsort()) @@ -3291,16 +3780,16 @@ def stability_plot( """ # Sampling if selection is None: - if self.explainer.x_init.shape[0] <= max_points: - list_ind = self.explainer.x_init.index.tolist() + if self._explainer.x_init.shape[0] <= max_points: + list_ind = self._explainer.x_init.index.tolist() else: - list_ind = random.sample(self.explainer.x_init.index.tolist(), max_points) + list_ind = random.sample(self._explainer.x_init.index.tolist(), max_points) # By default, don't compute calculation if it has already been done - if (self.explainer.features_stability is None) or self.last_stability_selection or force: - self.explainer.compute_features_stability(list_ind) + if (self._explainer.features_stability is None) or self._last_stability_selection or force: + self._explainer.compute_features_stability(list_ind) else: print("Computed values from previous call are used") - self.last_stability_selection = False + self._last_stability_selection = False elif isinstance(selection, list): if len(selection) == 1: raise ValueError("Selection must include multiple points") @@ -3309,22 +3798,24 @@ def stability_plot( f"Size of selection is bigger than max_points (default: {max_points}).\ Computation time might be affected" ) - self.explainer.compute_features_stability(selection) - self.last_stability_selection = True + self._explainer.compute_features_stability(selection) + self._last_stability_selection = True else: raise ValueError("Parameter selection must be a list") - column_names = np.array([self.explainer.features_dict.get(x) for x in self.explainer.x_init.columns]) + column_names = np.array([self._explainer.features_dict.get(x) for x in self._explainer.x_init.columns]) - variability = self.explainer.features_stability["variability"] - amplitude = self.explainer.features_stability["amplitude"] + variability = self._explainer.features_stability["variability"] + amplitude = self._explainer.features_stability["amplitude"] mean_variability = variability.mean(axis=0) mean_amplitude = amplitude.mean(axis=0) # Plot 1 : only show average variability on y-axis if distribution not in ["boxplot", "violin"]: - fig = self.plot_amplitude_vs_stability(mean_variability, mean_amplitude, column_names, file_name, auto_open) + fig = self._plot_amplitude_vs_stability( + mean_variability, mean_amplitude, column_names, file_name, auto_open + ) # Plot 2 : Show distribution of variability else: @@ -3336,10 +3827,10 @@ def stability_plot( variability = variability[:, keep] mean_amplitude = mean_amplitude[keep] - dataset = self.explainer.x_init.iloc[:, keep] + dataset = self._explainer.x_init.iloc[:, keep] column_names = column_names[keep] - fig = self.plot_stability_distribution( + fig = self._plot_stability_distribution( variability, distribution, mean_amplitude, dataset, column_names, file_name, auto_open ) @@ -3384,13 +3875,13 @@ def compacity_plot( """ # Sampling if selection is None: - if self.explainer.x_init.shape[0] <= max_points: - list_ind = self.explainer.x_init.index.tolist() + if self._explainer.x_init.shape[0] <= max_points: + list_ind = self._explainer.x_init.index.tolist() else: - list_ind = random.sample(self.explainer.x_init.index.tolist(), max_points) + list_ind = random.sample(self._explainer.x_init.index.tolist(), max_points) # By default, don't compute calculation if it has already been done - if (self.explainer.features_compacity is None) or self.last_compacity_selection or force: - self.explainer.compute_features_compacity(list_ind, 1 - approx, nb_features) + if (self._explainer.features_compacity is None) or self.last_compacity_selection or force: + self._explainer.compute_features_compacity(list_ind, 1 - approx, nb_features) else: print("Computed values from previous call are used") self.last_compacity_selection = False @@ -3400,13 +3891,13 @@ def compacity_plot( f"Size of selection is bigger than max_points (default: {max_points}).\ Computation time might be affected" ) - self.explainer.compute_features_compacity(selection, 1 - approx, nb_features) - self.last_compacity_selection = True + self._explainer.compute_features_compacity(selection, 1 - approx, nb_features) + self._last_compacity_selection = True else: raise ValueError("Parameter selection must be a list") - features_needed = self.explainer.features_compacity["features_needed"] - distance_reached = self.explainer.features_compacity["distance_reached"] + features_needed = self._explainer.features_compacity["features_needed"] + distance_reached = self._explainer.features_compacity["distance_reached"] # Make plots fig = make_subplots( @@ -3531,17 +4022,17 @@ def scatter_plot_prediction( auto_open: bool (default=False) open automatically the plot """ - if self.explainer.y_target is not None: + if self._explainer.y_target is not None: # Sampling - list_ind, addnote = self.explainer.plot._subset_sampling(selection, max_points) + list_ind, addnote = self._explainer.plot._subset_sampling(selection, max_points) # Classification Case - if self.explainer._case == "classification": - fig, subtitle = self.explainer.plot._prediction_classification_plot(list_ind, label) + if self._explainer._case == "classification": + fig, subtitle = self._explainer.plot._prediction_classification_plot(list_ind, label) # Regression Case - elif self.explainer._case == "regression": - fig, subtitle = self.explainer.plot._prediction_regression_plot(list_ind) + elif self._explainer._case == "regression": + fig, subtitle = self._explainer.plot._prediction_regression_plot(list_ind) # Add traces, title and template title = "True Values Vs Predicted Values" @@ -3615,17 +4106,17 @@ def _prediction_classification_plot( """ fig = go.Figure() - label_num, _, label_value = self.explainer.check_label_name(label) + label_num, _, label_value = self._explainer.check_label_name(label) # predict proba Color scale - if self.explainer.proba_values is not None: + if self._explainer.proba_values is not None: # Assign proba values of the target - df_proba_target = self.explainer.proba_values.copy() + df_proba_target = self._explainer.proba_values.copy() df_proba_target["proba_target"] = df_proba_target.iloc[:, label_num] proba_values = df_proba_target[["proba_target"]] # Proba subset: proba_values = proba_values.loc[list_ind, :] - target = self.explainer.y_target.loc[list_ind, :] - y_pred = self.explainer.y_pred.loc[list_ind, :] + target = self._explainer.y_target.loc[list_ind, :] + y_pred = self._explainer.y_pred.loc[list_ind, :] df_pred = pd.concat( [proba_values.reset_index(), y_pred.reset_index(drop=True), target.reset_index(drop=True)], axis=1 ) @@ -3706,13 +4197,13 @@ def _prediction_classification_plot( ) fig.update_layout(violingap=0, violinmode="overlay") - if self.explainer.label_dict is not None: + if self._explainer.label_dict is not None: fig.update_xaxes( tickmode="array", tickvals=list(df_pred["target"].unique()), - ticktext=list(df_pred["target"].apply(lambda x: self.explainer.label_dict[x]).unique()), + ticktext=list(df_pred["target"].apply(lambda x: self._explainer.label_dict[x]).unique()), ) - if self.explainer.label_dict is None: + if self._explainer.label_dict is None: fig.update_xaxes(tickvals=sorted(list(df_pred["target"].unique()))) return fig, subtitle @@ -3736,9 +4227,9 @@ def _prediction_regression_plot( fig = go.Figure() subtitle = None - prediction_error = self.explainer.prediction_error + prediction_error = self._explainer.prediction_error if prediction_error is not None: - if (self.explainer.y_target == 0).any().iloc[0]: + if (self._explainer.y_target == 0).any().iloc[0]: subtitle = "Prediction Error = abs(True Values - Predicted Values)" else: subtitle = "Prediction Error = abs(True Values - Predicted Values) / True Values" @@ -3747,9 +4238,9 @@ def _prediction_regression_plot( equal_bins = np.unique(equal_bins) bins_list = [i for i in equal_bins] values = pd.DataFrame(pd.cut([val[0] for val in prediction_error.values], bins=bins_list, labels=False)) - col_scale, _, _ = self.tuning_colorscale(values, keep_90_pct=False) + col_scale, _, _ = self._tuning_colorscale(values, keep_90_pct=False) - y_target = self.explainer.y_target.loc[list_ind] + y_target = self._explainer.y_target.loc[list_ind] if len(y_target) > 500: lower_quantile = y_target.iloc[:, 0].quantile(0.005) upper_quantile = y_target.iloc[:, 0].quantile(0.995) @@ -3769,7 +4260,7 @@ def _prediction_regression_plot( y_target_values = y_target.values.flatten() - y_pred = self.explainer.y_pred.loc[y_target.index] + y_pred = self._explainer.y_pred.loc[y_target.index] prediction_error = np.array(prediction_error.loc[y_target.index]) feature_values_array = y_target_values @@ -3804,9 +4295,9 @@ def _prediction_regression_plot( fig.add_trace(density_plot) # round predict - if self.round_digit is None: - self.tuning_round_digit() - y_pred = y_pred.map(lambda x: round(x, self.round_digit)) + if self._round_digit is None: + self._tuning_round_digit() + y_pred = y_pred.map(lambda x: round(x, self._round_digit)) y_pred_flatten = y_pred.values.flatten() hv_text = [ @@ -3911,14 +4402,14 @@ def _no_selection_sampling(self, max_points, col, col_value_count, random_seed): """ Handles sampling when no specific selection is made. """ - if self.explainer.x_init.shape[0] <= max_points: - return self.explainer.x_init.index.tolist(), None + if self._explainer.x_init.shape[0] <= max_points: + return self._explainer.x_init.index.tolist(), None elif col is None: - selected_indices = random.sample(self.explainer.x_init.index.tolist(), max_points) + selected_indices = random.sample(self._explainer.x_init.index.tolist(), max_points) return selected_indices, "Length of random Subset: " else: selected_indices = self._intelligent_sampling( - self.explainer.x_init, max_points, col, col_value_count, random_seed + self._explainer.x_init, max_points, col, col_value_count, random_seed ) return selected_indices, "Length of smart Subset: " @@ -3932,7 +4423,7 @@ def _list_selection_sampling(self, selection, max_points, col, col_value_count, selected_indices = random.sample(selection, max_points) return selected_indices, "Length of random Subset: " else: - subset = self.explainer.x_init.loc[selection] + subset = self._explainer.x_init.loc[selection] selected_indices = self._intelligent_sampling(subset, max_points, col, col_value_count, random_seed) return selected_indices, "Length of smart Subset: " @@ -3968,5 +4459,5 @@ def _format_additional_note(self, selected_indices, additional_note): """ Formats the additional note with the length and percentage of the selected subset. """ - percentage = int(np.round(100 * len(selected_indices) / self.explainer.x_init.shape[0])) + percentage = int(np.round(100 * len(selected_indices) / self._explainer.x_init.shape[0])) return f"{additional_note}{len(selected_indices)} ({percentage}%)" diff --git a/shapash/explainer/smart_state.py b/shapash/explainer/smart_state.py index b9e78364..05d3b12a 100644 --- a/shapash/explainer/smart_state.py +++ b/shapash/explainer/smart_state.py @@ -301,11 +301,11 @@ def summarize(self, s_contrib, var_dict, x_sorted, mask, columns_dict, features_ """ return summarize(s_contrib, var_dict, x_sorted, mask, columns_dict, features_dict) - def compute_features_import(self, contributions): + def compute_features_import(self, contributions, norm=1): """ Compute a relative features importance, sum of absolute values - ​​of the contributions for each - features importance compute in base 100 + ​​of the contributions for each + features importance compute in base 100 Parameters ---------- contributions: pd.DataFrame @@ -317,7 +317,7 @@ def compute_features_import(self, contributions): feature importance, One row by feature, index of the serie = contributions.columns """ - return compute_features_import(contributions) + return compute_features_import(contributions, norm) def compute_grouped_contributions(self, contributions, features_groups): """ diff --git a/shapash/manipulation/filters.py b/shapash/manipulation/filters.py index 6d959558..0860cf7f 100644 --- a/shapash/manipulation/filters.py +++ b/shapash/manipulation/filters.py @@ -113,7 +113,11 @@ def cutoff_contributions(mask, k=10): pd.Dataframe Mask where only the k-top contributions are considered. """ - return mask.replace(False, np.nan).cumsum(axis=1).isin(range(1, k + 1)) + # Convert False values to np.nan explicitly without changing data type + mask_nan = mask.astype(float).replace(0, np.nan) + + # Compute the cumulative sum and check if the index is within the top-k + return mask_nan.cumsum(axis=1).isin(range(1, k + 1)) def combine_masks(masks_list): diff --git a/shapash/manipulation/summarize.py b/shapash/manipulation/summarize.py index 8d4d0437..94a3dfea 100644 --- a/shapash/manipulation/summarize.py +++ b/shapash/manipulation/summarize.py @@ -44,11 +44,11 @@ def summarize_el(dataframe, mask, prefix): return df_summarized_matrix -def compute_features_import(dataframe): +def compute_features_import(dataframe, norm=1): """ Compute a relative features importance, sum of absolute values - ​​of the contributions for each - features importance compute in base 100 + ​​of the contributions for each + features importance compute in base 100 Parameters ---------- dataframe: pd.DataFrame @@ -60,7 +60,7 @@ def compute_features_import(dataframe): feature importance One row by feature, index of the serie = dataframe.columns """ - feat_imp = dataframe.abs().sum().sort_values(ascending=True) + feat_imp = (((dataframe.abs() ** norm).sum()) ** (1 / norm)).sort_values(ascending=True) tot = feat_imp.sum() return feat_imp / tot diff --git a/shapash/style/colors.json b/shapash/style/colors.json index ddb9abd6..2a4595ba 100644 --- a/shapash/style/colors.json +++ b/shapash/style/colors.json @@ -28,9 +28,19 @@ "rgb(0, 70, 92)" ], "contrib_distribution": "rgb(211, 211, 211)", + "feature_contributions_cumulative": [ + "rgb(255, 55, 55)", + "rgb(255, 166, 0)", + "rgb(255, 200, 100)", + "rgb(55, 190, 255)", + "rgb(0, 0, 255)", + "rgb(0, 0, 0)" + ], "featureimp_bar": { "1": "rgba(0, 154, 203, 1)", - "2": "rgba(223, 103, 0, 0.8)" + "2": "rgba(223, 103, 0, 0.8)", + "3": "rgba(240, 195, 162, 0.8)", + "4": "rgba(245, 122, 0, 0.8)" }, "featureimp_groups": { "0": "rgb(10, 204, 143)", @@ -128,9 +138,19 @@ "rgb(255, 77, 7)" ], "contrib_distribution": "rgb(211, 211, 211)", + "feature_contributions_cumulative": [ + "rgb(255, 55, 55)", + "rgb(255, 166, 0)", + "rgb(255, 200, 100)", + "rgb(55, 190, 255)", + "rgb(0, 0, 255)", + "rgb(0, 0, 0)" + ], "featureimp_bar": { "1": "rgba(244, 192, 0, 1.0)", - "2": "rgba(52, 55, 54, 0.7)" + "2": "rgba(52, 55, 54, 0.7)", + "3": "rgba(103, 208, 255, 0.8)", + "4": "rgba(0, 98, 128, 0.8)" }, "featureimp_groups": { "0": "rgb(245, 133, 24)", diff --git a/shapash/style/style_utils.py b/shapash/style/style_utils.py index 59968fba..f50e7f82 100644 --- a/shapash/style/style_utils.py +++ b/shapash/style/style_utils.py @@ -100,8 +100,11 @@ def define_style(palette): style_dict["dict_featimp_colors"] = { 1: {"color": featureimp_bar[1], "line": {"color": palette["featureimp_line"], "width": 0.5}}, 2: {"color": featureimp_bar[2]}, + 3: {"color": featureimp_bar[3], "line": {"color": palette["featureimp_line"], "width": 0.5}}, + 4: {"color": featureimp_bar[4], "line": {"color": palette["featureimp_line"], "width": 0.5}}, } style_dict["featureimp_groups"] = convert_string_to_int_keys(palette["featureimp_groups"]) + style_dict["feature_contributions_cumulative"] = palette["feature_contributions_cumulative"] style_dict["init_contrib_colorscale"] = palette["contrib_colorscale"] style_dict["contrib_distribution"] = palette["contrib_distribution"] style_dict["violin_area_classif"] = convert_string_to_int_keys(palette["violin_area_classif"]) diff --git a/shapash/webapp/smart_app.py b/shapash/webapp/smart_app.py index acbb119e..49c8fffa 100644 --- a/shapash/webapp/smart_app.py +++ b/shapash/webapp/smart_app.py @@ -2135,7 +2135,7 @@ def update_id_card(n_submit, label, sort_by, order, data, index): selected_contrib = get_id_card_contrib( self.explainer.data, index, self.explainer.features_dict, self.explainer.columns_dict, label_num ) - proba = self.explainer.plot.local_pred(index, label_num) + proba = self.explainer.plot._local_pred(index, label_num) title_contrib = f"Contribution: {label_value} ({proba.round(2):.2f})" _, _, predicted_label_value = self.explainer.check_label_name( selected_row.loc["_predict_", "feature_value"] diff --git a/tests/unit_tests/explainer/test_smart_plotter.py b/tests/unit_tests/explainer/test_smart_plotter.py index ffd3620a..f3b94ca7 100644 --- a/tests/unit_tests/explainer/test_smart_plotter.py +++ b/tests/unit_tests/explainer/test_smart_plotter.py @@ -111,6 +111,7 @@ def setUp(self): self.smart_explainer.proba_values = None self.smart_explainer.features_desc = dict(self.x_init.nunique()) self.smart_explainer.features_compacity = self.features_compacity + self.smart_explainer.inv_features_dict = {v: k for k, v in self.smart_explainer.features_dict.items()} def test_define_style_attributes(self): # clear style attributes @@ -123,7 +124,7 @@ def test_define_style_attributes(self): assert len(list(self.smart_explainer.plot._style_dict.keys())) > 0 @patch("shapash.explainer.smart_explainer.SmartExplainer.filter") - @patch("shapash.explainer.smart_plotter.SmartPlotter.local_pred") + @patch("shapash.explainer.smart_plotter.SmartPlotter._local_pred") def test_local_plot_1(self, local_pred, filter): """ Unit test Local plot 1 @@ -190,7 +191,7 @@ def test_local_plot_3(self, select_lines): @patch("shapash.explainer.smart_explainer.SmartExplainer.filter") @patch("shapash.explainer.smart_plotter.select_lines") - @patch("shapash.explainer.smart_plotter.SmartPlotter.local_pred") + @patch("shapash.explainer.smart_plotter.SmartPlotter._local_pred") def test_local_plot_4(self, local_pred, select_lines, filter): """ Unit test local plot 4 @@ -253,7 +254,7 @@ def test_local_plot_4(self, local_pred, select_lines, filter): @patch("shapash.explainer.smart_explainer.SmartExplainer.filter") @patch("shapash.explainer.smart_plotter.select_lines") - @patch("shapash.explainer.smart_plotter.SmartPlotter.local_pred") + @patch("shapash.explainer.smart_plotter.SmartPlotter._local_pred") def test_local_plot_5(self, local_pred, select_lines, filter): """ Unit test local plot 5 @@ -347,7 +348,7 @@ def test_local_plot_5(self, local_pred, select_lines, filter): @patch("shapash.explainer.smart_explainer.SmartExplainer.filter") @patch("shapash.explainer.smart_plotter.select_lines") - @patch("shapash.explainer.smart_plotter.SmartPlotter.local_pred") + @patch("shapash.explainer.smart_plotter.SmartPlotter._local_pred") def test_local_plot_groups_features(self, local_pred, select_lines, filter): """ Unit test local plot 6 for groups of features @@ -515,7 +516,7 @@ def test_local_plot_groups_features(self, local_pred, select_lines, filter): @patch("shapash.explainer.smart_explainer.SmartExplainer.filter") @patch("shapash.explainer.smart_plotter.select_lines") - @patch("shapash.explainer.smart_plotter.SmartPlotter.local_pred") + @patch("shapash.explainer.smart_plotter.SmartPlotter._local_pred") def test_local_plot_multi_index(self, local_pred, select_lines, filter): """ Unit test local plot multi index @@ -584,7 +585,7 @@ def test_get_selection(self): Unit test get selection """ line = ["person_A"] - output = self.smart_explainer.plot.get_selection(line, self.var_dict, self.x_sorted, self.contrib_sorted) + output = self.smart_explainer.plot._get_selection(line, self.var_dict, self.x_sorted, self.contrib_sorted) expected_output = np.array([0, 1]), np.array(["PhD", 34]), np.array([-3.4, 0.78]) assert len(output) == 3 assert np.array_equal(output[0], expected_output[0]) @@ -599,7 +600,7 @@ def test_apply_mask_one_line(self): var_dict = np.array([0, 1]) x_sorted = np.array(["PhD", 34]) contrib_sorted = np.array([-3.4, 0.78]) - output = self.smart_explainer.plot.apply_mask_one_line(line, var_dict, x_sorted, contrib_sorted) + output = self.smart_explainer.plot._apply_mask_one_line(line, var_dict, x_sorted, contrib_sorted) expected_output = np.array([0]), np.array(["PhD"]), np.array([-3.4]) assert len(output) == 3 assert np.array_equal(output[0], expected_output[0]) @@ -614,7 +615,7 @@ def test_check_masked_contributions_1(self): var_dict = ["X1", "X2"] x_val = ["PhD", 34] contrib = [-3.4, 0.78] - var_dict, x_val, contrib = self.smart_explainer.plot.check_masked_contributions(line, var_dict, x_val, contrib) + var_dict, x_val, contrib = self.smart_explainer.plot._check_masked_contributions(line, var_dict, x_val, contrib) expected_var_dict = ["X1", "X2"] expected_x_val = ["PhD", 34] expected_contrib = [-3.4, 0.78] @@ -633,7 +634,7 @@ def test_check_masked_contributions_2(self): self.smart_explainer.masked_contributions = pd.DataFrame( data=[[0.0, 2.5], [0.0, 1.6]], columns=["masked_neg", "masked_pos"], index=["person_A", "person_B"] ) - var_dict, x_val, contrib = self.smart_explainer.plot.check_masked_contributions(line, var_dict, x_val, contrib) + var_dict, x_val, contrib = self.smart_explainer.plot._check_masked_contributions(line, var_dict, x_val, contrib) expected_var_dict = ["X1", "X2", "Hidden Positive Contributions"] expected_x_val = ["PhD", 34, ""] expected_contrib = [-3.4, 0.78, 2.5] @@ -655,7 +656,7 @@ def test_plot_bar_chart_1(self): ) expected_output_fig = go.Figure(data=bars, layout=go.Layout(yaxis=dict(type="category"))) self.smart_explainer._case = "regression" - fig_output = self.smart_explainer.plot.plot_bar_chart("ind", var_dict, x_val, contributions) + fig_output = self.smart_explainer.plot._plot_bar_chart("ind", var_dict, x_val, contributions) for part in list(zip(fig_output.data, expected_output_fig.data)): assert part[0].x == part[1].x assert part[0].y == part[1].y @@ -678,7 +679,7 @@ def test_plot_bar_chart_2(self): expected_output_fig = go.Figure(data=bars, layout=go.Layout(yaxis=dict(type="category"))) self.smart_explainer._case = "regression" - fig_output = self.smart_explainer.plot.plot_bar_chart("ind", var_dict, x_val, contributions) + fig_output = self.smart_explainer.plot._plot_bar_chart("ind", var_dict, x_val, contributions) for part in list(zip(fig_output.data, expected_output_fig.data)): assert part[0].x == part[1].x assert part[0].y == part[1].y @@ -1120,7 +1121,7 @@ def test_plot_features_import_1(self): Unit test plot features import 1 """ serie1 = pd.Series([0.131, 0.51], index=["col1", "col2"]) - output = self.smart_explainer.plot.plot_features_import(serie1) + output = self.smart_explainer.plot._plot_features_import(serie1) data = go.Bar(x=serie1, y=serie1.index, name="Global", orientation="h") expected_output = go.Figure(data=data) @@ -1135,7 +1136,7 @@ def test_plot_features_import_2(self): """ serie1 = pd.Series([0.131, 0.51], index=["col1", "col2"]) serie2 = pd.Series([0.33, 0.11], index=["col1", "col2"]) - output = self.smart_explainer.plot.plot_features_import(serie1, serie2) + output = self.smart_explainer.plot._plot_features_import(serie1, serie2) data1 = go.Bar(x=serie1, y=serie1.index, name="Global", orientation="h") data2 = go.Bar(x=serie2, y=serie2.index, name="Subset", orientation="h") expected_output = go.Figure(data=[data2, data1]) @@ -1154,7 +1155,7 @@ def test_features_importance_1(self): """ xpl = self.smart_explainer xpl.explain_data = None - output = xpl.plot.features_importance(selection=["person_A", "person_B"]) + output = xpl.plot.features_importance(selection=["person_A", "person_B"], zoom=True) data1 = go.Bar(x=np.array([0.2296, 0.7704]), y=np.array(["Age", "Education"]), name="Subset", orientation="h") @@ -1171,6 +1172,31 @@ def test_features_importance_1(self): assert output.data[1].name == expected_output.data[1].name assert output.data[1].orientation == expected_output.data[1].orientation + def test_features_importance_cumulative_1(self): + """ + Unit test features importance cumulative 1 + """ + xpl = self.smart_explainer + xpl.explain_data = None + output = xpl.plot.features_importance(mode="cumulative", selection=["person_A", "person_B"], zoom=True) + + assert len(output.data) == 2 + assert output.data[0].type == "scatter" + assert output.data[1].type == "scatter" + + def test_features_importance_local_1(self): + """ + Unit test features importance local 1 + """ + xpl = self.smart_explainer + xpl.explain_data = None + output = xpl.plot.features_importance(mode="global-local", selection=["person_A", "person_B"], zoom=True) + + assert len(output.data) == 3 + assert output.data[0].type == "bar" + assert output.data[1].type == "bar" + assert output.data[2].type == "bar" + def test_features_importance_2(self): """ Unit test features importance 2 @@ -1199,6 +1225,41 @@ def test_features_importance_2(self): assert output.data[1].name == expected_output.data[1].name assert output.data[1].orientation == expected_output.data[1].orientation + def test_features_importance_cumulative_2(self): + """ + Unit test features importance cumulative 2 + """ + xpl = self.smart_explainer + # regression + xpl.contributions = self.contrib1 + xpl.backend.state = SmartState() + xpl.explain_data = None + xpl._case = "regression" + xpl.state = SmartState() + output = xpl.plot.features_importance(mode="cumulative", selection=["person_A", "person_B"]) + + assert len(output.data) == 2 + assert output.data[0].type == "scatter" + assert output.data[1].type == "scatter" + + def test_features_importance_local_2(self): + """ + Unit test features importance local 2 + """ + xpl = self.smart_explainer + # regression + xpl.contributions = self.contrib1 + xpl.backend.state = SmartState() + xpl.explain_data = None + xpl._case = "regression" + xpl.state = SmartState() + output = xpl.plot.features_importance(mode="global-local", selection=["person_A", "person_B"]) + + assert len(output.data) == 3 + assert output.data[0].type == "bar" + assert output.data[1].type == "bar" + assert output.data[2].type == "bar" + def test_features_importance_3(self): """ Unit test features importance for groups of features @@ -1248,6 +1309,103 @@ def test_features_importance_3(self): assert output.data[0].name == expected_output.data[0].name assert output.data[0].orientation == expected_output.data[0].orientation + def test_features_importance_cumulative_3(self): + """ + Unit test features importance cumulative for groups of features + """ + x_init = pd.DataFrame( + data=np.array([["PhD", 34, 1], ["Master", 27, 0]]), + columns=["X1", "X2", "X3"], + index=["person_A", "person_B"], + ) + + contrib = pd.DataFrame( + data=np.array([[-3.4, 0.78, 1.2], [1.2, 3.6, -0.3]]), + columns=["X1", "X2", "X3"], + index=["person_A", "person_B"], + ) + + smart_explainer = SmartExplainer(model=self.model) + smart_explainer.x_encoded = x_init + smart_explainer.x_init = x_init + smart_explainer.postprocessing_modifications = False + smart_explainer.features_imp_groups = None + smart_explainer.features_imp = None + smart_explainer.features_groups = {"group0": ["X1", "X2"]} + smart_explainer.contributions = [contrib, -contrib] + smart_explainer.features_dict = {"X1": "X1", "X2": "X2", "X3": "X3", "group0": "group0"} + smart_explainer.inv_features_dict = {"X1": "X1", "X2": "X2", "X3": "X3", "group0": "group0"} + smart_explainer.model = self.smart_explainer.model + smart_explainer._case, smart_explainer._classes = check_model(self.smart_explainer.model) + smart_explainer.backend = ShapBackend(model=self.smart_explainer.model) + smart_explainer.backend.state = MultiDecorator(SmartState()) + smart_explainer.explain_data = None + smart_explainer.state = MultiDecorator(SmartState()) + smart_explainer.contributions_groups = smart_explainer.state.compute_grouped_contributions( + smart_explainer.contributions, smart_explainer.features_groups + ) + smart_explainer.features_imp_groups = smart_explainer.state.compute_features_import( + smart_explainer.contributions_groups + ) + + output = smart_explainer.plot.features_importance(mode="cumulative") + + assert len(output.data) == 2 + assert output.data[0].type == "scatter" + assert output.data[1].type == "scatter" + + def test_features_importance_local_3(self): + """ + Unit test features importance local for groups of features + """ + x_init = pd.DataFrame( + data=np.array([["PhD", 34, 1], ["Master", 27, 0]]), + columns=["X1", "X2", "X3"], + index=["person_A", "person_B"], + ) + + contrib = pd.DataFrame( + data=np.array([[-3.4, 0.78, 1.2], [1.2, 3.6, -0.3]]), + columns=["X1", "X2", "X3"], + index=["person_A", "person_B"], + ) + + smart_explainer = SmartExplainer(model=self.model) + smart_explainer.x_encoded = x_init + smart_explainer.x_init = x_init + smart_explainer.postprocessing_modifications = False + smart_explainer.features_imp_groups = None + smart_explainer.features_imp = None + smart_explainer.features_groups = {"group0": ["X1", "X2"]} + smart_explainer.contributions = [contrib, -contrib] + smart_explainer.features_dict = {"X1": "X1", "X2": "X2", "X3": "X3", "group0": "group0"} + smart_explainer.inv_features_dict = {"X1": "X1", "X2": "X2", "X3": "X3", "group0": "group0"} + smart_explainer.model = self.smart_explainer.model + smart_explainer._case, smart_explainer._classes = check_model(self.smart_explainer.model) + smart_explainer.backend = ShapBackend(model=self.smart_explainer.model) + smart_explainer.backend.state = MultiDecorator(SmartState()) + smart_explainer.explain_data = None + smart_explainer.state = MultiDecorator(SmartState()) + smart_explainer.contributions_groups = smart_explainer.state.compute_grouped_contributions( + smart_explainer.contributions, smart_explainer.features_groups + ) + smart_explainer.features_imp_groups = smart_explainer.state.compute_features_import( + smart_explainer.contributions_groups + ) + smart_explainer.features_imp_groups_local_lev1 = smart_explainer.state.compute_features_import( + smart_explainer.contributions_groups, norm=3 + ) + smart_explainer.features_imp_groups_local_lev2 = smart_explainer.state.compute_features_import( + smart_explainer.contributions_groups, norm=7 + ) + + output = smart_explainer.plot.features_importance(mode="global-local") + + assert len(output.data) == 3 + assert output.data[0].type == "bar" + assert output.data[1].type == "bar" + assert output.data[2].type == "bar" + def test_features_importance_4(self): """ Unit test features importance for groups of features when displaying a group @@ -1297,12 +1455,103 @@ def test_features_importance_4(self): assert output.data[0].name == expected_output.data[0].name assert output.data[0].orientation == expected_output.data[0].orientation + def test_features_importance_cumulative_4(self): + """ + Unit test features importance cumulative for groups of features when displaying a group + """ + x_init = pd.DataFrame( + data=np.array([["PhD", 34, 1], ["Master", 27, 0]]), + columns=["X1", "X2", "X3"], + index=["person_A", "person_B"], + ) + + contrib = pd.DataFrame( + data=np.array([[-3.4, 0.78, 1.2], [1.2, 3.6, -0.3]]), + columns=["X1", "X2", "X3"], + index=["person_A", "person_B"], + ) + + smart_explainer = SmartExplainer(model=self.model) + smart_explainer.x_encoded = x_init + smart_explainer.x_init = x_init + smart_explainer.postprocessing_modifications = False + smart_explainer.features_imp_groups = None + smart_explainer.features_imp = None + smart_explainer.features_groups = {"group0": ["X1", "X2"]} + smart_explainer.contributions = [contrib, -contrib] + smart_explainer.features_dict = {"X1": "X1", "X2": "X2", "X3": "X3", "group0": "group0"} + smart_explainer.inv_features_dict = {"X1": "X1", "X2": "X2", "X3": "X3", "group0": "group0"} + smart_explainer.model = self.smart_explainer.model + smart_explainer.backend = ShapBackend(model=self.smart_explainer.model) + smart_explainer.backend.state = MultiDecorator(SmartState()) + smart_explainer.explain_data = None + smart_explainer._case, smart_explainer._classes = check_model(self.smart_explainer.model) + smart_explainer.state = smart_explainer.backend.state + smart_explainer.contributions_groups = smart_explainer.state.compute_grouped_contributions( + smart_explainer.contributions, smart_explainer.features_groups + ) + smart_explainer.features_imp_groups = smart_explainer.state.compute_features_import( + smart_explainer.contributions_groups + ) + + output = smart_explainer.plot.features_importance(mode="cumulative", group_name="group0") + + assert len(output.data) == 2 + assert output.data[0].type == "scatter" + assert output.data[1].type == "scatter" + + def test_features_importance_local_4(self): + """ + Unit test features importance local for groups of features when displaying a group + """ + x_init = pd.DataFrame( + data=np.array([["PhD", 34, 1], ["Master", 27, 0]]), + columns=["X1", "X2", "X3"], + index=["person_A", "person_B"], + ) + + contrib = pd.DataFrame( + data=np.array([[-3.4, 0.78, 1.2], [1.2, 3.6, -0.3]]), + columns=["X1", "X2", "X3"], + index=["person_A", "person_B"], + ) + + smart_explainer = SmartExplainer(model=self.model) + smart_explainer.x_encoded = x_init + smart_explainer.x_init = x_init + smart_explainer.postprocessing_modifications = False + smart_explainer.features_imp_groups = None + smart_explainer.features_imp = None + smart_explainer.features_groups = {"group0": ["X1", "X2"]} + smart_explainer.contributions = [contrib, -contrib] + smart_explainer.features_dict = {"X1": "X1", "X2": "X2", "X3": "X3", "group0": "group0"} + smart_explainer.inv_features_dict = {"X1": "X1", "X2": "X2", "X3": "X3", "group0": "group0"} + smart_explainer.model = self.smart_explainer.model + smart_explainer.backend = ShapBackend(model=self.smart_explainer.model) + smart_explainer.backend.state = MultiDecorator(SmartState()) + smart_explainer.explain_data = None + smart_explainer._case, smart_explainer._classes = check_model(self.smart_explainer.model) + smart_explainer.state = smart_explainer.backend.state + smart_explainer.contributions_groups = smart_explainer.state.compute_grouped_contributions( + smart_explainer.contributions, smart_explainer.features_groups + ) + smart_explainer.features_imp_groups = smart_explainer.state.compute_features_import( + smart_explainer.contributions_groups + ) + + output = smart_explainer.plot.features_importance(mode="global-local", group_name="group0") + + assert len(output.data) == 3 + assert output.data[0].type == "bar" + assert output.data[1].type == "bar" + assert output.data[2].type == "bar" + def test_local_pred_1(self): xpl = self.smart_explainer xpl.proba_values = pd.DataFrame( data=np.array([[0.4, 0.6], [0.3, 0.7]]), columns=["class_1", "class_2"], index=xpl.x_encoded.index.values ) - output = xpl.plot.local_pred("person_A", label=0) + output = xpl.plot._local_pred("person_A", label=0) assert isinstance(output, float) def test_plot_line_comparison_1(self): @@ -1338,7 +1587,7 @@ def test_plot_line_comparison_1(self): ) ) expected_output = go.Figure(data=fig) - output = xpl.plot.plot_line_comparison( + output = xpl.plot._plot_line_comparison( ["person_A", "person_B"], var_dict, contributions, predictions=predictions, dict_features=features_dict ) @@ -1367,7 +1616,7 @@ def test_plot_line_comparison_2(self): ) predictions = [data.loc[id] for id in index] - output = xpl.plot.plot_line_comparison( + output = xpl.plot._plot_line_comparison( index, var_dict, contributions, diff --git a/tests/unit_tests/webapp/utils/test_callbacks.py b/tests/unit_tests/webapp/utils/test_callbacks.py index 492fba96..b12594c3 100644 --- a/tests/unit_tests/webapp/utils/test_callbacks.py +++ b/tests/unit_tests/webapp/utils/test_callbacks.py @@ -3,12 +3,13 @@ import numpy as np import pandas as pd -from dash import dcc +from dash import dcc, html from sklearn.tree import DecisionTreeClassifier from shapash import SmartExplainer from shapash.webapp.smart_app import SmartApp from shapash.webapp.utils.callbacks import ( + create_dropdown_feature_filter, create_filter_modalities_selection, create_id_card_data, create_id_card_layout, @@ -20,6 +21,7 @@ get_id_card_contrib, get_id_card_features, get_indexes_from_datatable, + handle_page_navigation, select_data_from_bool_filters, select_data_from_date_filters, select_data_from_numeric_filters, @@ -422,3 +424,33 @@ def test_create_filter_modalities_selection(self): "_column2", {"type": "var_dropdown", "index": 1}, self.smart_app.round_dataframe ) assert type(new_element.children) == dcc.Dropdown + + def test_handle_page_navigation_1(self): + page, selected_feature = handle_page_navigation( + triggered_input="page_left.n_clicks", page="3", selected_feature="column1" + ) + assert page == 2 + assert selected_feature == None + + def test_handle_page_navigation_2(self): + page, selected_feature = handle_page_navigation( + triggered_input="page_right.n_clicks", page="3", selected_feature="column1" + ) + assert page == 4 + assert selected_feature == None + + def test_handle_page_navigation_3(self): + page, selected_feature = handle_page_navigation( + triggered_input="bool_groups.on", page="3", selected_feature="column1" + ) + assert page == 1 + assert selected_feature == None + + def test_handle_page_navigation_4(self): + page, selected_feature = handle_page_navigation(triggered_input="erreur", page="3", selected_feature="column1") + assert page == 3 + assert selected_feature == "column1" + + def test_create_dropdown_feature_filter(self): + dropdown = create_dropdown_feature_filter(1, []) + assert type(dropdown) == html.Div From 5eb60f11e6db6021b63900798ced7336c558ba9c Mon Sep 17 00:00:00 2001 From: Guillaume VIGNAL Date: Mon, 16 Sep 2024 09:48:58 +0200 Subject: [PATCH 2/3] Feature importance tutorial update --- .../tuto-plot03-features-importance.ipynb | 80 ++++++++++++++++--- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/tutorial/plots_and_charts/tuto-plot03-features-importance.ipynb b/tutorial/plots_and_charts/tuto-plot03-features-importance.ipynb index d2d815d7..e3279892 100644 --- a/tutorial/plots_and_charts/tuto-plot03-features-importance.ipynb +++ b/tutorial/plots_and_charts/tuto-plot03-features-importance.ipynb @@ -28,8 +28,6 @@ "metadata": {}, "outputs": [], "source": [ - "import pandas as pd\n", - "from category_encoders import OrdinalEncoder\n", "from sklearn.ensemble import ExtraTreesClassifier\n", "from sklearn.model_selection import train_test_split" ] @@ -249,7 +247,7 @@ "metadata": {}, "outputs": [], "source": [ - "clf = ExtraTreesClassifier(n_estimators=200).fit(Xtrain,ytrain)" + "clf = ExtraTreesClassifier(n_estimators=200, random_state=79).fit(Xtrain,ytrain['Survived'])" ] }, { @@ -300,12 +298,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "Backend: Shap TreeExplainer\n" + "INFO: Shap explainer type - \n" ] } ], "source": [ - "xpl.compile(x=Xtest)" + "xpl.compile(x=Xtest, y_target=ytest)" ] }, { @@ -394,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -408,15 +406,79 @@ "source": [ "xpl.plot.features_importance(max_features=3)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Understand local effect\n", + "\n", + "### Comparing features globally\n", + "This plot allows us to observe how the importance of features varies across different subpopulations. For instance, we can see that in certain subpopulations, the **Port of Embarkation** has a greater impact than the **Ticket Class**, highlighting the local variations in feature significance." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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bJHFixfL5ZdfLr/88ovMmTNHWrRokRowSrGWP//8s5xyyiny119/FVoKbYv27duXYin/uXVJC1pJ36/UAVMACEAAAhEggBBGoBGpAgQgkBoE8gthrNZvv/22nH322TJgwAAZMWJEasAoxVqqkD/22GOuBCeffLIMGjRIqlevLr/++qssXLhQnn76abngggukbdu2pVjK0rk1Qlg63LkrBCAAgSAEEMIg9IiFAAQgUIIEChNCnb549NFHy+GHHy4TJ07MK9Fvv/0m06dPl7feektWr14tDRo0kCOPPFKGDh0qFSpUcNfpz2+99VZ58803ZeXKldKoUSPZf//95bTTTpPatWu7a+677z658cYb5bnnnpN77rlHnn/+edm4caPstddecv7550uTJk22oqCjlrfddpt8/PHH7jodtdS1a3379s27LpbnCy+8ILNnz5annnpKsrKyZI899pAxY8a4csRSImXUaxOpr163++67S5cuXWTGjBmm1lN+H330kYvVsnfo0KHIfP7zn//IvHnz3DXXX3+9HHTQQe7PCxYskJEjR7o/d+/eXa677jr35/xSpVNPtY1+/PFH2WeffVycJp0+fMIJJ+TdO35Kca9evdzIcUGCdsMNN7hyJ5pHrK1iN9KpytWqVZPWrVu7/nTcccdJenp6gWVP9ppFUwMSBAEIQAACWxFACOkQEIAABMoJgcKEUIXvnHPOcSNV+m9NOlqlI4YqaxdeeKG0atXKjV5ddtllsvPOO+fJh44s6rUqknrN77//7oRj8+bNLl5TTAh0mqoKiQrN0qVLZfz48bJmzRp58MEH3VpGTSqDp556qnTu3NndV6Vy7ty5MmXKFPn3v/+dN4IZy/OYY45xgnbggQc64dGRNRVIFdlYSqSMidZX8wwqhCqsKrKamjVrJr1793brOFUMq1atuk1vihdClTGtq6ZEhPDQQw8Vbffc3FwXo3wmT57s/tyxY0e5//778+6nchmTRRX3XXbZpUAh/Omnn+TYY49NOI/8Qpi/gvEj04wQlpMPE4oJAQhAII4AQkh3gAAEIFBOCBS0hnDRokVuJEg3Npk1a5Y0bdrU1UalRUf9/vvf/+aN9OnPX3vtNTeqFxOGrl27Sv/+/d2U08JSTAh0o5Qzzjgj77Lvv//ejfzpiNmwYcPcz88991z59NNP5dlnn3XTKGNJhfPJJ590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9GiE0AANITRBQwhN2BBCGzZGCA3cEEIDNDaVsUEjKhQCCGEoGKORSXFCqCNuKno77rjjVhWOTRnVtYYff/yxXH311Xlre4499lgX0759exfz/fffO0GbPXt2QkJY2D2LW0M4adIkadOmjWidLr/8cqldu7acfPLJeaORWpYnnnhC5s+fL+ecc47ssMMOeXXSkUZGCP37NELoz4wRQhszhNDGjRFCGzdGCP25IYT+zBghtDEjKhwCCGE4HCORS1FCuG7dOundu7ebCqpTMuOTCqFuDqMHg15wwQWy//77u1/HYpo0aZK386T+JaE/19G54kYIi7pnfiHUkcuZM2e6PDXplFatz6BBg2TFihVyzz33yDvvvONGLVUMu3fv7qawqvy99tpr7s89evRw16usIoT+XRoh9GeGENqYIYQ2bgihjRtC6M8NIfRnhhDamBEVDgGEMByOkciluBFCFUId/StshPCKK66QcePGuSmYu+22m2OiMTfeeKO0aNFiG0a//vqrmzpa1LEWhd0zvxD269dPhgwZIt26dXPrBCdPniz16tWTwYMH5903JydHPvroIzdCqdNCddQwllRqdVqriq2OYiKE/l0aIfRnhhDamCGENm4IoY0bQujPDSH0Z4YQ2pgRFQ4BhDAcjpHIpTgh1PV8uj7v4osvLnQN4WeffSYTJkyQyy67TDp16iQzZsxwa/N05LBBgwby559/ynfffSd77rmnrF+/3gnjAw884OStoFTYPePXEOpfPDq1dOrUqdK6dWu3sczw4cOlV69eTgh1wxktiwqgrh88++yz3QilroHUdYe6mYz+hT9ixAgnhPpzhNC/SyOE/swQQhszhNDGDSG0cUMI/bkhhP7MEEIbM6LCIYAQhsMxErkUJ4Q6rVKnXuouoypzusvoxIkTt9llVNcR6s/Hjx/vNnWZM2eOvPzyy6ISp5vR6FrD4447zjG76667ZO7cuaIyMW3aNLdJTXwq7J75dxmdN2+ezJo1y4mlTgvVw9EbNWrkhFDzXbBggdtQpk6dOjJgwAA3kqj3VRnVclWtWlUOO+wwd73mgxD6d2mE0J8ZQmhjhhDauCGENm4IoT83hNCfGUJoY0ZUOAQQwnA4kkuECOi6wtcrtJbmnfaKUK2SXxWdkqsirv+QEieQnZMtGekZiQcYrywr7bL21x+lZ83VHExvbMcgYQih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rP4Ioa1nIIQ2bgihPzdGCP2ZaQRCaOOGEPpzQwj9mSGENmZEhUMAIQzAUYVQpeSQQw5xuTz44IPy5ptvyk033STvv/++zJgxQ1asWCEtW7aUc845R3bYYQd3nQrQcccdJ6+99pps2rRJdt11V3n22Weldu3abqRAp3weeuihW5VMP1wfeeQReeqpp0RHwrp06SLnnnuuG43U6Zvz58+XSpUqufVA1157rTRt2nSr+FWrVsnNN98sn376qbuuT58+rgyarrzySmncuLF88cUXsnjxYtl5551lzJgxrvyar/7ukksukebNm+eVv1+/fvLf//5X1q5dKwcccICcddZZUqFCBdEXNc1P89IX3R133NEJcsOGDQus+9133+14xKagfvXVV26k9Pzzz5fOnTsXmte0adO2YbbLLrtsNWVUp8/eeOON8vXXX0vdunVl4MCBcuCBB7py6LTSt99+W2rVqiXffvutqMyMGDFCOnXqJAih7aFACG3cEEJ/bgihPzOE0MZMoxBCf3YIoT8zhNDGjKhwCCCEATjGC6F+83rppZdKgwYNnHjo7/S/dRqpStzDDz8s99xzj1SuXNkJkE4Nvfzyy50A6lTP4ka75s2bJ3fddZdcc8017h6TJ0928qUjg5rGjRsne+21V4EjhPrBrLKz0047yeDBg0XlcPTo0XLmmWe6GBW4L7/8Mk8ML7roIlm5cqWTq65du8qdd97pRkBj99LyqyTqmkUtu8qjXte/f39XJhXd/fff35VLJXTNmjXu2pgM5697TAhXr17tZFblU6W0uLzyM4tfQ5iTk+PKr+XQci1atMiVc8qUKdKmTRsnhFq2qVOnOml966235I477nBthBDaHgqE0MYNIfTnhhD6M0MIbcwQQhs3hNDGTT/bNm7cKNWrV7dlQBQEjAQQQiM4DVPp++OPP9zI2Lp166Rjx46iMqXy9vnnn+dJkF47YMAAGT58uOy9995OCFXI9thjj7y7FyeEY8eOld12202OP/54F/P777/LySef7GRTRwWLEkIdARs1apQ88cQTbo2XpieffNKNnF144YVOBJs0aeJkUdOjjz4qb7zxhpMlTUuWLHHyOnv27DypGzlypOy7777uv1WmVFb1n/xJJU3XNer9YkKYv+7K49hjj5VnnnnGjQ6qsBWU8udVlBDqSKe2hdZFp+Rquv76692H7Bln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GWeYczdv3mxYhh49e/aUtm3buqrHyXzS/v37ZeLEifL+++9LoUKFpFOnTtK6deuITfr9999l8uTJ8tVXX8muXbvkmWeekRIlSpzMzXdd91jjJ7yQ9evXy7x58+Trr7+WkiVLyty5czOdMmzYMPnggw8y/q1gwYLm5zoRDrc/s9i8Aj/b69atk507d5qf2a5du0qjRo0SAZO4vddv3bpVZs+eLZ999pkcOnRITj/9dOnVq5dUqVKF9zaLe9vrr78uzz33nPz2229SoEABM87wOyAtLS3w483m3haPUyLf2wI/UE5wAymEJ7gDePncQ2DlypUyffp0GTt2rOABcujQoXLJJZeYB/hEPLZt2ya33HKLDBo0SM455xx55JFHBDtijhs3LguO9PR0cy5+yXfs2FGWL19uHriwYytYQghHjBiR6cE9NTVVkpOTA48WMoiHSvwi/+mnn8y4evDBB+XMM8/M0naI99q1a83DObgnihDGGz/hoDZt2iS//vqrEZlXX301ohBiLDZv3tyEJiUlSZ48eQI/1mx+ZvFFxZw5c6RFixZSunRpWbNmjUybNk1mzpxpxl+QD5t7/caNG+Xzzz+Xhg0bmnvZk08+KRs2bDD3Nhy8t7m7t3333XeCe37x4sVl9+7d5ouv2rVrm98bQT5s723xOOH3SCLe24I8RnJL2yiEuaUnWI8TTgAP4Mho3XjjjaYuK1asMFKD/yXisWDBAkEmBlk/HNu3bzdyjH8vVapUJiR4YAK/559/XvLly2c+69Kli/kfHsqdh6b58+cnFMqjR4/KNddcI6NGjTIZVhwPPfSQ+XPgwIFRWeCBqX379gkjhPHGTzRQ77zzjpHBSBnCJk2aSMuWLRNqvNn8zEYCgwzhzTffLE2bNg00Ny/3enxp06FDB5PtKlasGO9tlvc2DCxkp8eMGWPG2D333BPosaa9t0XjBCFMxHtboAdJLmkchTCXdASrceIJ4Jd8nz59MqZMbdmyRbp3724yEHnz5j3xFczhGowePdo88GBaj3NgiicyXPXr189Um9dee01eeuklMwXNOZARPPXUU803wBDCfv36memmEMYGDRoYWcyfP38OtypnL/fLL7+YaXiYrojsAg7895tvvilTpkyhEP5DIN740QghvmnHgTGIrHW9evVytvNPwNVsfmbDq4dsKzjNmDFDKlWqdAJqn3OX9HKvX716tZktgew9Ms+8t7m/t2HaPL4Q27t3r8nY44uySDMlcm4kZP+VNPe2WJwghIl4b8v+nuIVKIQcAyTwD4Grr75a7r//fjnrrLPMvzgZsUWLFknRokUTjtO9994r1atXN+LmHMie3nbbbXLRRRdl4oHMIKY6OtkvfIjMIuSvd+/eZg0i1sVVrlxZsE7uscceMw+dkMsgH1hPCaF+4403zMMjDkynxcPkrFmzKIT/EIg3fmyFEOsHsfYS4w/jEln+qVOnStWqVYM83MTmZzYUBDI2Q4YMMT+f+HkN+qG912P9G740xBpCJ4vKe5v7exvG2V9//WWmzr/11ltyww03SJkyZQI93DT3tlicEvXeFuhBkksaRyHMJR3Bapx4Al6+NT7xtfe/BjbZBttvQSGH2HTmlVdeMetKgnowQ+iuZ23Hj1NqtCmj4Ve9++67zWYgznRwd7U6+c6y+Zl1WodpzfgiDBkbcEqEdb2aez3Eb8CAAdKmTRszDTzawXtb7NkPDjcIIX7uI61JP/l+8qLXWHtvc8spUe5tQRoTubUtFMLc2jOsV44TwLoSrPNyNpHBxgPYQCCR1xB+8sknGb+wd+zYYaaURVtDOHjwYFm8eHHG5h2YKtm5c+eMjT1CO/Tbb78137JDCIO82QcetvEAifUyzo6r2GTm+PHjXEMYMiCwzsZm/NgK4fDhw012EGMyyAd+Nt3+zIIDdlMeOXKkYJyCUZC/nAntd9t7PabTQgYvv/xys34w1sF7W+x7m8Nu1apVZlMjZ3OeoP5cau9tbjklyr0tqOMjN7WLQpibeoN1OaEEsIkMpvGNHz/ebIuNKVSYFpSou4xiF0dMD8U3kFh/9eijjwqk0PlGF998YnMZrAfETmrdunUzvDANCCydX/ZYO4fNaYoUKSLlypUzU3GxBgeM8TAa9APTaNFmrP34+eefjfig3Vg7g39/4YUXDGcnM3P48GEzrQoc8WoF7MoX9DWs8cZPOCecD4nBzpj4wgbr3pydRA8ePGimiWLqN75swDkYb5MmTTJZwiAftj+z2O0WrzfBel/ni5lE2P033r0+9N4GPtgACjs7hmaYwQtjjvc2d/e2F1980Xzhit8ZzmsYcA/s379/kH8k4/5uDL+3xeKUyPe2QA+SXNI4CmEu6QhWI3cQQEYQN2S+h/Dv/sDDNF7FEek9hBDmmjVrml0Jcfzwww9m3SC+IS9fvrzZRMbJiuEBa+HChWb9IMQQEgkJSoS1maHvIYQcI2vqvIcQr0/AmqSlS5ea911i3CELEX4kwsZGscZPOCc8hCPLE3pA9iB+Bw4cMGtTsSkUpBGbyoB548aNc8dNJptr4fZnFq+oiDSFNlHeDxrrXh96b1u2bFnGTsuhXYcNtKpVq2amPfLe9r7ZNCvWvQ28ME0UOyhjfS8EG18iBn1jsXi/G8PvbbE4Jfq9LZtvnQlfPIUw4YcAAZAACZAACZAACZAACZAACSQqAQphovY8200CJEACJEACJEACJEACJJDwBCiECT8ECIAESIAESIAESIAESIAESCBRCVAIE7Xn2W4SIAESIAESIAESIAESIIGEJ0AhTPghQAAkQAIkQAIkQAIkQAIkQAKJSoBCmKg9z3aTAAmQAAmQAAmQAAmQAAkkPAEKYcIPAQIgARIgARIgARIgARIgARJIVAIUwkTtebabBEiABEiABEiABEiABEgg4QlQCBN+CBAACZAACZAACZAACZAACZBAohKgECZqz7PdJEACJEACJEACJEACJEACCU+AQpjwQ4AASIAESIAESIAESIAESIAEEpUAhTBRe57tJgESIAESIAESIAESIAESSHgCFMKEHwIEQAIkQAIkQAIkQAIkQAIkkKgEKISJ2vNsNwmQAAmQAAmQAAmQAAmQQMIToBAm/BAgABIgARIgARIgARIgARIggUQlQCFM1J5nu0mABEiABEiABEiABEiABBKeAIUw4YcAAZAACZAACZAACZAACZAACSQqAQphovY8200CJEACJEACJEACJEACJJDwBCiECT8ECIAESIAESIAESIAESIAESCBRCVAIE7Xn2W4SIAESIAESIAESIAESIIGEJ0AhTPghQAAkQAIkQAIkQAIkQAIkQAKJSoBCmKg9z3aTAAkkHIFnnnlGxo0bJ0lJSfLaa69J6dKlMzFYs2aN9O3b1/zb9OnTpX79+icto0mTJpk2Ll++PEfaMGvWLJk7d668++67OXI9Nxf53//+J/Pnz5cNGzbIrl275JRTTpHKlSvL1VdfLc2bN5fU1FQ3xWT7OeHscqrvcmOfZTtsXoAESIAEIhCgEHJYkAAJkECCEHCEMC0tTXr06CE33nhjppYPGzZMVq1aJQcPHqQQWo4JrVxMnTpVnn32WXn77bct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", + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "xpl.plot.features_importance(mode='global-local', max_features=10, zoom=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Comparing the features by their shapeley values curves\n", + "In the plot below, we observe the same effect as before. For example, in certain subpopulations, the **Port of Embarkation** has a greater impact than the **Ticket Class**. This offers another way to visualize feature importance both locally and globally.\n", + "\n", + "When the curves cross each other, it indicates that one feature has a higher local effect in a specific subpopulation, but a lower global impact across the entire dataset. On the other hand, if a curve consistently remains higher than another, it signifies that the feature is more important both globally and locally.\n", + "\n", + "After this initial analysis, you can use the contribution plot to gain deeper insights into how a particular feature influences the model's predictions." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA4QAAAH0CAYAAABl8+PTAAAgAElEQVR4XuydBXSUx9fGH0JwS4IESqG4S1vctcWtaHF3dwnuDsELFKdoi/3R4l68UNzdneDkO3f2e5PNsknezW42u9lnzsnJZnfembm/md28z947d6L4+/v7g4UESIAESIAESIAESIAESIAESMDlCEShIHS5OafBJEACJEACJEACJEACJEACJKAIUBByIZAACZAACZAACZAACZAACZCAixKgIHTRiafZJEACJEACJEACJEACJEACJEBByDVAAiRAAiRAAiRAAiRAAiRAAi5KgILQRSeeZpMACZAACZAACZAACZAACZAABSHXAAmQAAmQAAmQAAmQAAmQAAm4KAEKQhedeJpNAiRAAiRAAiRAAiRAAiRAAhSEXAMkQAIkQAIkQAIkQAIkQAIk4KIEKAhddOJpNgmQAAmQAAmQAAmQAAmQAAlQEHINkAAJkAAJkAAJkAAJkAAJkICLEqAgdNGJp9kkQAIkQAIkQAIkQAIkQAIkQEHINUACJEACJEACJEACJEACJEACLkqAgtBFJ55mkwAJkAAJkAAJkAAJkAAJkAAFIdcACZAACZAACZAACZAACZAACbgoAQpCF514mk0CJEACJEACJEACJEACJEACFIRcAyRAAiRAAiRAAiRAAiRAAiTgogQoCF104mk2CZAACZAACZAACZAACZAACVAQcg2QAAmQAAmQAAmQAAmQAAmQgIsSoCB00Ymn2SRAAiRAAiRAAiRAAiRAAiRAQcg1QAIkQAIkQAIkQAIkQAIkQAIuSoCC0EUnnmaTAAmQAAmQAAmQAAmQAAmQAAUh1wAJkAAJkAAJkAAJkAAJkAAJuCgBCkIXnXiaTQIkQAIkQAIkQAIkQAIkQAIUhFwDJEACJEACJEACJEACJEACJOCiBCgIXXTiaTYJkAAJkAAJkAAJkAAJkAAJUBByDZAACZAACZAACZAACZAACZCAixKgIHTRiafZJEACJEACJEACJEACJEACJEBByDVAAiRAAiRAAiRAAiRAAiRAAi5KgILQRSeeZpMACZAACZAACZAACZAACZAABSHXAAmQAAmQAAmQAAmQAAmQAAm4KAEKQhedeJpNAiRAAiRAAiRAAiRAAiRAAhSEXAMkYCcCb9++xbp167B//35cvnwZz549g7u7O7y9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MvDuu/Zp2GYb4MUXgZgYPR0iIAIiIAIiIAIiIAIiIAIi0OoJtG1BGJhI5rHHgMGDW/1N1wRFQAREQAREQAREQAREQAREoG1bCAMTyRx5JEBroZoIiIAIiIAIiIAIiIAIiIAItBECbddC+MorwP3329usRDJt5HHXNEVABERABERABERABERABLwE2qYgDEwkM348MHKkngwREAEREAEREAEREAEREAERaFME2qYgvOUW4J137I1WIpk29cBrsiIgAiIgAiIgAiIgAiIgAn4CbU8QLloEjB7tJ6BEMvr3IAIiIAIiIAIiIAIiIAIi0EYJtD1BeMYZwJIl9nYffjhw661t9NZr2iIgAiIgAiIgAiIgAiIgAm2dQNsShPPmATffbO95QgLw+utA585t/RnQ/EVABERABERABERABERABNoogbYjCIuKgOHDgcxMe6tpKbzkkjZ62zVtERABERABERABERABERABEQDajiB87jlg+nR7z1lmYs4cICVFz4AIiIAIiIAIiIAIiIAIiIAItFkCbUMQbtkCnHACkJ9vb7TKTLTZB14TFwEREAEREAEREAEREAER8BNoG4KQBehZiJ6ta1dg9mw9AyIgAiIgAiIgAiIgAiIgAiLQ5gm0fkG4YQMwbJj/Rk+eDBx5ZJu/8QIgAiIgAiIgAiIgAiIgAiIgAq1fEE6bBrz4or3TLELvWgp170VABERABERABERABERABESgjRNo3YJw82bg+OMBZhhlu+km4Kij2vgt1/RFQAREQAREQAREQAREQAREwBJo3YLwkUeAZ5+1M2Xs4KxZQHS07r0IiIAIiIAIiIAIiIAIiIAIiECrFoQ5OcBxxwEFBfZGT5xYOZZQt18EREAEREAEREAEREAEREAE2jiB1mshfPJJ4PHH/dbBN94AYmLa+O3W9EVABERABERABERABERABETAT6B1CkLWG6R1MDfXzvS662wdQjUREAEREAEREAEREAEREAEREAEfgdYpCJ97Dpg+3U6yY0fgrbdkHdRDLwIiIAIiIAIiIAIiIAIiIAIBBFqfIGRGUVoHs7PtVK+6Chg+XDdeBERABERABERABERABERABESg1QtC1hm8/347TVoH33wTiIvTjRcBERABERABERABERABERABEWjVgrCkxMYKbtxop3n55cApp+imi4AIiIAIiIAIiIAIiIAIiIAIBCHQulxGWWfwrrvsNNPTbexgQoJuvAiIgAiIgAiIgAiIgAiIgAiIQKsWhGVlwL//DaxbZ6c5bhxw+um66SIgAiIgAiIgAiIgAiIgAiIgAtUQaD0WwrlzgSlT7DRlHdQDLwIiIAIiIAIiIAIiIAIiIAK1EmgdgrC8HBgxAli1yk74oouAs86qdfLqIAIiIAIiIAIiIAIiIAIiIAJtmUDrEITvvw9MmmTvY2qqjR1MTm7L91VzFwEREAEREAEREAEREAEREIFaCbR8QVhRAZx2GrBsmZ3sBRcAo0fXOnF1EAEREAEREAEREAEREAEREIG2TqDlC8LPPgOuucbeR1kH2/rzrPmLgAiIgAiIgAiIgAiIgAjUg0DLF4SMF/zxRzvlc84Bxoypx/TVVQREQAREQAREQAREQAREQATaLoGWLQiXLAHOOMPevZgYYM4coEOHtns3NXMREAEREAEREAEREAEREAERqAeBli0Ib7wRmD/fTveII4BbbqnH1NVVBERABERABERABERABERABNo2gZYrCDMzgRNOAFiQnm3mTKBv37Z9NzV7ERABERABERABERABERABEagHgZYrCP/v/4Dnn7dTHTwYeOyxekxbXUVABERABERABERABERABERABFqmICwqAo46CsjPt3fwjjuAgw/W3RQBERABERABERABERABERABEagHgZYpCN98E7jzTjvNbt0AflcTAREQAREQAREQAREQAREQARGoF4GWKQhHjQL++MNOdOxY4Mwz6zVpdRYBERABERABERABERABERABEQBaniBcvNjWG2RjqYl33gHS03UvRUAEREAEREAEREAEREAEREAE6kmg5QlCxgvOnm2neeihwG231XPK6i4CIiACIiACIiACIiACIiACIkACLUsQFhTYZDKFhfbuMdPo7rvrToqACIiACIiACIiACIiACIiACDSAQMsShLNmAXfdZafZqxfw2msNmLIOEQEREAEREAEREAEREAEREAERIIGWJQhHjgT++sveuXHjgNNP110UARFoKgLFeUBJgf0U5zvr+c66872Y+7meD5SXN9WVhfY8MXFAXCIQ63ziEjzr7nZnm+mXAMQlAfEpob0OjSYCIiACIiACIiACTUCg5QjCwGQy770HpKY2ASKdQgRaAIHyMsAINlegOcLMK9B8Is4j6FyRV2mfI/y8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", + "text/plain": [ + "" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "xpl.plot.features_importance(mode='cumulative', normalize_by_nb_samples=True, degree=-0.7, zoom=True)" + ] } ], "metadata": { "celltoolbar": "Aucun(e)", "hide_input": false, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "keltarif_39", "language": "python", - "name": "python3" + "name": "keltarif_39" }, "language_info": { "codemirror_mode": { @@ -428,7 +490,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.11" + "version": "3.9.18" } }, "nbformat": 4, From c6fe6113caf7131b6d3e7fe6dc895fbdfd8812ab Mon Sep 17 00:00:00 2001 From: Guillaume VIGNAL Date: Tue, 17 Sep 2024 09:55:19 +0200 Subject: [PATCH 3/3] incorrect indentation --- .pre-commit-config.yaml | 4 ++-- .readthedocs.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 038294a7..5c1355b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,8 +26,8 @@ repos: exclude: ^(docs/|gdocs/) - id: check-added-large-files args: ['--maxkb=500'] - - id: no-commit-to-branch - args: ['--branch', 'master', '--branch', 'develop'] + - id: no-commit-to-branch + args: ['--branch', 'master', '--branch', 'develop'] - repo: https://github.com/psf/black rev: 21.12b0 hooks: diff --git a/.readthedocs.yml b/.readthedocs.yml index bf4c4630..cf0b28de 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -24,5 +24,5 @@ build: # Optionally set the version of Python and requirements required to build your docs python: - install: - - requirements: docs/requirements.txt + install: + - requirements: docs/requirements.txt