-
Notifications
You must be signed in to change notification settings - Fork 320
feat: implement automated reporting and visualization layer #4040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
dlebauer
merged 31 commits into
PecanProject:develop
from
ayushman1210:automated-reporting-and-visualization
Aug 14, 2026
Merged
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
b6d18a1
feat: implement automated reporting and visualization layer
ayushman1210 0a3b5e4
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 5e01b22
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 1f2609f
Fix: Serialize benchmark_results to RDS to prevent Quarto YAML parsin…
ayushman1210 4da8557
Fix CI: Add quarto dependency, update tests, and sync Roxygen docs
ayushman1210 8efa210
Feat: Add uncertainty ribbon and error bars to timeseries plot
ayushman1210 9635b36
chore: remove local scratch script from tracking
ayushman1210 4113e58
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 c92ff29
Update modules/benchmark/tests/testthat/test-visualization.R
ayushman1210 958f37d
Refine validation toolkit based on maintainer feedback
ayushman1210 1ee57d5
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 3278216
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 99e6e19
fix(benchmark): update docs, dependencies, and unit tests for timeser…
ayushman1210 776c019
refactor(benchmark): delegate plot coverage to metric_Coverage, reuse…
ayushman1210 92604f8
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 6788ce4
Merge branch 'develop' into automated-reporting-and-visualization
dlebauer 7990ebc
adressed review comments on metric coverage.PMU plot label and docs
ayushman1210 b53fd2c
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 87dc809
document unit arg in timeseries plot
divine7022 8e6df46
document unit arg in residual plot
divine7022 a578ba4
document unit arg in scatter plot
divine7022 f2d6f0c
regenerate timeseries plot man page
divine7022 ca72148
regenerate residual plot man page
divine7022 12563ab
regenerate scatter plot man page
divine7022 6f43127
regenerate compute_metrics man page
divine7022 88fee32
use .data pronoun for label in timeseries plot annotation
divine7022 b77afa9
use .data pronoun for label in residual plot annotation
divine7022 73319c7
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 3097520
Merge branch 'develop' into automated-reporting-and-visualization
dlebauer eeec791
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 6db9bd6
Merge branch 'develop' into automated-reporting-and-visualization
ayushman1210 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| ##' Generate Validation Benchmark Report | ||
| ##' | ||
| ##' @param benchmark_results A list containing `metrics` (data.frame), `aligned_data` (data.frame), and `plots` (list of ggplot objects) returned by the validation pipeline. | ||
| ##' @param output_file The path where the compiled report should be saved (e.g., "validation_report.html"). | ||
| ##' @param template The path to the Quarto template. Defaults to the one provided in the package `inst/reports/Validation_report.qmd`. | ||
| ##' | ||
| ##' @author PEcAn Project | ||
| ##' @export | ||
| generate_validation_report <- function(benchmark_results, output_file = "Validation_report.html", template = NULL) { | ||
| PEcAn.logger::logger.info("Generating Validation Benchmark Report...") | ||
|
|
||
| if (is.null(template)) { | ||
| template <- system.file("reports", "Validation_report.qmd", package = "PEcAn.benchmark") | ||
| if (template == "") { | ||
| # Fallback for development mode | ||
| template <- file.path(getwd(), "inst", "reports", "Validation_report.qmd") | ||
| } | ||
| } | ||
|
|
||
| if (!file.exists(template)) { | ||
| PEcAn.logger::logger.severe("Template file not found:", template) | ||
| stop("Quarto template not found.") | ||
| } | ||
|
|
||
| if (!requireNamespace("quarto", quietly = TRUE)) { | ||
| PEcAn.logger::logger.severe("The 'quarto' package is required to generate the report.") | ||
| stop("Please install the 'quarto' R package.") | ||
|
ayushman1210 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| # Ensure absolute paths | ||
| output_file <- normalizePath(output_file, mustWork = FALSE) | ||
| output_dir <- dirname(output_file) | ||
|
|
||
| if (!dir.exists(output_dir)) { | ||
| dir.create(output_dir, recursive = TRUE) | ||
| } | ||
|
|
||
| # Copy template to output directory to avoid permission issues in system folders | ||
| temp_qmd <- file.path(output_dir, basename(template)) | ||
| file.copy(template, temp_qmd, overwrite = TRUE) | ||
|
|
||
| # Quarto execute_params are converted to YAML. Complex R objects like ggplots | ||
| # cannot be passed via YAML. We must save them to an RDS and pass the path. | ||
| results_rds <- file.path(output_dir, "benchmark_results.rds") | ||
| saveRDS(benchmark_results, results_rds) | ||
|
|
||
| # Render the document | ||
| tryCatch({ | ||
| quarto::quarto_render( | ||
| input = temp_qmd, | ||
| output_file = basename(output_file), | ||
| execute_params = list(benchmark_results = results_rds) | ||
| ) | ||
|
|
||
| PEcAn.logger::logger.info("Validation report successfully generated at:", output_file) | ||
| }, error = function(e) { | ||
| PEcAn.logger::logger.severe("Failed to render validation report:", e$message) | ||
| stop(e) | ||
|
ayushman1210 marked this conversation as resolved.
Outdated
|
||
| }, finally = { | ||
| # Clean up the temporary template file | ||
| if (file.exists(temp_qmd)) { | ||
| file.remove(temp_qmd) | ||
| } | ||
| }) | ||
|
|
||
| return(invisible(output_file)) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,32 @@ | ||
| ##' Scatter Plot | ||
| ##' | ||
| ##' @param metric_dat dataframe to plot, with at least columns `model` and `obvs` | ||
| ##' @param var ignored | ||
| ##' @param var title for the plot | ||
| ##' @param filename path to save plot, or NA to not save | ||
| ##' @param draw.plot logical: Return the plot object? | ||
| ##' | ||
| ##' @author Betsy Cowdery | ||
| ##' @export | ||
|
|
||
| metric_scatter_plot <- function(metric_dat, var, filename = NA, draw.plot = is.na(filename)) { | ||
| PEcAn.logger::logger.info("Metric: Scatter Plot") | ||
|
|
||
| p <- ggplot2::ggplot(data = metric_dat) | ||
| p <- p + ggplot2::geom_point(ggplot2::aes(x = .data$model, y = .data$obvs), size = 4) | ||
| p <- p + ggplot2::geom_abline(slope = 1, intercept = 0, colour = "#666666", | ||
| size = 2, linetype = 2) | ||
| metric_dat <- as.data.frame(metric_dat) | ||
|
|
||
| p <- ggplot2::ggplot(data = metric_dat, ggplot2::aes(x = .data$model, y = .data$obvs)) + | ||
| ggplot2::geom_point(size = 2, alpha = 0.7, colour = "#619CFF") + | ||
| ggplot2::geom_abline(slope = 1, intercept = 0, colour = "#666666", | ||
| linewidth = 1, linetype = 2) + | ||
| ggplot2::labs(title = var, x = "Modeled", y = "Observed") + | ||
| ggplot2::theme_minimal(base_size = 14) | ||
|
|
||
| if (!is.na(filename)) { | ||
| grDevices::pdf(filename, width = 10, height = 6) | ||
| plot(p) | ||
| print(p) | ||
| grDevices::dev.off() | ||
| } | ||
|
|
||
| if (draw.plot) { | ||
| return(p) | ||
| } | ||
|
|
||
| invisible(p) | ||
| } # metric_scatter_plot |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| --- | ||
| title: "PEcAn Validation Benchmark Report" | ||
| author: "PEcAn Validation Toolkit" | ||
| format: | ||
| html: | ||
| theme: cosmo | ||
| toc: true | ||
| toc-depth: 3 | ||
| embed-resources: true | ||
| params: | ||
| benchmark_results: null | ||
| --- | ||
|
|
||
| ```{r setup, include=FALSE} | ||
| knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE) | ||
| library(ggplot2) | ||
| ``` | ||
|
|
||
| ## Executive Summary | ||
|
|
||
| The following scorecard summarizes the validation metrics computed during the benchmark run. | ||
|
|
||
| ```{r scorecard} | ||
| # Quarto execute_params passes strings. If it's a file path, load the RDS. | ||
| if (is.character(params$benchmark_results) && file.exists(params$benchmark_results)) { | ||
| results <- readRDS(params$benchmark_results) | ||
| } else { | ||
| results <- params$benchmark_results | ||
| } | ||
|
|
||
| if (!is.null(results) && !is.null(results$metrics)) { | ||
| # Check if knitr is available to use kable | ||
| if (requireNamespace("knitr", quietly = TRUE)) { | ||
| knitr::kable(results$metrics, digits = 3) | ||
| } else { | ||
| print(results$metrics) | ||
| } | ||
| } else { | ||
| cat("No metrics data provided.\n") | ||
| } | ||
| ``` | ||
|
|
||
| ## Visual Diagnostics | ||
|
|
||
| ```{r plots, results='asis', fig.width=10, fig.height=6} | ||
| if (!is.null(results) && !is.null(results$plots)) { | ||
|
ayushman1210 marked this conversation as resolved.
|
||
| plots <- results$plots | ||
|
|
||
| # if plots is a flat list of ggplot objects | ||
| if (inherits(plots[[1]], "ggplot")) { | ||
| for (p_name in names(plots)) { | ||
| cat(sprintf("\n### %s\n\n", p_name)) | ||
| print(plots[[p_name]]) | ||
| cat("\n\n") | ||
| } | ||
| } else { | ||
| # If plots are nested by variable: plots[[var_name]]$timeseries | ||
| for (var in names(plots)) { | ||
| cat(sprintf("\n### Variable: %s\n\n", var)) | ||
|
|
||
| var_plots <- plots[[var]] | ||
|
|
||
| if (!is.null(var_plots$timeseries)) { | ||
| print(var_plots$timeseries) | ||
| cat("\n\n") | ||
| } | ||
| if (!is.null(var_plots$scatter)) { | ||
| print(var_plots$scatter) | ||
| cat("\n\n") | ||
| } | ||
| if (!is.null(var_plots$residual)) { | ||
| print(var_plots$residual) | ||
| cat("\n\n") | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| cat("No plots provided.\n") | ||
| } | ||
| ``` | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.