Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8873932
docs: v3.1.0 documentation sweep + gg_vimp fix (CRAN release) (#109)
ehrlinger Jun 10, 2026
db71424
fix(vignettes): static PD surfaces + 96-dpi figures to cut install si…
ehrlinger Jun 10, 2026
18156a8
docs(examples): \donttest the slow plot.gg_variable example sections …
ehrlinger Jun 10, 2026
a7d8052
Cut CRAN overall check time below 10 min (#114)
ehrlinger Jun 11, 2026
0802143
chore: open 3.1.0.9000 dev cycle after the CRAN release (#115)
ehrlinger Jun 11, 2026
168bd7c
fix(cran): v3.1.1 — clear gcc-UBSAN additional issue (varPro test ski…
ehrlinger Jun 12, 2026
83d8021
fix(cran): v3.1.2 — skip only isopro(method="unsupv"), the sole gcc-U…
ehrlinger Jun 13, 2026
fdec398
chore(cran): record 3.1.2 submission (#123)
ehrlinger Jun 13, 2026
d93bb62
v3.2.0: RMST(τ) partial computation + #118 gg_varpro guard (#127)
ehrlinger Jun 23, 2026
dde7dc5
chore(cran): record v3.2.0 submission (SHA 9892d5ab)
ehrlinger Jun 23, 2026
253155d
v3.3.0: interpretable y-axis scales for varPro partial plots (prob/su…
ehrlinger Jun 24, 2026
adbfacb
v3.4.0: port uvarpro figures (gg_beta_uvarpro + gg_sdependent) to the…
ehrlinger Jun 24, 2026
9169471
docs: voice-polish pass on 3.3.0/3.4.0 prose (targets 3.4.0) (#131)
ehrlinger Jun 24, 2026
eb6f324
docs: short uvarpro vignette + varPro trim + fix stale VignetteIndexE…
ehrlinger Jul 1, 2026
18e5493
fix(gg_partial_rfsrc): impose factor levels by integer code, not labe…
ehrlinger Jul 1, 2026
76841b7
release: prep v3.4.0 for CRAN (cran-comments, Date, README docs sweep…
ehrlinger Jul 1, 2026
fc8544f
fix: .Rbuildignore excludes stray Rplots.pdf (top-level NOTE) (#135)
ehrlinger Jul 2, 2026
95900bd
fix: add default S3 methods to varPro-family wrappers (gg_isopro, gg_…
ehrlinger Jul 2, 2026
0f29d25
fix: ignore local CLAUDE.md (build + git) (#137)
ehrlinger Jul 2, 2026
89a10d7
docs(cran-comments): reflect actual test environments (mac-builder do…
ehrlinger Jul 2, 2026
2daf863
fix: default S3 methods for the classic rfsrc/randomForest wrappers (…
ehrlinger Jul 2, 2026
064a2f7
Merge main (v3.4.0/3.4.1) into dev_rhf
ehrlinger Jul 2, 2026
fabcef3
perf: hoist unique() and single bind_rows in .process_cat_var
ehrlinger Jul 2, 2026
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
5 changes: 3 additions & 2 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ framed.sty
^CRAN-SUBMISSION$
^\.github$
^\.claude$
^CLAUDE\.md$
^\.git$
^\.vscode$
^doc$
Expand All @@ -54,6 +55,6 @@ framed.sty
^vignettes/\.quarto$
^vignettes/.*_files$
^vignettes/.*\.html$
# Stray default-device plot dumps from running examples/tests locally
# (already in .gitignore; this keeps them out of the build tarball too)
# Stray default-device plot artefact (created by plotting with no open device),
# at the root or under tests/ — never ship it.
Rplots\.pdf$
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,20 @@ vignettes/ggRandomForests.html
vignettes/varpro_cache/
vignettes/varpro_files/
vignettes/varpro.html
vignettes/uvarpro_cache/
vignettes/uvarpro_files/
vignettes/uvarpro.html

.claude
CLAUDE.md
.positai

# Local dev tool state (brainstorm/superpowers)
# Brainstorm / visual-companion scratch
.superpowers/
# Rendered vignette artifacts (current quarto vignette names)
vignettes/ggRandomForests-regression.html
vignettes/ggRandomForests-survival.html
vignettes/ggRandomForests-regression_files/
vignettes/ggRandomForests-survival_files/
# Built package tarballs
*.tar.gz
6 changes: 3 additions & 3 deletions CRAN-SUBMISSION
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Version: 3.2.0
Date: 2026-06-23 16:43:46 UTC
SHA: 9892d5abc8c9278acd19835059bbe78ff887c7bc
Version: 3.4.0
Date: 2026-07-02 16:27:59 UTC
SHA: 16c8b4d791827785b68e78279b89c183b9d35d09
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Package: ggRandomForests
Type: Package
Title: Visually Exploring Random Forests
Version: 4.0.0.9000
Date: 2026-06-23
Date: 2026-07-02
Authors@R: person("John", "Ehrlinger",
role = c("aut", "cre"),
email = "john.ehrlinger@gmail.com")
Expand Down
8 changes: 8 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,19 @@ S3method(calc_roc,rfsrc)
S3method(gg_auct,rhf)
S3method(gg_beta_uvarpro,default)
S3method(gg_beta_uvarpro,uvarpro)
S3method(gg_beta_varpro,default)
S3method(gg_beta_varpro,varpro)
S3method(gg_brier,default)
S3method(gg_brier,rfsrc)
S3method(gg_error,default)
S3method(gg_error,randomForest)
S3method(gg_error,randomForest.formula)
S3method(gg_error,rfsrc)
S3method(gg_isopro,default)
S3method(gg_isopro,isopro)
S3method(gg_ivarpro,default)
S3method(gg_ivarpro,varpro)
S3method(gg_rfsrc,default)
S3method(gg_rfsrc,randomForest)
S3method(gg_rfsrc,rfsrc)
S3method(gg_rhf,rhf)
Expand All @@ -42,8 +48,10 @@ S3method(gg_sdependent,default)
S3method(gg_sdependent,uvarpro)
S3method(gg_survival,default)
S3method(gg_survival,rfsrc)
S3method(gg_variable,default)
S3method(gg_variable,randomForest)
S3method(gg_variable,rfsrc)
S3method(gg_vimp,default)
S3method(gg_vimp,randomForest)
S3method(gg_vimp,rfsrc)
S3method(plot,gg_auct)
Expand Down
68 changes: 66 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,37 @@ ggRandomForests v4.0.0 (development)
AUC(t) with a bootstrap CI ribbon when available and a 0.5 reference
line. `gg_auct.rhf(object, marker, auct_fit = NULL)` computes
`auct.rhf()` internally or reuses a cached fit.

ggRandomForests v3.4.1
======================
* The remaining `rfsrc`/`randomForest` wrappers -- `gg_error()`, `gg_vimp()`,
`gg_variable()`, `gg_rfsrc()`, and `gg_brier()` -- now have `default` S3
methods, so a wrong-class input gives a clear "expected an 'rfsrc' or
'randomForest' object" error (naming the class it got) instead of R's generic
"no applicable method". This finishes the dispatch-consistency pass started
for the varPro family in 3.4.0. (`gg_roc()` keeps its existing
`gg_roc.rfsrc` default, which accepts rfsrc-shaped objects.)

ggRandomForests v3.4.0
======================
* `gg_isopro()`, `gg_beta_varpro()`, and `gg_ivarpro()` now have `default` S3
methods, so a wrong-class input gives a clear "expected a '<class>' object"
error (naming the class it got) instead of R's generic "no applicable
method". This makes the varPro-family wrappers consistent with
`gg_beta_uvarpro()` / `gg_sdependent()`; the previously-unreachable inner
class checks were removed.
* Fix: `gg_partial_rfsrc()` now computes partial dependence correctly for
`factor` predictors. It was passing factor *labels* as
`partial.values` to `randomForestSRC::partial.rfsrc()`, which imposes a
level by its integer code (internally `as.numeric(partial.values)`).
Character labels ("No"/"Yes") became `NA` and numeric-looking labels
("4"/"6"/"8") became out-of-range codes, so every level collapsed to a
single value (a flat categorical partial plot). The wrapper now passes the
integer codes and relabels the output, matching `plot.variable(partial =
TRUE)` and the ground-truth partial dependence. The categorical `x` is now
returned as a `factor` in the model's level order, so the plot keeps that
order instead of re-sorting alphabetically. Continuous and numeric
low-cardinality predictors are unaffected.
* `gg_beta_uvarpro()` / `plot.gg_beta_uvarpro()`: tidy wrapper and bar chart
for `varPro::get.beta.entropy()` -- the unsupervised analogue of
`gg_beta_varpro()`. From a `uvarpro()` fit it aggregates the per-region
Expand All @@ -31,8 +62,41 @@ ggRandomForests v4.0.0 (development)
graph) with the "which variables are signal" ranking; shares the
`beta_fit` entropy matrix. Follows the `get.beta.entropy` + `sdependent`
workflow from the `varPro::uvarpro()` help (iowa-housing example).
* Fixes the intro vignette's placeholder `\VignetteIndexEntry`
("Vignette's Title" -> "Exploring Random Forests with ggRandomForests").
* New `uvarpro` vignette: a short, focused walk-through of the unsupervised
varPro wrappers (`gg_udependent()`, `gg_beta_uvarpro()`, `gg_sdependent()`)
on a single `uvarpro()` fit, using the shared `beta_fit` matrix. The three
unsupervised sections were lifted out of the `varpro` vignette, which now
points to the new one and covers the five supervised wrappers.
* Fixed the main vignette's `\VignetteIndexEntry`, which still carried the
template placeholder "Vignette's Title" -- it now reads "Exploring Random
Forests with ggRandomForests" (the index entry CRAN lists, not the document
title, was the stale one).

ggRandomForests v3.3.0
======================
* `gg_partial_varpro()`: **classification partial plots now default to
probability.** `scale = "auto"` on a classification fit resolves to `"prob"`
(P(Y = target class)) instead of raw log-odds; `"odds"` and `"logodds"` are
options. The back-transform is applied before averaging (mean predicted
probability). The `causal` contrast is shown only on `"logodds"`.
* `gg_partial_varpro()`: **survival partial plots now default to survival
probability.** `scale = "auto"` on a survival fit resolves to `"surv"`
(S(tau | x), bounded 0-1) via a new partialpro learner, instead of the
unbounded ensemble-mortality score (still available via
`scale = "mortality"`). `"surv"` and `"rmst"` default `tau` to the median
follow-up time when `time` is omitted -- a units-safe, data-driven horizon
(v3.2.0's `rmst` required `time`; this is a loosening). The resolved `tau` is
reported in a message and the axis label.
* `plot.gg_partial_varpro()`: documents what the `causal` (virtual-twins)
estimator is and when to use it, and explains why it is hidden on the bounded
probability scales.
* Documentation: `plot.gg_partial_varpro()` gains a "Reading an RMST curve"
section explaining how to interpret the `scale = "rmst"` y-axis -- RMST(tau)
is the expected event-free time within the first tau time-units (area under
S(t) out to tau), read in the model's own time units, bounded by tau, and
higher-is-better (the opposite direction from ensemble mortality). It also
notes that tau must be supplied in the fit's time units, since a tau beyond
the largest event time truncates to the full restricted mean. No code change.

ggRandomForests v3.2.0
======================
Expand Down
4 changes: 0 additions & 4 deletions R/gg_beta_uvarpro.R
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ gg_beta_uvarpro.default <- function(object, ..., cutoff = NULL,
#' @export
gg_beta_uvarpro.uvarpro <- function(object, ..., cutoff = NULL,
beta_fit = NULL) {
if (!inherits(object, "uvarpro")) {
stop("gg_beta_uvarpro: expected a 'uvarpro' object from varPro::uvarpro().",
call. = FALSE)
}
.assert_scalar_numeric_or_null(cutoff, "cutoff", "gg_beta_uvarpro")

# Resolve the beta matrix (cache path)
Expand Down
12 changes: 8 additions & 4 deletions R/gg_beta_varpro.R
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,17 @@ gg_beta_varpro <- function(object, ..., cutoff = NULL, beta_fit = NULL,
UseMethod("gg_beta_varpro", object)
}

#' @export
gg_beta_varpro.default <- function(object, ..., cutoff = NULL,
beta_fit = NULL, which_class = NULL) {
stop("gg_beta_varpro: expected a 'varpro' object from varPro::varpro(); ",
"got an object of class ", paste(class(object), collapse = "/"), ".",
call. = FALSE)
}

#' @export
gg_beta_varpro.varpro <- function(object, ..., cutoff = NULL,
beta_fit = NULL, which_class = NULL) {
if (!inherits(object, "varpro")) {
stop("gg_beta_varpro: expected a 'varpro' object from varPro::varpro().",
call. = FALSE)
}
fam <- object$family
if (!fam %in% c("regr", "class")) {
stop(sprintf(
Expand Down
7 changes: 7 additions & 0 deletions R/gg_brier.R
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ gg_brier <- function(object, ...) {
UseMethod("gg_brier", object)
}

#' @export
gg_brier.default <- function(object, ...) {
stop("gg_brier: expected an 'rfsrc' survival object from ",
"randomForestSRC::rfsrc(); got an object of class ",
paste(class(object), collapse = "/"), ".", call. = FALSE)
}

#' @export
gg_brier.rfsrc <- function(object,
subset = NULL,
Expand Down
7 changes: 7 additions & 0 deletions R/gg_error.R
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@
gg_error <- function(object, ...) {
UseMethod("gg_error", object)
}

#' @export
gg_error.default <- function(object, ...) {
stop("gg_error: expected an 'rfsrc' or 'randomForest' object; ",
"got an object of class ", paste(class(object), collapse = "/"), ".",
call. = FALSE)
}
#' @export
gg_error.rfsrc <- function(object, ...) {
## Check that the input object is of the correct type.
Expand Down
12 changes: 7 additions & 5 deletions R/gg_isopro.R
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,14 @@ gg_isopro <- function(object, ..., newdata = NULL) {
}

#' @export
gg_isopro.isopro <- function(object, ..., newdata = NULL) {
if (!inherits(object, "isopro")) {
stop("gg_isopro expects a 'isopro' object from varPro::isopro().",
call. = FALSE)
}
gg_isopro.default <- function(object, ..., newdata = NULL) {
stop("gg_isopro: expected an 'isopro' object from varPro::isopro(); ",
"got an object of class ", paste(class(object), collapse = "/"), ".",
call. = FALSE)
}

#' @export
gg_isopro.isopro <- function(object, ..., newdata = NULL) {
ntree <- tryCatch(
as.integer(object$isoforest$ntree),
error = function(e) NA_integer_
Expand Down
13 changes: 9 additions & 4 deletions R/gg_ivarpro.R
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,19 @@ gg_ivarpro <- function(object, ..., which_obs = NULL, which_class = NULL,
UseMethod("gg_ivarpro", object)
}

#' @export
gg_ivarpro.default <- function(object, ..., which_obs = NULL,
which_class = NULL, cutoff = NULL,
ivarpro_fit = NULL) {
stop("gg_ivarpro: expected a 'varpro' object from varPro::varpro(); ",
"got an object of class ", paste(class(object), collapse = "/"), ".",
call. = FALSE)
}

#' @export
gg_ivarpro.varpro <- function(object, ..., which_obs = NULL,
which_class = NULL, cutoff = NULL,
ivarpro_fit = NULL) {
if (!inherits(object, "varpro")) {
stop("gg_ivarpro: expected a 'varpro' object from varPro::varpro().",
call. = FALSE)
}
fam <- object$family
if (!fam %in% c("regr", "class")) {
stop(sprintf(
Expand Down
36 changes: 29 additions & 7 deletions R/gg_partial_rfsrc.R
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@
#' (the level of \code{xvar2.name}) and \code{time} (survival forests
#' only) for all continuous predictors.}
#' \item{categorical}{A \code{data.frame} with the same columns but
#' \code{x} kept as character, for low-cardinality predictors.}
#' \code{x} kept as a \code{factor} (levels in the model's level order,
#' not alphabetical), for low-cardinality predictors.}
#' }
#'
#' @seealso \code{\link{gg_partial}}, \code{\link[randomForestSRC]{partial.rfsrc}},
Expand Down Expand Up @@ -195,8 +196,9 @@ snap_partial_time <- function(rf_model, partial.time) {
## Build the evaluation grid (xval vector + categorical flag) for one variable.
make_eval_grid <- function(xname, newx, cat_limit, n_eval) {
# Use `[[` to preserve the column's class (factor, character, numeric, …).
# unlist(dplyr::select(...)) would coerce factors to integer codes, breaking
# both the cat_limit check and the partial.values passed to partial.rfsrc().
# unlist(dplyr::select(...)) would coerce factors to their integer codes,
# losing the level labels we need for the cat_limit check and for mapping
# partial.rfsrc()'s numeric output back to labels (see partial_one_var()).
xval <- newx[[xname]]
xval <- xval[!is.na(xval)]
if (length(xval) == 0L) {
Expand All @@ -207,14 +209,22 @@ make_eval_grid <- function(xname, newx, cat_limit, n_eval) {
return(NULL)
}
gr <- is.factor(xval) || is.character(xval) || length(unique(xval)) < cat_limit
if (!gr && length(unique(xval)) > n_eval) {
flevels <- NULL
if (is.factor(xval)) {
# partial.rfsrc() imposes a factor level by its INTEGER CODE -- internally
# it does as.numeric(partial.values). Passing the labels coerces character
# levels ("No"/"Yes") to NA, or numeric-looking labels ("4"/"6"/"8") to
# out-of-range codes, and every level collapses to a single value. Pass the
# codes here and relabel get.partial.plot.data()'s output in partial_one_var().
flevels <- levels(xval)
present <- levels(droplevels(xval)) # levels actually present in newx
xval <- match(present, flevels) # codes in the model's factor coding
} else if (!gr && length(unique(xval)) > n_eval) {
xval <- quantile_pts(xval, groups = n_eval)
} else if (is.factor(xval)) {
xval <- levels(droplevels(xval)) # preserve factor ordering; drop unused levels
} else {
xval <- sort(unique(xval))
}
list(xval = xval, categorical = gr)
list(xval = xval, categorical = gr, flevels = flevels)
}

## Thin wrapper around partial.rfsrc that builds the argument list.
Expand Down Expand Up @@ -250,6 +260,11 @@ partial_one_var <- function(xname, newx, rf_model,
is_surv, partial.time, partial.type,
xvar2.name, x2val)
pout <- randomForestSRC::get.partial.plot.data(partial.obj, granule = gr)
# Factor levels were passed to partial.rfsrc() as integer codes; map the
# numeric x that comes back to the original level labels.
if (!is.null(eg$flevels)) {
pout$x <- eg$flevels[as.integer(pout$x)]
}
# Survival forests with >1 partial.time return yhat as an
# [length(partial.values) x length(partial.time)] matrix; expand to long form
# so each (x, time) pair is its own row. For non-survival or single-time
Expand Down Expand Up @@ -320,6 +335,13 @@ split_partial_result <- function(pdta) {
continuous$type <- NULL
categorical <- pdta[!cont_idx, , drop = FALSE]
categorical$type <- NULL
# Keep the model's factor-level order. The rows arrive blocked by level in
# ascending code order (i.e. the model's level order), so first-appearance is
# that order; make x a factor so plot.gg_partial_rfsrc()'s factor(x) does not
# re-sort the levels alphabetically.
if (nrow(categorical) > 0L) {
categorical$x <- factor(categorical$x, levels = unique(categorical$x))
}
result <- list(continuous = continuous, categorical = categorical)
class(result) <- "gg_partial_rfsrc"
result
Expand Down
Loading
Loading