AI & Machine Learning

Explainable AI for Data Scientists: SHAP, LIME and Feature Importance

Python Pandas NumPy Scikit-learn Classification Feature Engineering Model Evaluation Ensemble Methods
1,078 words Includes Code
Explainable AI methods showing SHAP, LIME, Permutation Importance, and Tree Feature Importance for interpreting ML models
Key Takeaway: Black-box models make accurate predictions but cannot explain why. SHAP provides game-theoretic feature attributions. LIME creates local surrogate explanations. Permutation importance measures feature impact. Tree importance is fast but limited to tree models. Use multiple methods for comprehensive understanding.

Explainable AI for Data Scientists: SHAP, LIME and Feature Importance

Your model predicts a customer will churn. The business asks: "Why?"

You shrug. The model is a black box.

Explainable AI (XAI) solves this problem. It opens the black box and shows which features drove each prediction.

This article compares 4 XAI methods with Python examples and helps you choose the right one.

XAI Methods Comparison showing SHAP, LIME, Permutation Importance, and Tree Importance with scope, speed, and best use cases

The 4 Methods at a Glance

Method Scope Speed Model-Agnostic Foundation
SHAP Global + Local Slow Yes Game theory
LIME Local only Medium Yes Surrogate model
Permutation Global only Medium Yes Feature shuffling
Tree Importance Global only Fast No (trees) Information theory

1. SHAP (SHapley Additive exPlanations)

SHAP is based on game theory. It calculates each feature's contribution to every prediction.

import shap
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

# Load data
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=1000, n_features=10, random_state=42)
feature_names = [f'feature_{i}' for i in range(10)]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Train model
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# SHAP explanation
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

# Global feature importance
shap.summary_plot(shap_values[1], X_test, feature_names=feature_names)

# Single prediction explanation
shap.force_plot(
    explainer.expected_value[1],
    shap_values[1][0],
    X_test[0],
    feature_names=feature_names
)

SHAP Values Interpretation

# Get SHAP values for class 1 (positive class)
shap_vals = shap_values[1]

# For a single prediction
print("Feature contributions for first sample:")
for i, (name, val) in enumerate(zip(feature_names, shap_vals[0])):
    direction = "↑" if val > 0 else "↓"
    print(f"  {name}: {val:+.4f} {direction}")

# Positive SHAP value = pushes prediction toward positive class
# Negative SHAP value = pushes prediction toward negative class

SHAP Strengths

  • Theoretically grounded — Based on Shapley values from game theory
  • Consistent — Features always sum to the prediction difference
  • Global + Local — Individual and aggregate explanations
  • Visual — Beautiful summary and force plots

SHAP Limitations

  • Slow — Computationally expensive for large datasets
  • Memory intensive — Stores all feature combinations
  • Assumes independence — May not handle correlated features well

2. LIME (Local Interpretable Model-agnostic Explanations)

LIME explains individual predictions by fitting a simple surrogate model around the prediction.

import lime
import lime.lime_tabular

# Create LIME explainer
explainer = lime.lime_tabular.LimeTabularExplainer(
    X_train,
    feature_names=feature_names,
    class_names=['Negative', 'Positive'],
    mode='classification'
)

# Explain a single prediction
exp = explainer.explain_instance(
    X_test[0],
    model.predict_proba,
    num_features=10
)

# Show explanation
print("LIME Explanation for first sample:")
for feature, weight in exp.as_list():
    print(f"  {feature}: {weight:+.4f}")

# Visual explanation
exp.show_in_notebook()

LIME Strengths

  • Model-agnostic — Works with any model
  • Intuitive — Easy to understand "why this prediction"
  • Fast — Quick local explanations
  • Flexible — Works with text, images, tabular

LIME Limitations

  • Unstable — Different runs may give different explanations
  • Local only — No global feature importance
  • Hyperparameter sensitive — Kernel width affects results

3. Permutation Feature Importance

Permutation importance measures how much model performance decreases when a feature is randomly shuffled.

from sklearn.inspection import permutation_importance
import matplotlib.pyplot as plt

# Calculate permutation importance
result = permutation_importance(
    model, X_test, y_test,
    n_repeats=10,
    random_state=42,
    scoring='f1'
)

# Sort by importance
sorted_idx = result.importances_mean.argsort()

# Plot
plt.figure(figsize=(10, 6))
plt.boxplot(
    result.importances[sorted_idx].T,
    vert=False,
    labels=[feature_names[i] for i in sorted_idx]
)
plt.title("Permutation Feature Importance")
plt.xlabel("F1 Score Decrease")
plt.tight_layout()
plt.show()

