Feature importance and SHAP values

XGBoost · Practical · 12 min

Stakeholders, debugging and leakage checks all require knowing what drives the predictions.

XGBoost offers several views of a model.

Built-in importance: booster.get_score(importance_type=...) with:

  • "weight": how many times a feature is used in splits,
  • "gain": average loss reduction from its splits (the most meaningful of the three),
  • "cover": average number of samples affected; plus total_gain and total_cover.

The scikit-learn wrapper's feature_importances_ uses gain by default for tree boosters. These are global, computed on training data, and different types can rank features differently.

SHAP contributions: booster.predict(dmatrix, pred_contribs=True) returns, for every sample, one exact SHAP value per feature plus a bias column. For each row they sum to the raw prediction (the log-odds for classification). That explains individual predictions ("why was this loan rejected?"), and averaging absolute values gives a consistent global importance. pred_interactions=True gives pairwise interaction values. No extra package is needed, though the shap library adds plots.

Permutation importance from scikit-learn (sklearn.inspection.permutation_importance) on held-out data is model-agnostic and measures the effect on your actual metric.

As with any importance, correlated features share or steal credit, and none of this is causal.

Example

import numpy as np
import xgboost as xgb
from sklearn.datasets import load_diabetes

X, y = load_diabetes(return_X_y=True, as_frame=True)
model = xgb.XGBRegressor(n_estimators=200, max_depth=3, learning_rate=0.05).fit(X, y)
booster = model.get_booster()

gain = booster.get_score(importance_type="gain")
print(sorted(gain, key=gain.get, reverse=True)[:3])      # ['s5', 'bmi', 'bp']

contribs = booster.predict(xgb.DMatrix(X.iloc[:2]), pred_contribs=True)
print(contribs.shape)                                    # (2, 11) = 10 features + bias
print(np.allclose(contribs.sum(axis=1), model.predict(X.iloc[:2]), atol=1e-3))  # True

row = dict(zip(list(X.columns) + ["bias"], contribs[0].round(1)))
print(row["bmi"], row["s5"], row["bias"])                # 26.2 20.7 152.1

Remember

  • Prefer gain over weight for built-in importance
  • pred_contribs=True gives exact per-row SHAP values that sum to the prediction
  • Use permutation importance on held-out data to measure impact on your metric

Watch out for

  • Comparing 'weight' importances across features with very different numbers of unique values.

Check yourself

  1. For a binary classifier, what do the SHAP values from pred_contribs=True sum to for each row?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: xgboost.readthedocs.io/en/stable/prediction.html