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
32 changes: 32 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@ ggRandomForests v3.5.1
`method = "unsupv"`. It is benign -- the pointer is formed, never
dereferenced -- and the fix belongs upstream (`kogalur/randomForestSRC` PR
#478); `method = "rnd"` avoids it in the meantime.
* `gg_roc()` on an `rfsrc` forest now honors `which_outcome = 0`. The help page
has always documented `0` as the numeric spelling of `"all"`, but only the
string was normalized, so `0` fell through to `predicted[, 0]`. That is a
legal zero-column subset rather than an error, so the threshold sweep ran on
empty input and returned a two-row frame with no `sens`/`spec` columns, which
then broke `calc_auc()`. Both spellings now take the same route: a warning,
and a fallback to class 1. The macro-average that will replace the fallback
is still tracked under #72.
* The three ROC entry points still disagree about what "all classes" means --
`gg_roc()` on a `randomForest` fit macro-averages, `gg_roc()` on an `rfsrc`
fit falls back to class 1, and a direct `plot.gg_roc()` call on a raw
multi-class forest overlays one curve per class. That divergence is
unchanged here, but `?gg_roc` and `?plot.gg_roc` now say so instead of
implying the paths agree. Both also correct a longer-standing claim: a raw
forest passed to plain `plot()` never reaches `plot.gg_roc()` at all,
because `randomForestSRC` and `randomForest` register their own `plot`
methods and S3 dispatch prefers them. That branch is reachable only by
naming the method outright. `?gg_roc` further stops advertising character
class names on the `rfsrc` path, which only the `randomForest` method
accepts.
* `gg_partial_rfsrc()` validates `rf_model` before using it. It read `$xvar`
and `$xvar.names` first, so a non-forest failed with base R's "argument is of
length zero" rather than naming the problem. It now matches the error style
already used by `gg_error()`, `gg_vimp()`, `gg_variable()` and `gg_rfsrc()`.
* The `pbc` examples on `?gg_error`, `?plot.gg_error`, `?gg_vimp` and
`?plot.gg_rfsrc` lost their editorial asides and a stray trailing comma in
the `data()` call. The munging block itself is still duplicated across those
pages; consolidating it is deferred.
* `tests/testthat/test_lint.R` runs again, wrapped in `skip_on_cran()`. It had
been commented out entirely, so the suite enforced nothing about style
locally even though CI kept its own lint job. The guard keeps it off the
`R CMD check` clock.

ggRandomForests v3.5.0
======================
Expand Down
26 changes: 21 additions & 5 deletions R/calc_roc.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,24 @@
####**********************************************************************
####**********************************************************************
# Internal helper: normalize the which_outcome argument.
# "all" is not yet fully supported; fall back to class 1 with a warning.
# "all" is not yet fully supported on the rfsrc path; fall back to class 1
# with a warning. `0` is the documented numeric spelling of "all" and must
# take the same route: left unnormalized it indexes as `predicted[, 0]`,
# which is legal R that yields a zero-column matrix, so the whole threshold
# sweep runs on empty input and returns a degenerate two-row object with no
# sens/spec columns rather than raising an error. See #72 for the
# macro-average that will eventually replace this fallback.
.validate_which_outcome <- function(which_outcome) {
if (identical(which_outcome, "all")) {
warning("Must specify which_outcome for now.")
is_zero <- is.numeric(which_outcome) &&
length(which_outcome) == 1L &&
!is.na(which_outcome) &&
which_outcome == 0
if (identical(which_outcome, "all") || is_zero) {
warning(
"which_outcome = ", if (is_zero) "0" else "\"all\"",
" is not yet supported for rfsrc forests; falling back to class 1. ",
"Pass an explicit class index to select a different class."
)
return(1L)
}
which_outcome
Expand All @@ -39,8 +53,10 @@
#' \code{object$y} for randomForest.
#' @param which_outcome Integer index of the class for which the ROC curve is
#' computed (e.g. \code{1} for the first class, \code{2} for the second).
#' Use \code{"all"} to request all classes (currently falls back to class 1
#' with a warning).
#' Use \code{"all"}, or its numeric spelling \code{0}, to request all
#' classes. The \code{randomForest} method returns a macro-averaged
#' one-vs-rest curve; the \code{rfsrc} method warns and falls back to
#' class 1 (see #72).
#' @param oob Logical; if \code{TRUE} (default for rfsrc) use OOB predicted
#' probabilities. Forced to \code{FALSE} for \code{randomForest} objects.
#' @param ... Extra arguments passed to helper functions (currently unused).
Expand Down
6 changes: 2 additions & 4 deletions R/gg_error.R
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,8 @@
#' plot(gg_dta)
#'
#' ## ------------- pbc data
#' # Load a cached randomForestSRC object
#' # We need to create this dataset
#' data(pbc, package = "randomForestSRC",)
#' # For whatever reason, the age variable is in days... makes no sense to me
#' data(pbc, package = "randomForestSRC")
#' # Recode: binary columns to logical, low-cardinality columns to factors.
#' for (ind in seq_len(dim(pbc)[2])) {
#' if (!is.factor(pbc[, ind])) {
#' if (length(unique(pbc[which(!is.na(pbc[, ind])), ind])) <= 2) {
Expand Down
9 changes: 9 additions & 0 deletions R/gg_partial_rfsrc.R
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ gg_partial_rfsrc <- function(rf_model,
partial.type = c("surv", "chf", "mort"),
cat_limit = 10,
n_eval = 25) {
# Validate before dereferencing. $xvar and $xvar.names on a non-forest give
# NULL, and the ncol()/sum() comparison below then fails with base R's
# "argument is of length zero" rather than naming the real problem.
if (!inherits(rf_model, "rfsrc")) {
stop("gg_partial_rfsrc: expected an 'rfsrc' object; ",
"got an object of class ", paste(class(rf_model), collapse = "/"), ".",
call. = FALSE)
}

if (is.null(newx)) {
newx <- rf_model$xvar
}
Expand Down
18 changes: 14 additions & 4 deletions R/gg_roc.R
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@
#' \code{\link[randomForest]{randomForest}} object. Only forests with
#' \code{family == "class"} (rfsrc) or \code{type == "classification"}
#' (randomForest) are supported.
#' @param which_outcome Integer index or character name of the class to score.
#' For binary forests this is usually \code{1} or \code{2}; for multi-class
#' forests, any valid class index or level name. \code{which_outcome = "all"}
#' or \code{0} behaves differently by engine:
#' @param which_outcome Index of the class to score: \code{1} or \code{2} for
#' a binary forest, any valid class index for a multi-class one. A character
#' level name is accepted by the \code{randomForest} method only; the
#' \code{rfsrc} method requires an integer index.
#' \code{which_outcome = "all"} or \code{0} behaves differently by engine:
#' \describe{
#' \item{\code{randomForest} method}{Returns a macro-averaged
#' one-vs-rest ROC computed over the per-class probabilities.}
Expand Down Expand Up @@ -60,6 +61,15 @@
#' }
#' Pass it to \code{\link{calc_auc}} for the area under the curve.
#'
#' @section Defaults across entry points:
#' \code{plot(gg_roc(x))} and a direct \code{plot.gg_roc(x)} call on a raw
#' multi-class forest do not draw the same figure: \code{gg_roc(x)} returns a
#' single curve, while \code{\link{plot.gg_roc}} given a raw forest overlays
#' one curve per class. Plain \code{plot(x)} on a forest reaches neither, as
#' it dispatches to the forest's own method in \code{randomForestSRC} or
#' \code{randomForest}. Pass \code{which_outcome} explicitly when the
#' distinction matters; issue #72 tracks reconciling the two.
#'
#' @seealso \code{\link{plot.gg_roc}}, \code{\link{calc_roc}},
#' \code{\link{calc_auc}},
#' \code{\link[randomForestSRC]{rfsrc}},
Expand Down
6 changes: 2 additions & 4 deletions R/gg_vimp.R
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,8 @@
#' plot(gg_dta)
#'
#' ## -------- pbc data
#' # We need to create this dataset
#' data(pbc, package = "randomForestSRC", )
#' # For whatever reason, the age variable is in days...
#' # makes no sense to me
#' data(pbc, package = "randomForestSRC")
#' # Recode: binary columns to logical, low-cardinality columns to factors.
#' for (ind in seq_len(dim(pbc)[2])) {
#' if (!is.factor(pbc[, ind])) {
#' if (length(unique(pbc[which(!is.na(pbc[, ind])), ind])) <= 2) {
Expand Down
6 changes: 2 additions & 4 deletions R/plot.gg_error.R
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,8 @@
#' plot(gg_dta)
#'
#' ## ------------- pbc data
#' # Load a cached randomForestSRC object
#' # We need to create this dataset
#' data(pbc, package = "randomForestSRC",)
#' # For whatever reason, the age variable is in days... makes no sense to me
#' data(pbc, package = "randomForestSRC")
#' # Recode: binary columns to logical, low-cardinality columns to factors.
#' for (ind in seq_len(dim(pbc)[2])) {
#' if (!is.factor(pbc[, ind])) {
#' if (length(unique(pbc[which(!is.na(pbc[, ind])), ind])) <= 2) {
Expand Down
2 changes: 1 addition & 1 deletion R/plot.gg_rfsrc.R
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
#' ## -------- pbc data (larger dataset -- skipped on CRAN)
#' \donttest{
#' data(pbc, package = "randomForestSRC")
#' # For whatever reason, the age variable is in days; convert to years
#' # Recode: binary columns to logical, low-cardinality columns to factors.
#' for (ind in seq_len(dim(pbc)[2])) {
#' if (!is.factor(pbc[, ind])) {
#' if (length(unique(pbc[which(!is.na(pbc[, ind])), ind])) <= 2) {
Expand Down
19 changes: 17 additions & 2 deletions R/plot.gg_roc.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,23 @@
#'
#' @param x A \code{\link{gg_roc}} object, or a raw
#' \code{\link[randomForestSRC]{rfsrc}} or
#' \code{\link[randomForest]{randomForest}} classification forest. Hand it a
#' forest and \code{\link{gg_roc}} is called for you.
#' \code{\link[randomForest]{randomForest}} classification forest.
#'
#' A raw forest is accepted, but plain \code{plot(forest)} does not arrive
#' here. Both \code{randomForestSRC} and \code{randomForest} register their
#' own \code{plot} methods, so S3 dispatch sends a raw forest to
#' \code{\link[randomForestSRC]{plot.rfsrc}} or \code{plot.randomForest}
#' instead. This branch is reached only by naming the method outright, as
#' \code{plot.gg_roc(forest)}.
#'
#' That branch also does not use \code{gg_roc}'s own default for
#' \code{which_outcome}: given a multi-class forest and
#' \code{which_outcome = NULL} it calls \code{\link{gg_roc}} once per class
#' and overlays the one-vs-rest curves, where \code{gg_roc(x)} alone returns
#' a single curve -- a macro-average for \code{randomForest}, or class 1
#' with a warning for \code{rfsrc}. Prefer
#' \code{plot(gg_roc(x, which_outcome))}, which is explicit about both the
#' class and the engine. Issue #72 tracks reconciling the entry points.
#' @param which_outcome Integer; for multi-class problems, the index of the
#' class to plot. When \code{NULL} (default) and the forest has more than two
#' classes, the curves for all classes are overlaid in one plot. For binary
Expand Down
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,69 @@ vignette("uvarpro", package = "ggRandomForests")

## Function reference

Grouped by what you are trying to look at. The first column is the function you
call, the second is what you hand it.

### The forest itself

| Function | Input | What you get |
|---|---|---|
| `gg_error()` | `rfsrc` / `randomForest` | OOB error vs. number of trees |
| `gg_vimp()` | `rfsrc` / `randomForest` | Variable importance ranking |
| `gg_rfsrc()` | `rfsrc` / `randomForest` | Predicted vs. observed values |
| `gg_variable()` | `rfsrc` / `randomForest` | Marginal dependence data frame |

### Partial dependence

| Function | Input | What you get |
|---|---|---|
| `gg_partial()` | `plot.variable` output | Partial dependence (continuous + categorical) |
| `gg_partial_rfsrc()` | `rfsrc` model | Partial dependence via `partial.rfsrc` |
| `gg_survival()` | `rfsrc` survival forest | Kaplan–Meier / Nelson–Aalen estimates |
| `gg_roc()` | `rfsrc` / `randomForest` (class) | ROC curve data |
| `surv_partial.rfsrc()` | `rfsrc` survival forest | Survival partial dependence, one or more predictors |
| `quantile_pts()` | numeric vector | Quantile cut points for coplot panels |

### Survival

| Function | Input | What you get |
|---|---|---|
| `gg_survival()` | `rfsrc` survival forest, or a data frame | Kaplan–Meier / Nelson–Aalen estimates |
| `gg_brier()` | `rfsrc` (survival) | Time-resolved Brier score and CRPS |
| `kaplan()` | data frame + interval/censor columns | Nonparametric Kaplan–Meier estimate |
| `nelson()` | data frame + interval/censor columns | Nonparametric Nelson–Aalen estimate |

### Classification and ROC

| Function | Input | What you get |
|---|---|---|
| `gg_roc()` | `rfsrc` / `randomForest` (class) | ROC curve data |
| `calc_roc()` | `rfsrc` / `randomForest` (class) | The sensitivity/specificity sweep behind `gg_roc()` |
| `calc_auc()` | `gg_roc` object | Area under the curve |

### varPro — variable priority

These read a `varPro` fit rather than a forest. `varpro()` is the supervised
fit; `uvarpro()` is the unsupervised one, which needs no outcome.

| Function | Input | What you get |
|---|---|---|
| `gg_varpro()` | `varpro` fit | Release-rule variable importance |
| `gg_beta_varpro()` | `varpro` fit | Per-variable lasso-beta importance |
| `gg_ivarpro()` | `varpro` fit | Individual (local) variable importance |
| `gg_partial_varpro()` | `varpro` fit | Partial dependence (alias: `gg_partialpro()`) |
| `gg_isopro()` | `isopro` fit | Isolation-forest anomaly scores |
| `gg_udependent()` | `uvarpro` fit | Variable dependency graph |
| `gg_beta_uvarpro()` | `uvarpro` fit | Per-variable lasso-beta importance |
| `gg_sdependent()` | `uvarpro` fit | Signal-variable detection |
| `varpro_feature_names()` | character vector | Original names behind one-hot encoded features |

### SHAP

| Function | Input | What you get |
|---|---|---|
| `gg_shap()` | `rfsrc` / `randomForest` | Shapley additive explanation values |
| `shap_importance()` | `gg_shap` object | Global importance bar chart |
| `shap_beeswarm()` | `gg_shap` object | Beeswarm summary plot |
| `shap_dependence()` | `gg_shap` object | Dependence plot for one predictor |

Each `gg_*` function has a matching `plot()` S3 method that hands back a single plottable object: a `ggplot`
you extend with `+`, or a `patchwork` composite for the multi-panel methods. Every `gg_*` object also has `print()` and `summary()` methods: `print()`
Expand All @@ -118,6 +170,8 @@ entirely and build the figure from the tidy data yourself.

See [NEWS.md](NEWS.md) for the full changelog. Recent highlights:

- **v3.5.1** `gg_roc()` on an `rfsrc` forest now honors the documented `which_outcome = 0`, which had been returning an unusable two-row object; `gg_partial_rfsrc()` rejects a non-forest with a real error instead of "argument is of length zero". Also a test-only fix for the `gcc-UBSAN` report filed against 3.5.0.
- **v3.5.0** varPro fixes: `plot.gg_varpro()` no longer draws a phantom "NA" category, `gg_partial_varpro()` warns when you name a variable the fit cannot reach, and `scale = "chf"` now honors `xvar.names` instead of computing every variable. Vignette figures render with `ragg`, which cut the source tarball from 4.7 MB to 2.3 MB.
- **v3.4.0** Unsupervised varPro wrappers (`gg_beta_uvarpro()`, `gg_sdependent()`) with their own vignette; `gg_partial_rfsrc()` now handles factor predictors correctly.
- **v3.3.0** varPro partial plots default to interpretable scales — probability for classification, survival S(&tau;) for survival.
- **v3.1.0** varPro integration: release-rule importance, partial dependence, local importance, anomaly scores, and the dependency graph.
Expand Down
6 changes: 4 additions & 2 deletions man/calc_roc.rfsrc.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions man/gg_error.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 16 additions & 4 deletions man/gg_roc.rfsrc.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions man/gg_vimp.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions man/plot.gg_error.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion man/plot.gg_rfsrc.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading