SHAPIQ Library Tools#

SHAPIQ library has offers different visualizations. These are introduced here.

  1. Dataset preprocessing, Model Training, Testing, Evaluation Metrics

  2. Global Explanation Plots

  3. Local Explanation Plots

1. Dataset preprocessing, Model Training, Testing, Evaluation Metrics#

# Uncomment the libraries not installed
#!pip install numpy
#!pip install matplotlib
#!pip install pandas
#!pip install scikit-learn
#!pip install xgboost
#!pip install shapiq
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn import metrics

from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import xgboost as xgb
import shapiq
from tqdm.asyncio import tqdm

import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
drop_list = ['symmetry_worst', 'area_worst', 'symmetry_se','radius_mean','concave points_worst',
            'compactness_se', 'concavity_se','smoothness_se','perimeter_mean','concave points_mean',
            'texture_se','radius_se','smoothness_mean','texture_mean','concave points_se',
            'area_mean','compactness_mean','perimeter_worst','Unnamed: 32', 'id']
#importing our cancer dataset
dataset = pd.read_csv('data.csv')
dataset = dataset.drop(drop_list, axis = 1)
X = dataset.drop(['diagnosis'], axis = 1)
Y = dataset['diagnosis']
#Encoding categorical data values
labelencoder_Y = LabelEncoder()
Y = labelencoder_Y.fit_transform(Y)
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.25, random_state = 0)
sc = StandardScaler()
X_train_scaled = sc.fit_transform(X_train)
X_test_scaled = sc.transform(X_test)
classifier = xgb.XGBClassifier()
classifier.fit(X_train_scaled, Y_train)
Y_pred = classifier.predict(X_test_scaled)
print(metrics.classification_report(Y_test, Y_pred))
print(metrics.confusion_matrix(Y_test, Y_pred))
              precision    recall  f1-score   support

           0       0.99      0.97      0.98        90
           1       0.95      0.98      0.96        53

    accuracy                           0.97       143
   macro avg       0.97      0.97      0.97       143
weighted avg       0.97      0.97      0.97       143

[[87  3]
 [ 1 52]]
# set feature names to classifier
classifier.feature_names = list(X.columns.values)

2. Global Explanation Plots#

i. The bar plot below is based on feature interaction caclculations until order 2:#

%%capture
explanations_si = []
explainer = shapiq.TreeExplainer(model=classifier, max_order=2, index="k-SII", verbose=False)
for instance_id in tqdm(range(len(X_test))):
    x_explain = X_test_scaled[instance_id]
    si = explainer.explain(x=x_explain, verbose=False)
    explanations_si.append(si)
shapiq.plot.bar_plot(explanations_si, feature_names=classifier.feature_names, show=True)
../_images/a792feb39cc40004fcd4882abf85290ed871a798e5df2dd47b97987ad48ce8fb.png
explanations_si[0].interaction_lookup
{(): 0,
 (np.int64(0),): 1,
 (np.int64(3),): 2,
 (np.int64(5),): 3,
 (np.int64(6),): 4,
 (np.int64(7),): 5,
 (np.int64(9),): 6,
 (np.int64(11),): 7,
 (np.int64(0), np.int64(3)): 8,
 (np.int64(0), np.int64(5)): 9,
 (np.int64(0), np.int64(6)): 10,
 (np.int64(0), np.int64(7)): 11,
 (np.int64(0), np.int64(9)): 12,
 (np.int64(0), np.int64(11)): 13,
 (np.int64(3), np.int64(5)): 14,
 (np.int64(3), np.int64(6)): 15,
 (np.int64(3), np.int64(7)): 16,
 (np.int64(3), np.int64(9)): 17,
 (np.int64(3), np.int64(11)): 18,
 (np.int64(5), np.int64(6)): 19,
 (np.int64(5), np.int64(7)): 20,
 (np.int64(5), np.int64(9)): 21,
 (np.int64(5), np.int64(11)): 22,
 (np.int64(6), np.int64(7)): 23,
 (np.int64(6), np.int64(9)): 24,
 (np.int64(6), np.int64(11)): 25,
 (np.int64(7), np.int64(9)): 26,
 (np.int64(7), np.int64(11)): 27,
 (np.int64(9), np.int64(11)): 28,
 (np.int64(1),): 29,
 (np.int64(4),): 30,
 (np.int64(8),): 31,
 (np.int64(10),): 32,
 (np.int64(1), np.int64(4)): 33,
 (np.int64(1), np.int64(6)): 34,
 (np.int64(1), np.int64(7)): 35,
 (np.int64(1), np.int64(8)): 36,
 (np.int64(1), np.int64(9)): 37,
 (np.int64(1), np.int64(10)): 38,
 (np.int64(4), np.int64(6)): 39,
 (np.int64(4), np.int64(7)): 40,
 (np.int64(4), np.int64(8)): 41,
 (np.int64(4), np.int64(9)): 42,
 (np.int64(4), np.int64(10)): 43,
 (np.int64(6), np.int64(8)): 44,
 (np.int64(6), np.int64(10)): 45,
 (np.int64(7), np.int64(8)): 46,
 (np.int64(7), np.int64(10)): 47,
 (np.int64(8), np.int64(9)): 48,
 (np.int64(8), np.int64(10)): 49,
 (np.int64(9), np.int64(10)): 50,
 (np.int64(2),): 51,
 (np.int64(0), np.int64(2)): 52,
 (np.int64(0), np.int64(4)): 53,
 (np.int64(0), np.int64(10)): 54,
 (np.int64(2), np.int64(3)): 55,
 (np.int64(2), np.int64(4)): 56,
 (np.int64(2), np.int64(6)): 57,
 (np.int64(2), np.int64(7)): 58,
 (np.int64(2), np.int64(10)): 59,
 (np.int64(3), np.int64(4)): 60,
 (np.int64(3), np.int64(10)): 61,
 (np.int64(0), np.int64(1)): 62,
 (np.int64(1), np.int64(5)): 63,
 (np.int64(4), np.int64(5)): 64,
 (np.int64(5), np.int64(10)): 65,
 (np.int64(0), np.int64(8)): 66,
 (np.int64(2), np.int64(8)): 67,
 (np.int64(2), np.int64(9)): 68,
 (np.int64(1), np.int64(11)): 69,
 (np.int64(4), np.int64(11)): 70,
 (np.int64(8), np.int64(11)): 71,
 (np.int64(5), np.int64(8)): 72,
 (np.int64(3), np.int64(8)): 73,
 (np.int64(1), np.int64(3)): 74,
 (np.int64(2), np.int64(5)): 75,
 (np.int64(2), np.int64(11)): 76}
