This notebook illustrates creating explanations for a binary classification model, employee attrition classification, that uses one to one and one to many feature transformations from raw data to engineered features. It will showcase raw feature transformations with TabularExplainer from SHAP.
Problem: Employee attrition classification with scikit-learn (run model explainer locally)
# %pip install --upgrade interpret-community
After installing packages, you must close and reopen the notebook as well as restarting the kernel.
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from lightgbm import LGBMClassifier
import pandas as pd
import numpy as np
from urllib.request import urlretrieve
import zipfile
outdirname = 'dataset.6.21.19'
zipfilename = outdirname + '.zip'
urlretrieve('https://publictestdatasets.blob.core.windows.net/data/' + zipfilename, zipfilename)
with zipfile.ZipFile(zipfilename, 'r') as unzip:
unzip.extractall('.')
attritionData = pd.read_csv('./WA_Fn-UseC_-HR-Employee-Attrition.csv')
# Dropping Employee count as all values are 1 and hence attrition is independent of this feature
attritionData = attritionData.drop(['EmployeeCount'], axis=1)
# Dropping Employee Number since it is merely an identifier
attritionData = attritionData.drop(['EmployeeNumber'], axis=1)
attritionData = attritionData.drop(['Over18'], axis=1)
# Since all values are 80
attritionData = attritionData.drop(['StandardHours'], axis=1)
# Converting target variables from string to numerical values
target_map = {'Yes': 0, 'No': 1}
attritionData["Attrition_numerical"] = attritionData["Attrition"].apply(lambda x: target_map[x])
target = attritionData["Attrition_numerical"]
attritionXData = attritionData.drop(['Attrition_numerical', 'Attrition'], axis=1)
# Split data into train and test
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(attritionXData,
target,
test_size=0.2,
random_state=0,
stratify=target)
# Creating dummy columns for each categorical feature
categorical = []
for col, value in attritionXData.iteritems():
if value.dtype == 'object':
categorical.append(col)
# Store the numerical columns in a list numerical
numerical = attritionXData.columns.difference(categorical)
You can explain raw features by either using a sklearn.compose.ColumnTransformer
or a list of fitted transformer tuples. The cell below uses sklearn.compose.ColumnTransformer
. In case you want to run the example with the list of fitted transformer tuples, comment the cell below and uncomment the cell that follows after.
from sklearn.compose import ColumnTransformer
# We create the preprocessing pipelines for both numeric and categorical data.
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())])
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))])
transformations = ColumnTransformer(
transformers=[
('num', numeric_transformer, numerical),
('cat', categorical_transformer, categorical)])
# Append classifier to preprocessing pipeline.
# Now we have a full prediction pipeline.
clf = Pipeline(steps=[('preprocessor', transformations),
('classifier', LGBMClassifier())])
model = clf.fit(X_train, y_train)
# clf.steps[-1][1] returns the trained classification model
# Using SHAP TabularExplainer
from interpret.ext.blackbox import TabularExplainer
explainer = TabularExplainer(clf.steps[-1][1],
initialization_examples=X_train,
features=attritionXData.columns,
classes=['Leaving', 'Staying'],
transformations=transformations)
Explain overall model predictions (global explanation)
# Passing in test dataset for evaluation examples - note it must be a representative sample of the original data
# X_train can be passed as well, but with more examples explanations will take longer although they may be more accurate
global_explanation = explainer.explain_global(X_test)
# Print out a dictionary that holds the sorted feature importance names and values
print('global importance rank: {}'.format(global_explanation.get_feature_importance_dict()))
Explain local data points (individual instances)
# You can pass a specific data point or a group of data points to the explain_local function
# E.g., Explain the first data point in the test set
instance_num = 1
local_explanation = explainer.explain_local(X_test[:instance_num])
# Get the prediction for the first member of the test set and explain why model made that prediction
prediction_value = clf.predict(X_test)[instance_num]
sorted_local_importance_values = local_explanation.get_ranked_local_values()[prediction_value]
sorted_local_importance_names = local_explanation.get_ranked_local_names()[prediction_value]
print('local importance values: {}'.format(sorted_local_importance_values))
print('local importance names: {}'.format(sorted_local_importance_names))
Load the interpretability visualization dashboard
from raiwidgets import ExplanationDashboard
ExplanationDashboard(global_explanation, model, dataset=X_test, true_y=y_test.values)