Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Suggests:
ISLR2,
knitr,
patchwork,
randomPlantedForest,
randomPlantedForest (>= 0.3.0),
ranger,
rmarkdown,
testthat (>= 3.0.0),
Expand All @@ -51,7 +51,7 @@ Remotes:
PlantedML/randomPlantedForest
Additional_repositories: https://plantedml.r-universe.dev
Config/roxygen2/version: 8.0.0
Config/roxygen2/markdown: TRUE
Config/testthat/edition: 3
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# glex 0.6.0.9000 (development version)

* `randomPlantedForest (>= 0.3.0)` is now required (in `Suggests:`): it fixes an
out-of-bounds read in `purify_3()` that crashed R on Windows
(PlantedML/randomPlantedForest#61), so rpf tests and examples run on all platforms.

* `$shap` is now a scalar `NA` (with a warning) when the decomposition is constrained via
`max_interaction` or `features`: a constrained decomposition does not sum to the
full model prediction, so SHAP values cannot be reconstructed from it without
Expand Down
2 changes: 1 addition & 1 deletion R/autoplot.R
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ autoplot.glex <- function(object, predictors, ...) {
#' `"Remaining terms"` label.
#' @param ... (Unused)
#'
#' @return A [`ggplot`][ggplot2::ggplot] object.
#' @returns A [`ggplot`][ggplot2::ggplot] object.
#' @export
#' @seealso [glex_vi]
#' @family Visualization functions
Expand Down
2 changes: 1 addition & 1 deletion R/data_bike.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#' A reduced version of the `Bikeshare` data as included with `ISLR2`.
#' The dataset has been converted to a [data.table::data.table()], with the following changes:
#'
#' - `hr` has been copnverted to a numeric
#' - `hr` has been converted to a numeric
#' - `workingday` was recoded to a binary `factor` with labels `c("No Workingday", "Workingday")`
#' - `season` was recoded to a `factor` with labels `c("Winter", "Spring", "Summer", "Fall")`
#' - Variables `atemp`, `day`, `registered` and `casual` were removed
Expand Down
42 changes: 20 additions & 22 deletions R/glex-options.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,26 @@
#' The color choices used across all `glex` visualization functions can be
#' adjusted globally via [options()]:
#'
#' \describe{
#' \item{`glex.palette`}{(`NULL`) Diverging palette used to color continuous
#' interaction effects in [plot_twoway_effects()] and
#' [plot_threeway_effects()]. The default `NULL` uses a blue/red gradient
#' built from `glex.colors_sign`, matching the look of the Python `shap`
#' and `shapiq` packages. Set to the name of a diverging
#' [scico][scico::scico] palette (e.g. `"vik"`, `"roma"`) to use that
#' instead.}
#' \item{`glex.palette_discrete`}{(`"Dark2"`) Discrete palette used to color
#' categorical predictors in interaction plots. Accepts a vector of colors
#' (used via [ggplot2::scale_color_manual()]), the string `"okabe-ito"`
#' (the colorblind-safe Okabe-Ito palette via [grDevices::palette.colors()]),
#' the name of a [scico][scico::scico] palette, or the name of an
#' [RColorBrewer][ggplot2::scale_color_brewer] palette.}
#' \item{`glex.colors_sign`}{(`c("#008BFB", "#FF0051")`) Two colors for
#' negative and positive contributions in [glex_explain()], also used as
#' the endpoints of the default continuous gradient. The defaults
#' follow the blue/red convention familiar from the Python `shap` and
#' `shapiq` packages.}
#' \item{`glex.color_line`}{(`"#194155"`) Color for main effect lines and
#' columns drawn by `autoplot()` and [plot_pdp()].}
#' }
#' * `glex.palette` (`NULL`): Diverging palette used to color continuous
#' interaction effects in [plot_twoway_effects()] and
#' [plot_threeway_effects()]. The default `NULL` uses a blue/red gradient
#' built from `glex.colors_sign`, matching the look of the Python `shap`
#' and `shapiq` packages. Set to the name of a diverging
#' [scico][scico::scico] palette (e.g. `"vik"`, `"roma"`) to use that
#' instead.
#' * `glex.palette_discrete` (`"Dark2"`): Discrete palette used to color
#' categorical predictors in interaction plots. Accepts a vector of colors
#' (used via [ggplot2::scale_color_manual()]), the string `"okabe-ito"`
#' (the colorblind-safe Okabe-Ito palette via [grDevices::palette.colors()]),
#' the name of a [scico][scico::scico] palette, or the name of an
#' [RColorBrewer][ggplot2::scale_color_brewer] palette.
#' * `glex.colors_sign` (`c("#008BFB", "#FF0051")`): Two colors for
#' negative and positive contributions in [glex_explain()], also used as
#' the endpoints of the default continuous gradient. The defaults
#' follow the blue/red convention familiar from the Python `shap` and
#' `shapiq` packages.
#' * `glex.color_line` (`"#194155"`): Color for main effect lines and
#' columns drawn by `autoplot()` and [plot_pdp()].
#'
#' @examples
#' # Use a scico palette for continuous effects instead of the default gradient
Expand Down
42 changes: 21 additions & 21 deletions R/glex.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
#' Defaults to using all possible interactions available in the model.\cr
#' For [`xgboost`][xgboost::xgb.train], this defaults to the `max_depth` parameter of the model fit.\cr
#' If not set in `xgboost`, the default value of `6` is assumed.
#' @param features Vector of column names in x to calculate components for. Default is \code{NULL}, i.e. all features are used.
#' @param features Vector of column names in `x` to calculate components for. Default is `NULL`, i.e. all features are used.
#' @param ... Further arguments passed to methods.
#'
#' @return Decomposition of the regression or classification function.
#' @returns Decomposition of the regression or classification function.
#' A `list` with elements:
#' * `shap`: SHAP values, derived from the functional decomposition as
#' \eqn{\phi_j = \sum_{S \ni j} m_S / |S|}. This reconstruction is only valid if the
Expand Down Expand Up @@ -71,17 +71,15 @@ glex.default <- function(object, ...) {

#' @rdname glex
#' @export
#' @examples
#' @examplesIf requireNamespace("randomPlantedForest", quietly = TRUE)
#'
#' # Random Planted Forest -----
#' if (requireNamespace("randomPlantedForest", quietly = TRUE)) {
#' 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)
#' }
glex.rpf <- function(object, x, max_interaction = NULL, features = NULL, ...) {
if (!requireNamespace("randomPlantedForest", quietly = TRUE)) {
stop(paste0(
Expand Down Expand Up @@ -168,15 +166,15 @@ glex.rpf <- function(object, x, max_interaction = NULL, features = NULL, ...) {
#'
#' @param max_background_sample_size The maximum number of background samples used for the FastPD algorithm, only used when `weighting_method = "fastpd"`. Defaults to `nrow(x)`.
#' @param weighting_method Use either "path-dependent", "fastpd" (default), or "empirical". See References for details.
#' @examples
#' @examplesIf requireNamespace("xgboost", quietly = TRUE)
#' # xgboost -----
#' if (requireNamespace("xgboost", quietly = TRUE)) {
#' 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)
#' max_depth = 4, learning_rate = .1,
#' nrounds = 10, verbosity = 0, nthreads = 1
#' )
#' glex(xg, x[27:32, ])
#' glex(xg, mtcars[27:32, ])
#'
Expand All @@ -185,7 +183,6 @@ glex.rpf <- function(object, x, max_interaction = NULL, features = NULL, ...) {
#' doParallel::registerDoParallel()
#' glex(xg, x[27:32, ])
#' }
#' }
glex.xgb.Booster <- function(
object,
x,
Expand Down Expand Up @@ -365,20 +362,24 @@ get_xgb_base_score <- function(object) {
#' @importFrom stats predict
#' @importFrom utils combn
#' @details
#' The different weighting methods are described in detail in Liu et al. (2024). The default method is "fastpd" as it consistently estimates the correct partial dependence function.
#' 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. (2024).
#' \emph{Fast Estimation of Partial Dependence Functions using Trees}.
#' arXiv preprint \href{https://arxiv.org/abs/2410.13448}{arXiv:2410.13448}.
#' @examples
#' 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](https://proceedings.mlr.press/v267/liu25bm.html) |
#' [arXiv:2410.13448](https://arxiv.org/abs/2410.13448)
#' @examplesIf requireNamespace("ranger", quietly = TRUE)
#' # ranger -----
#' if (requireNamespace("ranger", quietly = TRUE)) {
#' 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)
#' 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(rf, mtcars[27:32, ])
#'
Expand All @@ -387,7 +388,6 @@ get_xgb_base_score <- function(object) {
#' doParallel::registerDoParallel()
#' glex(rf, x[27:32, ])
#' }
#' }
glex.ranger <- function(
object,
x,
Expand Down Expand Up @@ -838,7 +838,7 @@ max_order <- function(trees) {
#' @param trees data.table
#' @param x observerations, matrix like data-structure
#' @param all_S all combinations of interactions up to certain order
#' @param weighting_method the weighting method that was supplied to \code{glex}
#' @param weighting_method the weighting method that was supplied to `glex`
#' @keywords internal
#' @noRd
tree_fun_wrapper <- function(
Expand Down
6 changes: 2 additions & 4 deletions R/glex_explain.R
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#' Preferred value may depend on the number of vertical elements, hence it may be necessary to adjust
#' this value as needed.
#'
#' @return A [ggplot][ggplot2::ggplot] object.
#' @returns A [ggplot][ggplot2::ggplot] object.
# Invisibly: A `list` with elements
# * `components`: A [`data.table`] of the prediction components scaled by their degree of interaction,
# grouped by their associated reference term.
Expand All @@ -30,18 +30,16 @@
#' @export
#' @family Visualization functions
#'
#' @examples
#' @examplesIf requireNamespace("randomPlantedForest", quietly = TRUE)
#' set.seed(1)
#' # Random Planted Forest -----
#' if (requireNamespace("randomPlantedForest", quietly = TRUE)) {
#' library(randomPlantedForest)
#'
#' rp <- rpf(mpg ~ ., data = mtcars[1:26, ], max_interaction = 2)
#'
#' glex_rpf <- glex(rp, mtcars[27:32, ])
#'
#' glex_explain(glex_rpf, id = 3, predictors = "hp", threshold = 0.01)
#' }
glex_explain <- function(
object,
id,
Expand Down
11 changes: 5 additions & 6 deletions R/glex_vi.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#' @param object Object of class `glex`.
#' @param ... (Unused)
#'
#' @return A [data.table::data.table()] with columns:
#' @returns A [data.table::data.table()] with columns:
#' * `degree` (`integer`): Degree of interaction of the `term`, with `1` being main effects,
#' `2` being 2-degree interactions etc.
#' * `term` (`character`): Model term, e.g. main effect `x1` or interaction term `x1:x2`, `x1:x3:x5` etc.
Expand All @@ -24,24 +24,23 @@
#' \deqn{\mathtt{m\_rel} = \frac{\mathtt{m}}{m_0}}
#'
#' @seealso [autoplot.glex_vi]
#' @examples
#' @examplesIf requireNamespace("xgboost", quietly = TRUE)
#' set.seed(1)
#'
#' # xgboost -----
#' if (requireNamespace("xgboost", quietly = TRUE)) {
#' 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)
#' max_depth = 4, learning_rate = .1,
#' nrounds = 10, verbosity = 0, nthreads = 1
#' )
#' glex_xgb <- glex(xg, x[27:32, ])
#' vi_xgb <- glex_vi(glex_xgb)
#'
#' library(ggplot2)
#' autoplot(vi_xgb)
#' autoplot(vi_xgb, by_degree = TRUE)
#' }
glex_vi <- function(object, ...) {
checkmate::assert_class(object, classes = "glex")

Expand Down
12 changes: 4 additions & 8 deletions R/plot_main_effect.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
#' Default is `"b"` for both sides. Set to `"none"` to disable rug plot.
#' @param ... Used for future expansion.
#'
#' @return A `ggplot2` object.
#' @returns A `ggplot2` object.
#' @import ggplot2
#' @export
#' @seealso [plot_pdp()]
#'
#' @examples
#' if (requireNamespace("randomPlantedForest", quietly = TRUE)) {
#' @examplesIf requireNamespace("randomPlantedForest", quietly = TRUE)
#' library(randomPlantedForest)
#'
#' # introduce factor variables to show categorical feature handling
Expand All @@ -32,7 +31,6 @@
#' # Main effects ----
#' plot_main_effect(components, "wt")
#' plot_main_effect(components, "cyl")
#' }
plot_main_effect <- function(object, predictor, rug_sides = "b", ...) {
plot_main_effect_impl(object, predictor, pdp = FALSE, ...)
}
Expand All @@ -46,13 +44,12 @@ plot_main_effect <- function(object, predictor, rug_sides = "b", ...) {
#' @param predictor `(character(1))` predictor names, e.g. `"x1"` to plot
#' main effect of `x1`.
#'
#' @return A `ggplot2` object.
#' @returns A `ggplot2` object.
#' @import ggplot2
#' @export
#' @seealso [plot_main_effect()]
#' @family Visualization functions
#' @examples
#' if (requireNamespace("randomPlantedForest", quietly = TRUE)) {
#' @examplesIf requireNamespace("randomPlantedForest", quietly = TRUE)
#' library(randomPlantedForest)
#'
#' # introduce factor variables to show categorical feature handling
Expand All @@ -66,7 +63,6 @@ plot_main_effect <- function(object, predictor, rug_sides = "b", ...) {
#'
#' plot_pdp(components, "wt")
#' plot_pdp(components, "cyl")
#' }
plot_pdp <- function(object, predictor, rug_sides = "b", ...) {
plot_main_effect_impl(object, predictor, pdp = TRUE)
}
Expand Down
4 changes: 1 addition & 3 deletions R/plot_twoway_effects.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#' @rdname plot_components
#' @export
#' @examples
#' if (requireNamespace("randomPlantedForest", quietly = TRUE)) {
#' @examplesIf requireNamespace("randomPlantedForest", quietly = TRUE)
#' library(randomPlantedForest)
#'
#' # 2-degree interaction effects ----
Expand All @@ -16,7 +15,6 @@
#' # 2d categorical, heatmap of arbitrary orientation
#' plot_twoway_effects(components, c("vs", "cyl"))
#' plot_twoway_effects(components, c("cyl", "vs"))
#' }
plot_twoway_effects <- function(object, predictors, rug_sides = "b", ...) {
checkmate::assert_class(object, "glex")
checkmate::assert_character(predictors, len = 2, unique = TRUE)
Expand Down
4 changes: 1 addition & 3 deletions R/print.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
#'
#' @export
#' @importFrom utils str
#' @examples
#' @examplesIf requireNamespace("randomPlantedForest", quietly = TRUE)
#' # Random Planted Forest -----
#' if (requireNamespace("randomPlantedForest", quietly = TRUE)) {
#' library(randomPlantedForest)
#' rp <- rpf(mpg ~ hp + wt + drat, data = mtcars[1:26, ], max_interaction = 2)
#'
#' glex(rp, mtcars[27:32, ])
#' }
print.glex <- function(x, ...) {
n <- nrow(x$x)
n_m <- ncol(x$m)
Expand Down
2 changes: 1 addition & 1 deletion R/theme_glex.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#' @param grid_x (`TRUE`) Display horizontal grid lines?
#' @param grid_y (`FALSE`) Display vertical grid lines?
#'
#' @return A `ggplot2` theme object
#' @returns A `ggplot2` theme object
#' @export
#' @import ggplot2
#' @examples
Expand Down
10 changes: 4 additions & 6 deletions R/utils-components.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
#' @param components An object of class `glex`.
#' @param term (`character(1)`) A main term name to subset by, e.g. `"x1"`.
#'
#' @return
#' - `subset_components`: An object of class `glex`.
#' - `subset_component_names`: A character vector.
#' @returns
#' - `subset_components()`: An object of class `glex`.
#' - `subset_component_names()`: A character vector.
#'
#' @rdname subset_components
#' @export
#' @examples
#' if (requireNamespace("randomPlantedForest", quietly = TRUE)) {
#' @examplesIf requireNamespace("randomPlantedForest", quietly = TRUE)
#' library(randomPlantedForest)
#'
#' # introduce factor variables to show categorical feature handling
Expand All @@ -26,7 +25,6 @@
#' subset_components(components, "hp")
#'
#' subset_component_names(components, "hp")
#' }
#'
subset_components <- function(components, term) {
checkmate::assert_string(term)
Expand Down
1 change: 1 addition & 0 deletions air.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ line-ending = "lf"
persistent-line-breaks = true # preserve existing line breaks when reformatting
default-exclude = true # use built-in default exclusions
assignment-style = "arrow"
exclude = ["attic/", ".git/"]
Loading
Loading