Skip to content

Fix empty-figure bugs in gg_partial_rfsrc (release v2.7.1)#70

Merged
ehrlinger merged 2 commits into
mainfrom
fix/empty-figure-rfsrc-data-transforms
Apr 27, 2026
Merged

Fix empty-figure bugs in gg_partial_rfsrc (release v2.7.1)#70
ehrlinger merged 2 commits into
mainfrom
fix/empty-figure-rfsrc-data-transforms

Conversation

@ehrlinger
Copy link
Copy Markdown
Owner

Summary

Bug-fix release. The survival vignette's partial-dependence figures were
rendering empty because gg_partial_rfsrc() called
randomForestSRC::partial.rfsrc() without partial.type for survival
forests, triggering a zero-length comparison
(if (partial.type == \"rel.freq\") ...) inside the C-level prediction
routine and aborting the call. The chunk had `error=TRUE`, so the
failure surfaced as missing figures rather than an error.

A second latent bug surfaced once the first was fixed: with multiple
`partial.time` values, `get.partial.plot.data()` returns yhat as an
`[length(partial.values) × length(partial.time)]` matrix, but the code
assumed a vector and crashed on the `time` column assignment.

Fixes

  • Pass `partial.type = "surv"` by default for survival forests; expose
    it as a new `gg_partial_rfsrc()` argument (`"surv"` / `"chf"` /
    `"mort"`).
  • Reshape multi-`partial.time` results to long form so each
    `(x, time)` pair is one row.
  • Improve `plot.gg_partial_rfsrc()` survival layout: predictor value on
    the x-axis with one curve per (rounded) time point coloured by
    `Time`, faceted by variable name. Previous layout produced a
    saturated legend with dozens of nearly-identical lines.

Empty-figure regression suite

New `tests/testthat/test_plot_layer_data.R` (57 assertions) uses
`ggplot2::layer_data()` to verify each `plot.gg_*()` method renders
non-empty layers across every supported forest family — regression,
classification, survival, with/without `conf.int` / `by` / multi-time
partial-dependence. Catches transform/plot column-name mismatches
structurally without visual inspection.

Release

  • `DESCRIPTION`: `Version: 2.7.1` (semver patch — bug-fix only),
    `Date: 2026-04-27`.
  • `NEWS.md`: new `v2.7.1` heading; `v2.7.0` block preserved below.
  • `cran-comments.md`: rewritten for the v2.7.1 submission.

Test plan

  • `R CMD check --no-manual` passes locally with vignette
    re-building enabled (Status: OK; 0 errors / 0 warnings / 0 notes).
  • `testthat::test_dir("tests/testthat")` passes (0 failures).
  • All three vignettes render with zero
    `cell-output cell-output-error` divs in the resulting HTML.
  • `partial-dep-1.png` in the survival vignette now shows the
    expected per-variable curves at the requested time horizons.

🤖 Generated with Claude Code

Survival forests in gg_partial_rfsrc() were calling partial.rfsrc() without
partial.type, triggering a zero-length comparison inside randomForestSRC's
C-level prediction code that aborted the call -- which is why the survival
vignette's partial-dependence chunks rendered no figures.

* Pass partial.type = "surv" by default for survival forests; expose it
  as a new gg_partial_rfsrc() argument accepting "surv" / "chf" / "mort".
* Reshape multi-partial.time results to long form: get.partial.plot.data()
  returns yhat as a [length(partial.values) x length(partial.time)]
  matrix, but the previous code assumed a vector and crashed on the
  time-column assignment.
* Improve plot.gg_partial_rfsrc() survival layout: predictor on x-axis,
  one curve per (rounded) time point coloured by Time, faceted by name.
  The previous default put time on the x-axis with one near-identical
  line per predictor value.
* New tests/testthat/test_plot_layer_data.R uses ggplot2::layer_data() to
  verify each plot.gg_*() method renders non-empty layers across all
  forest families (regression / classification / survival, with/without
  conf.int, by, multi-time partial-dep). Catches empty-figure regressions
  without visual inspection.

Bump DESCRIPTION to 2.7.1 (semver patch -- bug-fix only) and refresh
cran-comments.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

❌ Patch coverage is 92.50000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.30%. Comparing base (5468748) to head (3216c92).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
R/plot.gg_partial.R 81.25% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #70      +/-   ##
==========================================
+ Coverage   83.59%   84.30%   +0.71%     
==========================================
  Files          25       25              
  Lines        2005     2032      +27     
==========================================
+ Hits         1676     1713      +37     
+ Misses        329      319      -10     
Files with missing lines Coverage Δ
R/gg_partial_rfsrc.R 88.80% <100.00%> (+2.56%) ⬆️
R/plot.gg_partial.R 78.67% <81.25%> (+9.07%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ehrlinger ehrlinger requested a review from Copilot April 27, 2026 17:38
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Bug-fix patch release addressing an “empty figure” failure mode in survival partial-dependence plots by ensuring randomForestSRC::partial.rfsrc() is called with the required partial.type, and by correctly reshaping multi-time partial dependence output for plotting.

Changes:

  • Add partial.type argument to gg_partial_rfsrc() (defaulting to "surv" for survival forests) and pass it through to randomForestSRC::partial.rfsrc().
  • Reshape multi-partial.time survival partial-dependence yhat matrix output into long form.
  • Add a regression test suite using ggplot2::layer_data() to ensure plot.gg_*() methods produce non-empty rendered layers across forest families.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
R/gg_partial_rfsrc.R Adds partial.type handling for survival forests and reshapes multi-time partial output into long form.
R/plot.gg_partial.R Updates survival partial-dependence plot layout to use predictor on x-axis and time as the curve grouping/color.
tests/testthat/test_plot_layer_data.R Adds regression tests that validate plots render non-empty layer data (prevents “empty figure” regressions).
man/gg_partial_rfsrc.Rd Documents new partial.type argument.
DESCRIPTION Bumps package version/date for v2.7.1.
NEWS.md Adds v2.7.1 release notes describing the fixes and new tests.
cran-comments.md Updates CRAN submission notes for v2.7.1.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread R/plot.gg_partial.R Outdated
Comment on lines +126 to +128
ggplot2::geom_line() +
ggplot2::facet_wrap(~name, scales = "free") +
ggplot2::labs(x = "Time", y = "Partial Effect", color = "Predictor value")
ggplot2::facet_wrap(~name, scales = "free_x") +
ggplot2::labs(x = NULL, y = "Predicted Survival", color = "Time")
Comment thread R/plot.gg_partial.R Outdated
Comment on lines +111 to +113
if (!is.null(cont$time)) {
## Survival forest: predictor value is the grouping variable; x-axis is time
## Survival forest: predictor value on x-axis, one curve per time point
## (rounded for a tidy legend). Time is typically a small set (1-3 horizons)
Comment thread R/plot.gg_partial.R Outdated
Comment on lines 115 to 124
cont$.time_lbl <- factor(round(cont$time, 2),
levels = sort(unique(round(cont$time, 2))))
gg_cont <- ggplot2::ggplot(
cont,
ggplot2::aes(
x = .data$time,
x = .data$x,
y = .data$yhat,
color = factor(.data$x),
group = factor(.data$x)
color = .data$.time_lbl,
group = .data$.time_lbl
)
* plot.gg_partial_rfsrc(): y-axis label now adapts to partial.type. The
  attribute is stamped on the gg_partial_rfsrc object and read back via a
  new partial_surv_y_label() helper that maps "surv" -> "Predicted
  Survival", "chf" -> "Predicted CHF", "mort" -> "Predicted Mortality"
  (with a "Predicted Survival" fallback for legacy objects without the
  attribute).
* plot.gg_partial_rfsrc(): group/colour by full-precision time (not the
  rounded label), so distinct time horizons that round to the same value
  no longer collapse into a single curve. Rounding is applied only to
  legend labels via scale_color_discrete(labels = ...).
* plot.gg_partial_rfsrc() roxygen: revised section header to match the
  new layout (one curve per evaluation time over the predictor's value;
  y-axis adapts to partial.type).
* tests: lock in the y-label dispatch for "surv" / "chf" / "mort" and the
  full-precision time grouping (synthetic two-time object with values
  that round to the same 2-dp display).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ehrlinger
Copy link
Copy Markdown
Owner Author

Addressed all three Copilot review items in 3216c92:

  1. Hard-coded "Predicted Survival" y-label (R/plot.gg_partial.R:128) — partial.type now propagates from gg_partial_rfsrc() to the plot via an attribute on the returned object. New helper partial_surv_y_label() maps:

    • "surv" → "Predicted Survival"
    • "chf" → "Predicted CHF"
    • "mort" → "Predicted Mortality"
      (Falls back to "Predicted Survival" if the attribute is absent — covers legacy objects.)
  2. Stale roxygen (R/plot.gg_partial.R:113) — updated the survival-section description to match the new layout (one curve per evaluation time over the predictor's value, y-axis adapts to partial.type).

  3. Rounded .time_lbl collapsing distinct horizons (R/plot.gg_partial.R:124) — group/colour by the full-precision time (factor(time, levels = sort(unique(time)))) so distinct horizons that round to the same display value never merge. Rounding is applied only to legend labels via scale_color_discrete(labels = ...).

Locked in by two new tests in tests/testthat/test_plot_layer_data.R:

  • y-label dispatch for "surv" / "chf" / "mort".
  • Synthetic two-time object with time = c(1.001, 1.002) (both round to "1") to prove the curves stay separated.

R CMD check --no-manual is Status: OK.

@ehrlinger ehrlinger merged commit b79f6fa into main Apr 27, 2026
15 checks passed
@ehrlinger ehrlinger deleted the fix/empty-figure-rfsrc-data-transforms branch April 27, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants