📦 Optional dependencies
This article uses features from the pdstools
explanationsextra. Install with your favorite package manager, e.g.uv pip install "pdstools[explanations]".
Global Explanations for Adaptive Gradient Boosting Models¶
This notebook demonstrates how to analyze and visualize global explanations for Adaptive Gradient Boosting models in Pega Adaptive Decision Manager (ADM). Global explanations provide insights into which predictors have the most influence on model predictions and how different predictor values correlate with the model’s scores.
Important: The SHAP explanation datasets used in this notebook are available starting with Pega Infinity ‘25. In earlier versions, the explanation files are not written to the repository.
The explanation files contain SHAP (SHapley Additive exPlanations) contributions for a sample of model executions. This notebook shows how these contributions are aggregated to provide global explanations for Gradient Boosting models. In Pega Infinity ‘25, a Global Explanations report can also be generated directly from Prediction Studio.
Note on earlier versions: In versions prior to Infinity ‘25, Feature Importance (also known as Predictor Importance) is already available for Gradient Boosting models and uses SHAP values. In Infinity ‘25, we enhance this by providing detailed insights into the correlation between predictor values and the model’s score (for example, high income may correlate with a high contribution to the propensity score for a platinum credit card offer) rather than just the importances of predictors. See: https://docs.pega.com/bundle/platform/page/platform/decision-management/view-summarized-reports-adm.html
Pre-Aggregated data exported from Infinity¶
Prerequisite: You have already exported pre-aggregated explanation files from Infinity.
Parameters:
base_path: the folder that contains the pre-aggregated explanation filesmodel_name:optional- the model rule to check for explanations; if not passed, will pick up any file in the folderfrom_date:optional- if not passed, will be today - 7 daysto_date:optional- if not passed, will be today
[2]:
from pdstools import datasets
import datetime
import polars as pl
import logging
# logging.basicConfig(level=logging.INFO) # Uncomment to see progress for large files
# Fetched over HTTPS from the pdstools repository, so this works from an
# installed package as well as from a repository checkout.
explanations = datasets.sample_explanations(
model_name='AdaptiveBoostCT',
from_date=datetime.datetime(2024, 3, 28),
to_date=datetime.datetime(2025, 3, 28),
)
Plotting contributions¶
The contributions_overall() method plots the top-n most influential predictors and, for each of them, shows how individual predictor values contribute to the model score. Numeric values are binned (max 10 bins), symbolic predictors show the top-k categories.
Results can be shown for the model overall or drilled down into the action hierarchy (direction, channel, issue, group, action name), referred to as “context” in the API.
Overall model¶
Calling contributions_overall() without selecting a level in the action hierarchy aggregates over all actions.
Parameters:
top_n: Number of top predictors to plot.top_k: Number of top predictor values for symbolic predictors to plot.remaining: IfTrue, the remaining predictors will be plotted as a single bar.missing: IfTrue, the missing values will be plotted as a separate bar.descending: IfTrue, the predictors will be sorted in descending order of their contributions, i.e. least contributing predictors will be plotted first.sort_by: Method to calculate contributions. Options includecontribution,contribution_abs,contribution_weighted. Default iscontribution_abs(average absolute contributions to predictions).display_by: Contribution type to display when plotting. Options includecontribution,contribution_abs,contribution_weighted. Default iscontribution(average contributions to predictions).
[3]:
overall_model, model_top_predictors = explanations.plot.contributions_overall(top_n=3, top_k=5, remaining=True)
[4]:
overall_model.show()
[5]:
for top_predictor in model_top_predictors:
top_predictor.show()
By level in the action hierarchy¶
[6]:
selected_context = explanations.aggregates.unique_contexts()[0]
selected_context
[6]:
{'pyChannel': 'PegaBatch',
'pyDirection': 'E2E Test',
'pyGroup': 'E2E Test',
'pyIssue': 'Batch',
'pyName': 'P13'}
[7]:
context_header, overall_by_context, context_top_predictors = explanations.plot.contributions_by_context(
context=selected_context,
top_n=3,
top_k=5,
)
display(context_header)
display(overall_by_context)
[8]:
for top_predictor in context_top_predictors:
top_predictor.show()
Advanced data exploration¶
For more control, you can work with the aggregates object directly, inspect the underlying data, and build custom analyses.
[9]:
aggregates = explanations.aggregates # load the aggregated data
Inspect data for the overall model¶
Get the top-n predictors and their contributions using predictor_contributions().
[10]:
df_overall = aggregates.predictor_contributions(top_n = 3, remaining=False)
df_overall
[10]:
| context_partition | predictor_name | predictor_type | contribution | contribution_abs | contribution_weighted | contribution_weighted_abs | frequency | contribution_min | contribution_max |
|---|---|---|---|---|---|---|---|---|---|
| str | str | str | f64 | f64 | f64 | f64 | i64 | f64 | f64 |
| "whole_model" | "pyName" | "SYMBOLIC" | -0.021242 | 0.021307 | -0.021245 | 0.02131 | 9214 | -0.042513 | 0.02603 |
| "whole_model" | "Age" | "NUMERIC" | -0.011185 | 0.011656 | -0.011108 | 0.011488 | 9136 | -0.032099 | 0.023485 |
| "whole_model" | "Occupation" | "SYMBOLIC" | -0.008861 | 0.010097 | -0.008921 | 0.010072 | 9152 | -0.029788 | 0.06411 |
Inspect the most influential values of those predictors using predictor_value_contributions().
[11]:
top_n_predictors = df_overall.select(pl.col('predictor_name')).unique().to_series().to_list()
aggregates.predictor_value_contributions(
predictors=top_n_predictors,
top_k = 2,
remaining=False
)
[11]:
| context_partition | predictor_name | predictor_type | bin_order | bin_contents | contribution | contribution_abs | contribution_weighted | contribution_weighted_abs | frequency | contribution_min | contribution_max | sort_column | sort_value |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | i64 | str | f64 | f64 | f64 | f64 | i64 | f64 | f64 | str | f64 |
| "whole_model" | "Age" | "NUMERIC" | 0 | "MISSING" | -0.012488 | 0.01451 | -0.000466 | 0.000542 | 341 | -0.032099 | 0.023485 | "bin_order" | 0.0 |
| "whole_model" | "Age" | "NUMERIC" | 4 | "[38.000:43.000]" | -0.012895 | 0.013182 | -0.001242 | 0.00127 | 880 | -0.026761 | 0.014212 | "bin_order" | 4.0 |
| "whole_model" | "Occupation" | "SYMBOLIC" | 5 | "MISSING" | -0.011989 | 0.012913 | -0.000314 | 0.000339 | 240 | -0.0268 | 0.029334 | "contribution_abs" | 0.012913 |
| "whole_model" | "Occupation" | "SYMBOLIC" | 45 | "Psychotherapist, child" | -0.016364 | 0.016364 | -0.000245 | 0.000245 | 137 | -0.029788 | -0.007757 | "contribution_abs" | 0.016364 |
| "whole_model" | "Occupation" | "SYMBOLIC" | 31 | "TEFL teacher" | 0.018942 | 0.019132 | 0.000339 | 0.000343 | 164 | -0.011896 | 0.06411 | "contribution_abs" | 0.019132 |
| "whole_model" | "pyName" | "SYMBOLIC" | 4 | "P18" | -0.024549 | 0.024549 | -0.001292 | 0.001292 | 485 | -0.037306 | -0.009315 | "contribution_abs" | 0.024549 |
| "whole_model" | "pyName" | "SYMBOLIC" | 2 | "P1" | -0.02562 | 0.02562 | -0.001396 | 0.001396 | 502 | -0.042513 | -0.010836 | "contribution_abs" | 0.02562 |
Inspect data by level in the action hierarchy¶
The same methods can drill down into the action hierarchy instead of looking at the overall model.
[12]:
import random
context_info = random.choice(aggregates.unique_contexts())
print('Selected random context: \n')
for key, value in context_info.items():
print(f'{key}: {value}')
df_by_context = aggregates.predictor_contributions(
context=context_info,
top_n=3,
remaining=False)
df_by_context
Selected random context:
pyChannel: PegaBatch
pyDirection: E2E Test
pyGroup: E2E Test
pyIssue: Batch
pyName: P5
[12]:
| context_partition | predictor_name | predictor_type | contribution | contribution_abs | contribution_weighted | contribution_weighted_abs | frequency | contribution_min | contribution_max |
|---|---|---|---|---|---|---|---|---|---|
| str | str | str | f64 | f64 | f64 | f64 | i64 | f64 | f64 |
| "{"partition":{"pyChannel":"Peg… | "pyName" | "SYMBOLIC" | -0.022571 | 0.022571 | -0.022571 | 0.022571 | 414 | -0.033339 | -0.004782 |
| "{"partition":{"pyChannel":"Peg… | "Occupation" | "SYMBOLIC" | -0.0094 | 0.010793 | -0.009172 | 0.010597 | 464 | -0.022903 | 0.046346 |
| "{"partition":{"pyChannel":"Peg… | "Age" | "NUMERIC" | -0.010168 | 0.010452 | -0.010291 | 0.010496 | 439 | -0.021089 | 0.015328 |
[13]:
top_n_predictors = df_by_context.select(pl.col('predictor_name')).unique().to_series().to_list()
aggregates.predictor_value_contributions(
predictors=top_n_predictors,
top_k=2,
context=context_info,
remaining=False)
[13]:
| context_partition | predictor_name | predictor_type | bin_order | bin_contents | contribution | contribution_abs | contribution_weighted | contribution_weighted_abs | frequency | contribution_min | contribution_max | sort_column | sort_value |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | i64 | str | f64 | f64 | f64 | f64 | i64 | f64 | f64 | str | f64 |
| "{"partition":{"pyChannel":"Peg… | "Age" | "NUMERIC" | 0 | "MISSING" | -0.008347 | 0.009816 | -0.000247 | 0.000291 | 13 | -0.01875 | 0.004834 | "bin_order" | 0.0 |
| "{"partition":{"pyChannel":"Peg… | "Age" | "NUMERIC" | 5 | "[42.500:48.000]" | -0.013201 | 0.013201 | -0.001293 | 0.001293 | 43 | -0.021089 | -0.006434 | "bin_order" | 5.0 |
| "{"partition":{"pyChannel":"Peg… | "Age" | "NUMERIC" | 6 | "[48.000:53.000]" | -0.013403 | 0.013403 | -0.001313 | 0.001313 | 43 | -0.019928 | -0.00803 | "bin_order" | 6.0 |
| "{"partition":{"pyChannel":"Peg… | "Occupation" | "SYMBOLIC" | 48 | "MISSING" | -0.006484 | 0.009999 | -0.000042 | 0.000065 | 3 | -0.015616 | 0.005273 | "contribution_abs" | 0.009999 |
| "{"partition":{"pyChannel":"Peg… | "Occupation" | "SYMBOLIC" | 12 | "Psychotherapist, child" | -0.018079 | 0.018079 | -0.000468 | 0.000468 | 12 | -0.022903 | -0.014263 | "contribution_abs" | 0.018079 |
| "{"partition":{"pyChannel":"Peg… | "Occupation" | "SYMBOLIC" | 22 | "TEFL teacher" | 0.0246 | 0.0246 | 0.00053 | 0.00053 | 10 | 0.016159 | 0.046346 | "contribution_abs" | 0.0246 |
| "{"partition":{"pyChannel":"Peg… | "pyName" | "SYMBOLIC" | 1 | "P5" | -0.022571 | 0.022571 | -0.022571 | 0.022571 | 414 | -0.033339 | -0.004782 | "contribution_abs" | 0.022571 |
[ ]: