Explain a Model: Permutation Importance, Target Leakage, Partial Dependence and Exact Shapley Values
Hands-on lab · IDE in your browser

Explain a Model: Permutation Importance, Target Leakage, Partial Dependence and Exact Shapley Values

Audit a loan-default model that looks too good. Find the column it leans on with permutation importance, trace that column to after the decision with the data dictionary, check the honest model's behaviour with partial dependence and ICE curves, compute exact Shapley values for single applicants, and turn them into truthful reason codes.

Time
50 min
Checked steps
5
Level
Intermediate
Setup
None
Read step 1

Hands-on labs require Pro · $29.99/mo · cancel anytime

Lab cockpit50 min · 5 stepsSession running
4 / 5 steps passingReasons an applicant can act on · step 5 of 5
explain.py▶ Run✓ Check
NOT_STATED = "Employment history was not provided"  def reason_codes(phi, x, features, k=3):    """The texts of the (at most k) features that pushed this applicant's risk UP the most (positive Shapley value),    largest first. employment_years at 0 means "not stated": use NOT_STATED for it then."""     
TerminalOutput

The job

Harbour Credit Union's new default model scores an AUC of 0.997 and is about to decide personal loans. Before it does, you find out what it relies on, whether it could work on real applications, and how to tell a declined applicant why.

5 steps, each checked when you finish it

A check runs your work at the end of every step. Hints and the full solution are there if you get stuck.

  1. 1

    What the model leans on

    Harbour Credit Union's data team trained a default model on 6,000 past personal loans.

    You writepermutation_importance()
  2. 2

    A feature from the future

    A column that predicts the outcome almost perfectly usually records the outcome.

    You writeusable_features()
  3. 3

    How inputs move predictions

    With the honest model, check that its behaviour makes sense to a lender.

    You writeice()
  4. 4

    Shapley values, exactly

    Why was this applicant scored 0.30?

    You writecoalition_value()
  5. 5

    Reasons an applicant can act on

    A declined applicant is entitled to know the main reasons.

    You writereason_codes()

Step 1 as it appears in the lab

The lab’s own text. The hint and the solution stay inside the lab.

Step 1: What the model leans on

Harbour Credit Union's data team trained a default model on 6,000 past personal loans. It scores an AUC of 0.997, better than any credit model has a right to be. Before anyone uses it, find out what it relies on.

Permutation importance asks one question per input: shuffle this column, breaking its link to the outcome, and how much worse does the model get? If the model depends on a column, the score drops. It works on any model, because it only needs predictions.

Do this

1. Write permutation_importance() in explain.py.

2. Run it on the model trained with every column.

explain.py, the file you edit100 lines
"""Explaining Harbour Credit Union's loan-default model. You write the functions marked TODO, one step at a time."""
from itertools import combinations
from math import factorial

import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split

LABEL = "defaulted"


def load():
    """(train, valid): a stratified 70/30 split of loans.csv."""
    df = pd.read_csv("loans.csv")
    return train_test_split(df, test_size=0.3, random_state=0, stratify=df[LABEL])


def fit(train, features):
    return HistGradientBoostingClassifier(random_state=0).fit(train[features], train[LABEL])


def auc(model, data, features):
    return float(roc_auc_score(data[LABEL], model.predict_proba(data[features])[:, 1]))


# ---------- Step 1: what does the model lean on ----------

def permutation_importance(model, data, features, repeats=5, seed=0):
    """{feature: mean drop in AUC} when that feature's column is shuffled (rng.permutation, rng =
    np.random.default_rng(seed), one shared generator), `repeats` times, the other columns left as they are."""
    # TODO (Step 1): base AUC; for each feature, `repeats` times: copy data, shuffle that column
    # with rng.permutation, AUC again; the mean drop per feature.
    raise NotImplementedError("Step 1: write permutation_importance()")


# ---------- Step 2: a feature from the future ----------

def usable_features(dictionary, decision_point="application"):
    """The columns recorded at the decision point, in dictionary order: what exists when the loan is decided."""
    raise NotImplementedError("usable_features() arrives in Step 2")


def single_feature_auc(data, column):
    """How well one column alone separates defaults: roc_auc_score of the raw values, flipped to max(auc, 1 - auc)."""
    raise NotImplementedError("single_feature_auc() arrives in Step 2")


# ---------- Step 3: how each input moves the prediction ----------

def ice(model, X, feature, grid):
    """Individual conditional expectation: an array (rows of X, len(grid)) with each row's predicted default
    probability when `feature` is set to each grid value and everything else stays as it is."""
    raise NotImplementedError("ice() arrives in Step 3")


def partial_dependence(model, X, feature, grid):
    """The average of ice() over the rows: one mean probability per grid value."""
    raise NotImplementedError("partial_dependence() arrives in Step 3")


def direction(values, tol=0.005):
    """"up" if every step rises by more than -tol and the curve ends higher than it starts, "down" for the mirror
    image, else "mixed"."""
    raise NotImplementedError("direction() arrives in Step 3")


# ---------- Step 4: Shapley values, exactly ----------

def coalition_value(model, x, background, S):
    """The mean predicted probability over the background rows after setting the columns in S (a tuple of column
    positions) to the applicant x's values."""
    raise NotImplementedError("coalition_value() arrives in Step 4")


def shapley_values(model, x, background):
    """One value per column: the average, over every coalition S of the other columns, of v(S + {j}) - v(S),
    weighted |S|! (n - |S| - 1)! / n!."""
    raise NotImplementedError("shapley_values() arrives in Step 4")


# ---------- Step 5: reasons an applicant can act on ----------

REASONS = {
    "income": "Income is low for the amount requested",
    "debt_to_income": "Existing debt payments are high compared with income",
    "loan_amount": "The amount requested is high",
    "term_months": "The repayment term is long",
    "credit_history_years": "Credit history is short",
    "late_payments_12m": "Recent late payments on other credit",
    "employment_years": "Time with current employer is short",
}
NOT_STATED = "Employment history was not provided"


def reason_codes(phi, x, features, k=3):
    """The texts of the (at most k) features that pushed this applicant's risk UP the most (positive Shapley value),
    largest first. employment_years at 0 means "not stated": use NOT_STATED for it then."""
    raise NotImplementedError("reason_codes() arrives in Step 5")
Provided for you:data_dictionary.csvloans.csvtry_it.py

Frequently asked questions

What is target leakage?

A feature that is only known after the outcome, or because of it, such as calls from a collections team on a loan that defaulted. It makes offline metrics look excellent, while the feature does not exist when the real prediction must be made.

What is the difference between permutation importance and Shapley values?

Permutation importance measures how much a model's overall score depends on each feature by shuffling it. Shapley values explain one prediction, splitting the difference from the average prediction among the features.

How are exact Shapley values computed?

For each feature, average its marginal contribution over every coalition of the other features, weighted by |S|!(n-|S|-1)!/n!. A coalition's value is the mean prediction with its features fixed to the case's values and the rest drawn from background data.

Explaining machine learning models

Explanation methods answer different questions: what a model depends on overall, how it responds to one input, and why it scored one case as it did. This lab builds each from scratch. You compute permutation importance, detect target leakage with a data dictionary and single-feature AUCs, draw partial dependence and ICE curves, calculate exact interventional Shapley values over every coalition, and produce adverse-action reason codes.