Skip to main content
Version: v1.4.1

Explainability

xplainable models are glass-box: every prediction decomposes exactly into a base value plus one contribution per feature. Nothing is approximated after the fact — the breakdown is the computation the model performed.

Feel it before you read about it — drag the inputs and watch the breakdown re-sum to the score:

Live breakdown — churn modelreal model output
26.7%churn probability (proba)link score: -1.0089pred: RETAIN
base_value
+0.062
avg_order_value = 84
-0.073
discount_applied = 0.1
-0.167
plan = premium
-0.172
tenure_days = 381
-0.147
tenure_days_&_avg_order_value
+0.001
tenure_days_&_discount_applied
+0.085
tenure_days_&_plan
-0.294
avg_order_value_&_discount_applied
-0.059
avg_order_value_&_plan
-0.033
discount_applied_&_plan
-0.212
base + Σ contributions = -1.0090 → sigmoid → 26.7%
Responses are precomputed /v1/predict outputs from a real trained model (inputs snap to the demo grid). Interaction terms carry the _&_ separator — watch them shift as the rest of the row changes.

The additive contract

For every scored row:

score = base_value + sum(feature scores)

This holds exactly, not approximately. The breakdown array returned by /predict lists base_value first, followed by one entry per feature:

"breakdown": [
{ "feature": "base_value", "value": null, "score": -0.20 },
{ "feature": "tenure_days", "value": "420", "score": 0.85 },
{ "feature": "plan", "value": "premium", "score": 0.17 }
]
  • feature — the feature name (or base_value for the intercept)
  • value — the input value the row carried for that feature, as a string
  • score — that feature's additive contribution to this row's score

Which scale is the breakdown on?

Model typeBreakdown scaleSums to
ClassificationLink scale (log-odds / logit)score (convert with sigmoid to compare against proba)
RegressionRaw target unitsscore, which equals pred

For classification the calibrated probability proba is derived from the link-scale score, so a positive feature score always pushes the probability up and a negative one pushes it down — but the size of the probability change depends on where the row sits on the sigmoid.

Interaction terms (v2 models)

v2 (XGM) models can include pairwise interaction terms. These appear in the breakdown as their own entries with an "_&_" separator in the name and a null value (there is no single input value to report):

{ "feature": "tenure_days_&_plan", "value": null, "score": 0.09 }

The additive contract still holds — interaction scores are part of the sum.

Why two similar rows can get different explanations

When a model carries interactions, a feature's contribution for a given row is conditioned on the rest of that row. The same discount_applied = 0.1 can contribute +0.3 for a long-tenure customer and -0.1 for a new one, because the interaction terms fold the other feature values into its effective contribution. This is the mechanism behind personalized optimization results: each row gets its own contribution curves, so the optimal intervention is genuinely row-specific.

Model-level explainers

Row breakdowns explain a single prediction. For a model-wide view, use the platform SDK.

explain_model()

Digests the model profile into the base value and the top features by importance, each with an effect direction and a one-line summary:

explanation = client.workflow.explain_model(model_id)
{
"model_id": "...",
"version_id": "...",
"target": "churned",
"base_value": -0.2,
"n_features": 14,
"top_features": [
{
"name": "tenure_days",
"importance": 0.35,
"direction": "decreasing",
"summary": "Higher tenure_days lowers the prediction."
}
]
}

direction is one of increasing, decreasing, mixed, or flat for numeric features, and categorical for categorical ones.

get_model_profile()

The full raw profile — per-feature score curves (numeric bins) and per-category scores — as rendered in the platform UI:

profile = client.models.get_model_profile(version_id)

get_feature_info()

Feature types, health metrics, and distributions:

info = client.models.get_feature_info(version_id)
Trimming payloads

If you only need decisions and not explanations, call /predict with show_breakdown=false — responses are substantially smaller.