# Print results
print("Permutation Importance:")
for i in sorted_idx:
    print(f"  {feature_names[i]}: {result.importances_mean[i]:.4f} "
          f"± {result.importances_std[i]:.4f}")

Permutation Strengths

  • Model-agnostic — Works with any model
  • Intuitive — "How much does shuffling hurt?"
  • Reliable — Less biased than tree importance
  • Simple — Easy to implement and understand

Permutation Limitations

  • Global only — No single-prediction explanations
  • Correlated features — Importance spread across correlated features
  • Computationally expensive — Multiple re-evaluations needed

4. Tree Feature Importance

Tree-based models have built-in feature importance (Gini or entropy-based).

import numpy as np

# Tree-based feature importance
importances = model.feature_importances_
std = np.std([tree.feature_importances_ for tree in model.estimators_], axis=0)

# Sort
sorted_idx = importances.argsort()

# Plot
plt.figure(figsize=(10, 6))
plt.barh(
    range(len(sorted_idx)),
    importances[sorted_idx],
    xerr=std[sorted_idx],
    align='center'
)
plt.yticks(range(len(sorted_idx)), [feature_names[i] for i in sorted_idx])
plt.title("Tree Feature Importance (Gini)")
plt.xlabel("Importance")
plt.tight_layout()
plt.show()

# Print results
print("Tree Feature Importance:")
for i in sorted_idx:
    print(f"  {feature_names[i]}: {importances[i]:.4f}")

Tree Strengths

  • Very fast — Calculated during training
  • No extra computation — Built into the model
  • Works well — For tree-based models

Tree Limitations

  • Trees only — Not applicable to other models
  • Biased — Prefers high-cardinality features
  • Impurity-based — Can be misleading
  • No direction — Does not show positive/negative impact

When to Use Each Method

Scenario Best Method Why
Production explanation API SHAP Theoretically grounded, consistent
Debugging single prediction LIME Fast, intuitive local explanation
Quick feature ranking Permutation Importance Simple, model-agnostic
Tree model analysis Tree Importance Fast, built-in
Regulatory compliance SHAP Theoretically defensible
Model comparison Permutation Importance Same metric across models

Complete Comparison

from sklearn.inspection import permutation_importance
import shap
import lime.lime_tabular

def compare_xai_methods(model, X_train, X_test, y_test, feature_names):
    """Compare all 4 XAI methods."""
    
    results = {}
    
    # 1. Tree Importance
    results['tree'] = dict(zip(
        feature_names,
        model.feature_importances_
    ))
    
    # 2. Permutation Importance
    perm_result = permutation_importance(
        model, X_test, y_test, n_repeats=10, random_state=42
    )
    results['permutation'] = dict(zip(
        feature_names,
        perm_result.importances_mean
    ))
    
    # 3. SHAP
    explainer = shap.TreeExplainer(model)
    shap_values = explainer.shap_values(X_test)
    results['shap'] = dict(zip(
        feature_names,
        np.abs(shap_values[1]).mean(axis=0)
    ))
    
    # 4. LIME (for single instance)
    lime_explainer = lime.lime_tabular.LimeTabularExplainer(
        X_train, feature_names=feature_names, mode='classification'
    )
    lime_exp = lime_explainer.explain_instance(
        X_test[0], model.predict_proba, num_features=len(feature_names)
    )
    results['lime'] = dict(lime_exp.as_list())
    
    return results

# Compare
results = compare_xai_methods(model, X_train, X_test, y_test, feature_names)

# Rank features by each method
for method, scores in results.items():
    print(f"\n{method.upper()} Rankings:")
    sorted_features = sorted(scores.items(), key=lambda x: abs(x[1]), reverse=True)
    for rank, (feat, score) in enumerate(sorted_features[:5], 1):
        print(f"  {rank}. {feat}: {score:.4f}")

Try It Yourself

Further Reading

💬 Discuss this topic on BestWordz Community

Conclusion

Explainable AI is not optional. Regulations require it. Users demand it. Debugging needs it.

The 4 methods serve different purposes:

  • SHAP — Theoretically grounded, comprehensive, but slow
  • LIME — Fast local explanations, but unstable
  • Permutation Importance — Simple, reliable, but global only
  • Tree Importance — Very fast, but trees only and biased

The best practice: use multiple methods. If all methods agree on a feature's importance, you can be confident. If they disagree, investigate further.

If you cannot explain your model's predictions, you do not fully understand your model.

💬 Discuss on BestWordz Community

Join the conversation about Python, Pandas, NumPy on the BestWordz Community forum.

Visit Forum →