From 950cd890956a7c04cc274f3cc9b231072ce4c70d Mon Sep 17 00:00:00 2001 From: Utkarsh Date: Tue, 24 Mar 2026 02:17:09 +0530 Subject: [PATCH 1/3] Fix .kde_correction() crash on empty vector after removing infinite PIT values --- NEWS.md | 1 + R/ppc-loo.R | 8 ++++++++ tests/testthat/test-ppc-loo.R | 16 ++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/NEWS.md b/NEWS.md index 7ce82a4b..eae94566 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # bayesplot (development version) +* Fix `.kde_correction()` crash on empty vector after removing infinite PIT values by adding guards for insufficient finite values and all-NA convolution results. * Validate equal chain lengths in `validate_df_with_chain()`, reject missing chain labels, and renumber data-frame chain labels internally when converting to arrays. diff --git a/R/ppc-loo.R b/R/ppc-loo.R index 2f91c5b5..d515d6bb 100644 --- a/R/ppc-loo.R +++ b/R/ppc-loo.R @@ -803,6 +803,10 @@ ppc_loo_ribbon <- x <- x[is.finite(x)] } + if (length(x) < 2) { + abort("Not enough finite PIT values for KDE boundary correction.") + } + if (grid_len < 100) { grid_len <- 100 } @@ -824,6 +828,10 @@ ppc_loo_ribbon <- xs <- (grid_breaks[2:n_breaks] + grid_breaks[1:(n_breaks - 1)]) / 2 + if (all(is.na(bc_pvals))) { + abort("KDE boundary correction produced all NA values.") + } + first_nonNA <- utils::head(which(!is.na(bc_pvals)), 1) last_nonNA <- utils::tail(which(!is.na(bc_pvals)), 1) bc_pvals[1:first_nonNA] <- bc_pvals[first_nonNA] diff --git a/tests/testthat/test-ppc-loo.R b/tests/testthat/test-ppc-loo.R index 83cdaf50..a678a7a9 100644 --- a/tests/testthat/test-ppc-loo.R +++ b/tests/testthat/test-ppc-loo.R @@ -72,6 +72,22 @@ test_that(".kde_correction warns when PIT values are non-finite", { expect_equal(length(out$bc_pvals), 128) }) +test_that(".kde_correction errors when all PIT values are infinite", { + pit_vals <- c(Inf, -Inf, Inf) + expect_error( + .kde_correction(pit_vals, bw = "nrd0", grid_len = 128), + "Not enough finite PIT values" + ) +}) + +test_that(".kde_correction errors when only one finite PIT value remains", { + pit_vals <- c(Inf, 0.5, -Inf) + expect_error( + .kde_correction(pit_vals, bw = "nrd0", grid_len = 128), + "Not enough finite PIT values" + ) +}) + test_that("ppc_loo_pit_qq returns ggplot object", { skip_if_not_installed("rstanarm") skip_if_not_installed("loo") From 33e0a3e788d94b71d2d3558560da84d2be1dfbe0 Mon Sep 17 00:00:00 2001 From: Utkarsh Date: Wed, 25 Mar 2026 18:45:03 +0530 Subject: [PATCH 2/3] Move PIT validation to ppc_loo_pit_data() entry point using validate_pit() --- NEWS.md | 2 +- R/ppc-loo.R | 14 +------------- tests/testthat/test-ppc-loo.R | 34 ++++++++++++---------------------- 3 files changed, 14 insertions(+), 36 deletions(-) diff --git a/NEWS.md b/NEWS.md index 43828fe1..d0458669 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # bayesplot (development version) -* Fix `.kde_correction()` crash on empty vector after removing infinite PIT values by adding guards for insufficient finite values and all-NA convolution results. +* Validate user-provided `pit` values in `ppc_loo_pit_data()` using `validate_pit()` to reject invalid inputs (non-numeric, out of range, NAs) at the entry point instead of in internal helpers. * Eliminate redundant data processing in `mcmc_areas_data()` by reusing the prepared MCMC array for both interval and density computation. * Validate equal chain lengths in `validate_df_with_chain()`, reject missing chain labels, and renumber data-frame chain labels internally when converting diff --git a/R/ppc-loo.R b/R/ppc-loo.R index d515d6bb..7f910794 100644 --- a/R/ppc-loo.R +++ b/R/ppc-loo.R @@ -302,7 +302,7 @@ ppc_loo_pit_data <- boundary_correction = TRUE, grid_len = 512) { if (!is.null(pit)) { - stopifnot(is.numeric(pit), is_vector_or_1Darray(pit)) + pit <- validate_pit(pit) inform("'pit' specified so ignoring 'y','yrep','lw' if specified.") } else { suggested_package("rstantools") @@ -795,18 +795,6 @@ ppc_loo_ribbon <- # Generate boundary corrected values via a linear convolution using a # 1-D Gaussian window filter. This method uses the "reflection method" # to estimate these pvalues and helps speed up the code - if (any(is.infinite(x))) { - warn(paste( - "Ignored", sum(is.infinite(x)), - "Non-finite PIT values are invalid for KDE boundary correction method" - )) - x <- x[is.finite(x)] - } - - if (length(x) < 2) { - abort("Not enough finite PIT values for KDE boundary correction.") - } - if (grid_len < 100) { grid_len <- 100 } diff --git a/tests/testthat/test-ppc-loo.R b/tests/testthat/test-ppc-loo.R index a678a7a9..d910a8c8 100644 --- a/tests/testthat/test-ppc-loo.R +++ b/tests/testthat/test-ppc-loo.R @@ -59,32 +59,22 @@ test_that("ppc_loo_pit_overlay works with boundary_correction=FALSE", { expect_gg(p1) }) -test_that(".kde_correction warns when PIT values are non-finite", { - set.seed(123) - pit_vals <- c(stats::runif(500), Inf) - expect_warning( - out <- .kde_correction(pit_vals, bw = "nrd0", grid_len = 128), - "Non-finite PIT values are invalid" +test_that("ppc_loo_pit_data validates user-provided pit values", { + expect_error( + ppc_loo_pit_data(pit = c(0.5, Inf)), + "between 0 and 1" ) - expect_type(out, "list") - expect_true(all(c("xs", "bc_pvals") %in% names(out))) - expect_equal(length(out$xs), 128) - expect_equal(length(out$bc_pvals), 128) -}) - -test_that(".kde_correction errors when all PIT values are infinite", { - pit_vals <- c(Inf, -Inf, Inf) expect_error( - .kde_correction(pit_vals, bw = "nrd0", grid_len = 128), - "Not enough finite PIT values" + ppc_loo_pit_data(pit = c(-1, 0.5)), + "between 0 and 1" + ) + expect_error( + ppc_loo_pit_data(pit = c(0.5, NA)), + "NAs not allowed" ) -}) - -test_that(".kde_correction errors when only one finite PIT value remains", { - pit_vals <- c(Inf, 0.5, -Inf) expect_error( - .kde_correction(pit_vals, bw = "nrd0", grid_len = 128), - "Not enough finite PIT values" + ppc_loo_pit_data(pit = "not numeric"), + "is.numeric" ) }) From cf8e11d0632863e439249e4d14048072ec3cead8 Mon Sep 17 00:00:00 2001 From: Utkarsh Date: Fri, 27 Mar 2026 01:08:38 +0530 Subject: [PATCH 3/3] Apply validate_pit() in ppc_loo_pit_qq() --- R/ppc-loo.R | 2 +- tests/testthat/test-ppc-loo.R | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/R/ppc-loo.R b/R/ppc-loo.R index 7f910794..3fc0abb3 100644 --- a/R/ppc-loo.R +++ b/R/ppc-loo.R @@ -348,7 +348,7 @@ ppc_loo_pit_qq <- function(y, compare <- match.arg(compare) if (!is.null(pit)) { - stopifnot(is.numeric(pit), is_vector_or_1Darray(pit)) + pit <- validate_pit(pit) inform("'pit' specified so ignoring 'y','yrep','lw' if specified.") } else { suggested_package("rstantools") diff --git a/tests/testthat/test-ppc-loo.R b/tests/testthat/test-ppc-loo.R index 5f77f876..ecc5b475 100644 --- a/tests/testthat/test-ppc-loo.R +++ b/tests/testthat/test-ppc-loo.R @@ -78,6 +78,25 @@ test_that("ppc_loo_pit_data validates user-provided pit values", { ) }) +test_that("ppc_loo_pit_qq validates user-provided pit values", { + expect_error( + ppc_loo_pit_qq(pit = c(0.5, Inf)), + "between 0 and 1" + ) + expect_error( + ppc_loo_pit_qq(pit = c(-1, 0.5)), + "between 0 and 1" + ) + expect_error( + ppc_loo_pit_qq(pit = c(0.5, NA)), + "NAs not allowed" + ) + expect_error( + ppc_loo_pit_qq(pit = "not numeric"), + "is.numeric" + ) +}) + test_that("ppc_loo_pit_qq returns ggplot object", { skip_if_not_installed("rstanarm") skip_if_not_installed("loo")