Global explanations for tree-based models by decomposing regression or classification functions into the sum of main components and interaction components of arbitrary order. Calculates SHAP values and q-interaction SHAP for all values of q for tree-based models such as xgboost.
Usage
glex(object, x, max_interaction = NULL, features = NULL, ...)
# Default S3 method
glex(object, ...)
# S3 method for class 'rpf'
glex(object, x, max_interaction = NULL, features = NULL, ...)
# S3 method for class 'xgb.Booster'
glex(
object,
x,
max_interaction = NULL,
features = NULL,
max_background_sample_size = NULL,
weighting_method = "fastpd",
...
)
# S3 method for class 'ranger'
glex(
object,
x,
max_interaction = NULL,
features = NULL,
max_background_sample_size = NULL,
weighting_method = "fastpd",
...
)Arguments
- object
Model to be explained, either of class
xgb.Boosterorrpf.- x
Data to be explained.
- max_interaction
(
integer(1): NULL)
Maximum interaction size to consider. Defaults to using all possible interactions available in the model.
Forxgboost, this defaults to themax_depthparameter of the model fit.
If not set inxgboost, the default value of6is assumed.- features
Vector of column names in
xto calculate components for. Default isNULL, i.e. all features are used.- ...
Further arguments passed to methods.
- max_background_sample_size
The maximum number of background samples used for the FastPD algorithm, only used when
weighting_method = "fastpd". Defaults tonrow(x).- weighting_method
Use either "path-dependent", "fastpd" (default), or "empirical". See References for details.
Value
Decomposition of the regression or classification function.
A list with elements:
shap: SHAP values, derived from the functional decomposition as \(\phi_j = \sum_{S \ni j} m_S / |S|\). This reconstruction is only valid if the decomposition is complete: if it is constrained (seeconstrained), the components no longer sum to the full model prediction and the SHAP efficiency property cannot hold, soshapis a scalarNA(with a warning) whilemremains valid. For multiclass models, columns are class-specific like those ofm. Note thatrandomPlantedForestmodels report a singleinterceptfor all classes, so for multiclass modelsintercept + rowSums(shap)reconstructs the predicted class scores only approximately.m: Functional decomposition into all main and interaction components in the model, up to the degree specified bymax_interaction. The variable names correspond to the original variable names, with:separating interaction terms as one would specify in aformulainterface.intercept: Intercept term, the expected value of the prediction.constrained: Character vector naming the arguments that constrained the decomposition ("max_interaction","features"), orcharacter(0)if it is complete. Uselength(x$constrained) > 0to check whethershapis valid. A constraint that only drops terms whose value is zero leaves the decomposition unchanged;glex()confirms this against the model's predictions, reports it with a message, and treats the result as complete.remainder: What the dropped terms are collectively worth, per observation:prediction - (intercept + rowSums(m)). Present exactly when the decomposition is constrained, and absent otherwise, sointercept + rowSums(m) + remainderreconstructs the prediction in either case. For multiclassrandomPlantedForestmodels it is class-wise, mirroringm.Like
mandshap, it is on the scale that the model is decomposed on, which forxgboostis the link scale and not the response: for abinary:logisticmodel the reconstruction gives the margin, andplogis(intercept + rowSums(m) + remainder)gives the predicted probability.rangerprobability forests andrandomPlantedForestare decomposed on the response scale, where no such back-transformation is needed. Addingremainderto a probability is therefore never correct forxgboost.
Details
For parallel execution using xgboost models, register a backend, e.g. with
doParallel::registerDoParallel().
The different weighting methods are described in detail in Liu et al. (2025). The default
method is "fastpd" as it consistently estimates the correct partial dependence function.
References
Liu, J., Steensgaard, T., Wright, M. N., Pfister, N., & Hiabu, M. (2025). Fast Estimation of Partial Dependence Functions using Trees. Proceedings of the 42nd International Conference on Machine Learning, PMLR 267:39496-39534. PMLR | arXiv:2410.13448
Examples
# Random Planted Forest -----
library(randomPlantedForest)
rp <- rpf(mpg ~ ., data = mtcars[1:26, ], max_interaction = 2)
glex_rpf <- glex(rp, mtcars[27:32, ])
str(glex_rpf, list.len = 5)
#> List of 5
#> $ m :Classes ‘data.table’ and 'data.frame': 6 obs. of 55 variables:
#> ..$ cyl : num [1:6] 0.773 0.773 -0.352 0.245 -0.352 ...
#> ..$ disp : num [1:6] 0.695 1.276 -1.09 0.406 -0.94 ...
#> ..$ hp : num [1:6] 2.95 4.32e-05 -2.32 -1.02 -2.32 ...
#> ..$ drat : num [1:6] 1.516 -0.143 1.516 -0.37 -0.37 ...
#> ..$ wt : num [1:6] 1.62 1.763 0.593 0.567 -0.801 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ intercept : num 19.8
#> $ x :Classes ‘data.table’ and 'data.frame': 6 obs. of 10 variables:
#> ..$ cyl : num [1:6] 4 4 8 6 8 4
#> ..$ disp: num [1:6] 120.3 95.1 351 145 301 ...
#> ..$ hp : num [1:6] 91 113 264 175 335 109
#> ..$ drat: num [1:6] 4.43 3.77 4.22 3.62 3.54 4.11
#> ..$ wt : num [1:6] 2.14 1.51 3.17 2.77 3.57 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ constrained: chr(0)
#> $ shap :Classes ‘data.table’ and 'data.frame': 6 obs. of 10 variables:
#> ..$ cyl : num [1:6] 0.767 0.719 -0.35 0.334 -0.356 ...
#> ..$ disp: num [1:6] 0.715 1.293 -1.03 0.501 -0.879 ...
#> ..$ hp : num [1:6] 3.16 0.179 -2.187 -1.025 -2.175 ...
#> ..$ drat: num [1:6] 1.656 -0.118 1.611 -0.384 -0.35 ...
#> ..$ wt : num [1:6] 1.751 1.966 0.656 0.545 -0.791 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> - attr(*, "class")= chr [1:3] "glex" "rpf_components" "list"
# xgboost -----
library(xgboost)
x <- as.matrix(mtcars[, -1])
y <- mtcars$mpg
xg <- xgboost(x[1:26, ], y[1:26],
max_depth = 4, learning_rate = .1,
nrounds = 10, verbosity = 0, nthreads = 1
)
glex(xg, x[27:32, ])
#> glex object of subclass xgb_components
#> Explaining predictions of 6 observations with 31 terms of up to 5 degrees
#>
#> List of 5
#> $ shap :Classes ‘data.table’ and 'data.frame': 6 obs. of 10 variables:
#> ..$ cyl : num [1:6] 6.70e-02 6.95e-02 1.91e-17 6.95e-02 -2.71e-01 ...
#> ..$ disp: num [1:6] -0.501 2.445 -0.555 -0.555 -0.278 ...
#> ..$ hp : num [1:6] 1.1457 -0.0159 -0.2283 -0.0704 -0.7703 ...
#> ..$ drat: num [1:6] 0 0 0 0 0 0
#> ..$ wt : num [1:6] 0.623 0.809 0.759 0.531 -3.272 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ m :Classes ‘data.table’ and 'data.frame': 6 obs. of 31 variables:
#> ..$ cyl : num [1:6] 0.0742 0.0742 0 0.0742 0 ...
#> ..$ cyl:disp : num [1:6] 0 0 0 0 0 0
#> ..$ cyl:disp:hp : num [1:6] -1.11e-16 1.18e-16 -1.04e-17 4.16e-17 -1.04e-17 ...
#> ..$ cyl:disp:hp:wt : num [1:6] 3.33e-16 -3.96e-16 1.18e-16 -1.46e-16 -2.78e-17 ...
#> ..$ cyl:disp:wt : num [1:6] -4.16e-17 -1.39e-17 -1.39e-17 -4.16e-17 0.00 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ intercept : num 20.6
#> $ x :Classes ‘data.table’ and 'data.frame': 6 obs. of 10 variables:
#> ..$ cyl : num [1:6] 4 4 8 6 8 4
#> ..$ disp: num [1:6] 120.3 95.1 351 145 301 ...
#> ..$ hp : num [1:6] 91 113 264 175 335 109
#> ..$ drat: num [1:6] 4.43 3.77 4.22 3.62 3.54 4.11
#> ..$ wt : num [1:6] 2.14 1.51 3.17 2.77 3.57 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ constrained: chr(0)
#> - attr(*, "class")= chr [1:3] "glex" "xgb_components" "list"
glex(xg, mtcars[27:32, ])
#> glex object of subclass xgb_components
#> Explaining predictions of 6 observations with 31 terms of up to 5 degrees
#>
#> List of 5
#> $ shap :Classes ‘data.table’ and 'data.frame': 6 obs. of 11 variables:
#> ..$ mpg : num [1:6] 0 0 0 0 0 0
#> ..$ cyl : num [1:6] 6.70e-02 6.95e-02 1.91e-17 6.95e-02 -2.71e-01 ...
#> ..$ disp: num [1:6] -0.501 2.445 -0.555 -0.555 -0.278 ...
#> ..$ hp : num [1:6] 1.1457 -0.0159 -0.2283 -0.0704 -0.7703 ...
#> ..$ drat: num [1:6] 0 0 0 0 0 0
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ m :Classes ‘data.table’ and 'data.frame': 6 obs. of 31 variables:
#> ..$ cyl : num [1:6] 0.0742 0.0742 0 0.0742 0 ...
#> ..$ cyl:disp : num [1:6] 0 0 0 0 0 0
#> ..$ cyl:disp:hp : num [1:6] -1.11e-16 1.18e-16 -1.04e-17 4.16e-17 -1.04e-17 ...
#> ..$ cyl:disp:hp:wt : num [1:6] 3.33e-16 -3.96e-16 1.18e-16 -1.46e-16 -2.78e-17 ...
#> ..$ cyl:disp:wt : num [1:6] -4.16e-17 -1.39e-17 -1.39e-17 -4.16e-17 0.00 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ intercept : num 20.6
#> $ x :Classes ‘data.table’ and 'data.frame': 6 obs. of 11 variables:
#> ..$ mpg : num [1:6] 26 30.4 15.8 19.7 15 21.4
#> ..$ cyl : num [1:6] 4 4 8 6 8 4
#> ..$ disp: num [1:6] 120.3 95.1 351 145 301 ...
#> ..$ hp : num [1:6] 91 113 264 175 335 109
#> ..$ drat: num [1:6] 4.43 3.77 4.22 3.62 3.54 4.11
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ constrained: chr(0)
#> - attr(*, "class")= chr [1:3] "glex" "xgb_components" "list"
if (FALSE) { # \dontrun{
# Parallel execution
doParallel::registerDoParallel()
glex(xg, x[27:32, ])
} # }
# ranger -----
library(ranger)
x <- as.matrix(mtcars[, -1])
y <- mtcars$mpg
rf <- ranger(
x = x[1:26, ], y = y[1:26],
num.trees = 5, max.depth = 3,
node.stats = TRUE
)
glex(rf, x[27:32, ])
#> glex object of subclass xgb_components
#> Explaining predictions of 6 observations with 25 terms of up to 4 degrees
#>
#> List of 5
#> $ shap :Classes ‘data.table’ and 'data.frame': 6 obs. of 10 variables:
#> ..$ cyl : num [1:6] 0.624 0.624 -1.427 0.97 -1.427 ...
#> ..$ disp: num [1:6] 0.154 0.154 -0.628 0.364 -0.409 ...
#> ..$ hp : num [1:6] 0.1625 0.4063 -0.0812 -0.325 -0.325 ...
#> ..$ drat: num [1:6] 1.28 -1.12 1.36 -1.28 -1.28 ...
#> ..$ wt : num [1:6] 3.62 3.45 -1.83 -1.28 -2.79 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ m :Classes ‘data.table’ and 'data.frame': 6 obs. of 25 variables:
#> ..$ cyl : num [1:6] 0.97 0.97 -0.963 0.97 -0.963 ...
#> ..$ cyl:qsec : num [1:6] 0 0 -0.14 0 -0.14 ...
#> ..$ cyl:qsec:carb : num [1:6] 0.00 0.00 -1.46e-01 7.11e-16 -1.46e-01 ...
#> ..$ cyl:carb : num [1:6] -2.93e-01 -2.93e-01 -2.92e-01 -7.11e-16 -2.92e-01 ...
#> ..$ qsec : num [1:6] -0.236 -0.236 -0.236 -0.236 -0.236 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ intercept : num 22.2
#> $ x :Classes ‘data.table’ and 'data.frame': 6 obs. of 10 variables:
#> ..$ cyl : num [1:6] 4 4 8 6 8 4
#> ..$ disp: num [1:6] 120.3 95.1 351 145 301 ...
#> ..$ hp : num [1:6] 91 113 264 175 335 109
#> ..$ drat: num [1:6] 4.43 3.77 4.22 3.62 3.54 4.11
#> ..$ wt : num [1:6] 2.14 1.51 3.17 2.77 3.57 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ constrained: chr(0)
#> - attr(*, "class")= chr [1:3] "glex" "xgb_components" "list"
glex(rf, mtcars[27:32, ])
#> glex object of subclass xgb_components
#> Explaining predictions of 6 observations with 25 terms of up to 4 degrees
#>
#> List of 5
#> $ shap :Classes ‘data.table’ and 'data.frame': 6 obs. of 11 variables:
#> ..$ mpg : num [1:6] 0 0 0 0 0 0
#> ..$ cyl : num [1:6] 0.624 0.624 -1.427 0.97 -1.427 ...
#> ..$ disp: num [1:6] 0.154 0.154 -0.628 0.364 -0.409 ...
#> ..$ hp : num [1:6] 0.1625 0.4063 -0.0812 -0.325 -0.325 ...
#> ..$ drat: num [1:6] 1.28 -1.12 1.36 -1.28 -1.28 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ m :Classes ‘data.table’ and 'data.frame': 6 obs. of 25 variables:
#> ..$ cyl : num [1:6] 0.97 0.97 -0.963 0.97 -0.963 ...
#> ..$ cyl:qsec : num [1:6] 0 0 -0.14 0 -0.14 ...
#> ..$ cyl:qsec:carb : num [1:6] 0.00 0.00 -1.46e-01 7.11e-16 -1.46e-01 ...
#> ..$ cyl:carb : num [1:6] -2.93e-01 -2.93e-01 -2.92e-01 -7.11e-16 -2.92e-01 ...
#> ..$ qsec : num [1:6] -0.236 -0.236 -0.236 -0.236 -0.236 ...
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ intercept : num 22.2
#> $ x :Classes ‘data.table’ and 'data.frame': 6 obs. of 11 variables:
#> ..$ mpg : num [1:6] 26 30.4 15.8 19.7 15 21.4
#> ..$ cyl : num [1:6] 4 4 8 6 8 4
#> ..$ disp: num [1:6] 120.3 95.1 351 145 301 ...
#> ..$ hp : num [1:6] 91 113 264 175 335 109
#> ..$ drat: num [1:6] 4.43 3.77 4.22 3.62 3.54 4.11
#> .. [list output truncated]
#> ..- attr(*, ".internal.selfref")=<pointer: 0x5632b811ca10>
#> $ constrained: chr(0)
#> - attr(*, "class")= chr [1:3] "glex" "xgb_components" "list"
if (FALSE) { # \dontrun{
# Parallel execution
doParallel::registerDoParallel()
glex(rf, x[27:32, ])
} # }