len(explanations_si[0].values)
77

In my calculation, there should be (12C1 + 12C2 + 12C0 = 12 + 66 + 1) = 79 values calculated for each data point when they are saying that they are calculating interactions until order2. But there are 77 values here. What happens to the remaining 2 values?#

Question: I further looked up into all the combinations above and found out that the listed interactions are missing these - (np.int64(1), np.int64(2)) and (np.int64(10), np.int64(11)) interactions. Isn’t this a mistake that invalidates the library’s calculations, since they are disregarding these feature combinations altogether, and all other calculations using this library are based on these interaction calculations?

iii. The bar plot below is based on all feature interactions:#

%%capture
explanations_mi = []
explainer = shapiq.TreeExplainer(model=classifier, max_order=len(classifier.feature_names), index="k-SII")
for instance_id in tqdm(range(len(X_test))):
    x_explain = X_test_scaled[instance_id]
    mi = explainer.explain(x=x_explain)
    explanations_mi.append(mi)
shapiq.plot.bar_plot(explanations_mi, feature_names=classifier.feature_names, show=True)
../_images/ff689b950f6a085f59b58a489cbdc90e14a42e106cef14c7397530a9e50de9ee.png
len(explanations_mi[0])
778

3. Local Explanation Plots#

Selecting instance from the test dataset to explain

def explain_instance(classifier, X_test, X_test_scaled, Y_test, instance_id):
    """
    Prints and returns the actual values, scaled values, and prediction for a selected instance.
    
    Parameters:
        classifier  : Trained model with feature names
        X_test      : DataFrame of test features (actual values)
        X_test_scaled : Scaled version of X_test (numpy array)
        Y_test      : True labels for the test set
        instance_id : Index of the instance to be explained
        
    Returns:
        dict: Contains actual values, scaled values, true label, and predicted value
    """
    x_explain_actual = X_test.iloc[instance_id]
    x_explain_scaled = X_test_scaled[instance_id]
    y_true = Y_test[instance_id]
    y_pred = classifier.predict(x_explain_scaled.reshape(1, -1))[0]
    
    # Print formatted output
    print(f"\n{'Feature Name':<25}{'Actual Value':<15}{'Scaled Value':<15}")
    print("=" * 50)
    for i, feature in enumerate(classifier.feature_names):
        print(f"{feature:<25}{x_explain_actual[i]:<15}{x_explain_scaled[i]:<15.4f}")

    print("\n" + "=" * 50)
    print(f"Instance ID     : {instance_id}")
    print(f"True Value      : {y_true}")
    print(f"Predicted Value : {y_pred}")
    print("=" * 50)
    
    return {
        "actual_values": x_explain_actual.to_dict(),
        "scaled_values": x_explain_scaled,
        "true_label": y_true,
        "predicted_label": y_pred
    }

i. Force Plot#

Calculating interaction values for order 1, 2, and all features:#

%%capture
# selecting an instance from the test dataset to explain using SHAPIQ
instance_id = 0
explain_this_record = explain_instance(classifier=classifier, X_test=X_test, X_test_scaled=X_test_scaled, Y_test=Y_test, instance_id=instance_id)

# create explanations for different orders
si_order: dict[int, shapiq.InteractionValues] = {}
for order in tqdm([1, 2, len(classifier.feature_names)]):
    index = "k-SII" if order > 1 else "SV"  # will also be set automatically by the explainer
    explainer = shapiq.Explainer(model=classifier, max_order=order, index=index)
    si_order[order] = explainer.explain(x=explain_this_record['scaled_values'])
si_order

