Representing SHAP values as a percentage contribution in binary problems

Viewed 77

I have a dataset that can be simplified to this:

ranking-A ranking-B ranking-C color-red color-blue color-green state-1 state-0
1 0 0 1 0 0 1 0
0 1 0 0 1 0 0 1
0 0 1 0 0 1 1 0

Which I fed into a probablistic algorithm and then a shap Explainer.

I'm not really well versed with data processing in general since this is my first project, but what I would like to do is to express ranking and color as a percentage of how much they contributed to the algorithm making the decision of state 1 instead of state 0. Can someone point me towards the right direction in doing so? My first thought was to scale all the values such that the sum of the absolute values of these values add up to 100%, and then adding up the values of ranking-A, ranking-B and ranking-C etc to find the % contribution of the column ranking. Is doing this accurate?

Example code:

import tensorflow as tf
import tensorflow_probability as tfp
import numpy as np
import pandas as pd
import shap
from sklearn.preprocessing import OneHotEncoder
from keras import Sequential
from keras.layers import Dense

tfd = tfp.distributions
tfpl = tfp.layers
oneHotEncoder = OneHotEncoder(sparse=False)
def nll(y_true, y_pred):
    return -y_pred.log_prob(y_true)

color = list(zip(*list(oneHotEncoder.fit_transform(np.array(['red', 'green','blue','red', 'green','blue','red', 'green','blue']).reshape(-1,1)))))
ranking = list(zip(*list(oneHotEncoder.fit_transform(np.array(['A','B','C','A','B','C','A','B','C']).reshape(-1,1)))))
x_data = pd.DataFrame(data=list(zip(*color+ranking)),columns=['color-1','color-2','color-3','ranking-1','ranking-2','ranking-3'])
print(x_data)
y_data = tf.keras.utils.to_categorical([1,0,1,1,0,1,1,0,1])
y_data = y_data
print(y_data)

model = Sequential([
        Dense(10, input_dim=6, activation='relu'),
        Dense(units=tfpl.OneHotCategorical.params_size(2)),
        tfpl.OneHotCategorical(event_size=2,
                               convert_to_tensor_fn=tfd.Distribution.mode)
    ])
model.compile(loss = nll,
              optimizer='adam',
              metrics=['accuracy'],
              experimental_run_tf_function=False)

model.fit(np.array(x_data), y_data, epochs=15, verbose=False)
explainer = shap.Explainer(model.predict, x_data)
shap_values = explainer.__call__(x_data.iloc[[0]]).values[0]

predicted_probabilities = model(np.array(x_data)[0][np.newaxis, :]).mean().numpy()[0][1]
print('model certainty for entry 0: {:.0%}'.format(round(predicted_probabilities,4)))

columns = ['color-1','color-2','color-3','ranking-1','ranking-2','ranking-3']
print('\nobtained shap values for entry 0:')
for i in range(6):
    print(f'{columns[i]}: {shap_values[i]}')

print('\nwanted results:')
import random
percentages = []
percentages.append(round(random.uniform(0,100-sum(percentages)),3))
percentages.append(100-sum(percentages))
columns = ['color','ranking']
for i in range(2):
    print(f'{columns[i]}: {percentages[i]}% contribution to prediction')
    
print('\npossible method:')
totalShapValue = sum([abs(i[1]) for i in shap_values])
rescaledShapValue = [abs(i[1]/totalShapValue*100) for i in shap_values]
resultFrame = pd.DataFrame(data=[rescaledShapValue],columns=['color-1','color-2','color-3','ranking-1','ranking-2','ranking-3'])
for i in resultFrame:
    if i in resultFrame.columns:
        prefix = i.split('-')[0]
        siblingColumns = [col for col in resultFrame if col.startswith(prefix)]
        resultFrame[prefix]= resultFrame[siblingColumns].sum(axis=1)
        resultFrame.drop(siblingColumns,axis=1,inplace=True)
print(resultFrame)

The expected behaviour is that I get the percentage that each column provides during the model's calculation for probablity that a particular entry belongs to state 1.

0 Answers
Related