Visualizing Interactions of order 1 (since it is limited to order 1 only, it should be the same as SHAP values):#

sv = si_order[1]  # get the SV

sv.plot_force(feature_names=classifier.feature_names, show=True)
../_images/f106063e18824759aada1acf4b32001350e9f3f188ca2ec8e10b5d3ae9ed9571.png

Visualizing interactions of order until 2:#

si = si_order[2]  # get the 2-SII

si.plot_force(feature_names=classifier.feature_names, show=True)
../_images/6cce20ff022eccdf3e579ed681ea38e145dbada6a95da4551c4fcbcb6afc05ac.png

Visualizing All feature interactions:#

mi = si_order[len(classifier.feature_names)]  # get the Moebius transform

mi.plot_force(feature_names=classifier.feature_names, show=True)
../_images/3aef33f3f602b0e126c772c5aa94103a875c3d31419fa0b2600b178d80981bdb.png
print("model prediction in log-odds space: ", np.log(classifier.predict_proba(explain_this_record['scaled_values'].reshape(1, -1))[0][1] / classifier.predict_proba(explain_this_record['scaled_values'].reshape(1, -1))[0][0]))
model prediction in log-odds space:  6.552892

So, sum of the shap values and baseline_value is also not equal to the model’s output in the log-odds space.

ii. Waterfall Plot#

Visualizing Feature Interactions until order 1 (the same as SHAP values):#

sv.plot_waterfall(feature_names=classifier.feature_names, show=True, abbreviate=False)
../_images/d1051749ab165f66fc5abdd1f99486e99b4728bad16b68538ee172b71ea0bad2.png

Visualizing Feature Interactions until order 2:#

si.plot_waterfall(feature_names=classifier.feature_names, show=True, abbreviate=False)
../_images/e45372bfe17f299db60bdc7ee8bb6843c61b3427000c65e0d49f9a5a42875a7a.png

Visualizing the impact of all feature interactions:#

mi.plot_waterfall(feature_names=classifier.feature_names, show=True, abbreviate=False)
../_images/4364a02703a0ba80547726dd917796c6a6b03c1ab7c14616a2c90ddd4c47bdc8.png

iii. Network Plot#

Network Plot notes:

  1. The network plot visualizes all first- and second-order interactions of computed Shapley Interactions.

  2. It does plot all second order interactions.

  3. Color Scheme: positive interactions are shown in red, negative interactions in blue.

  4. The strength of an interaction is encoded in the width of the edge connecting the two features.

  5. The strength of a first-order interaction is encoded in the size of the node for the respective feature.

  6. The second-order interactions are also plotted with a decreasing opacity depending on the strength of the interaction.

Visualizing Network Plot for interaction order until 2:#

si.plot_network(feature_names=classifier.feature_names, show=True, draw_legend=False)
../_images/4c51039a9686b3607acec68c0de0a41056165ba5f03db79f72ac1896948bf89e.png

Visualizing Network Plot for all interaction orders:#

mi.plot_network(feature_names=classifier.feature_names, show=True, draw_legend=False)
../_images/a7358e96dd033ace4ea0e94b8db15563eddc508ec9b2cfb6b9e12b3cb24ae2f4.png

iii. SI Graph Plot#

SI Graph notes:

  1. The nodes are the features,

  2. The edges are the interactions between the features.

  3. Interactions between more than two features are visualized as a hyper-edge.

  4. Color scheme: red for positive interactions, blue for negative interactions.

  5. The strength of an interaction is encoded in the width of the edge connecting the two features.

  6. The strength of a first-order interaction is encoded in the size of the node for the respective feature.

  7. The transparency of the edge/node is used to encode the strength of the interaction/feature attribution.

Visualizing feature interactions until first order in SI Graph Plot (this makes this an alternative to ):#

# we abbreviate the feature names since, they are plotted inside the nodes
abbrev_feature_names = shapiq.plot.utils.abbreviate_feature_names(classifier.feature_names)
sv.plot_si_graph(
    feature_names=abbrev_feature_names, show=True, size_factor=2.5, node_size_scaling=1.5
)
../_images/619bf17ccd4f70c3993e0ea8a4296fdb09204de45bf1cc50ee2b7b06ecf90b02.png

Visualizing feature interactions until second order in SI Graph Plot:#

# we abbreviate the feature names since, they are plotted inside the nodes
abbrev_feature_names = shapiq.plot.utils.abbreviate_feature_names(classifier.feature_names)
si.plot_si_graph(
    feature_names=abbrev_feature_names, show=True, size_factor=2.5, node_size_scaling=1.5
)
../_images/194547a2b5c5185dfb8c750f3af459f9a004488ff52300b8faff1f55f05d1ab0.png

Visualizing all feature interactions in SI Graph Plot:#

# we abbreviate the feature names since, they are plotted inside the nodes
abbrev_feature_names = shapiq.plot.utils.abbreviate_feature_names(classifier.feature_names)
mi.plot_si_graph(
    feature_names=abbrev_feature_names, show=True, size_factor=2.5, node_size_scaling=1.5
)
../_images/04ae75842f7fb3987caae06081ad431a38d477fe1f99142b86767334b0d54624.png