From 6e96431e27783d214e36c37c94f83897d8eed552 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 30 Jan 2024 09:18:02 +0100 Subject: [PATCH 1/5] Only use finite breaks for computing fuzz --- R/bin.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/bin.R b/R/bin.R index 5cb1a948ee..787ed26c9d 100644 --- a/R/bin.R +++ b/R/bin.R @@ -1,6 +1,7 @@ bins <- function(breaks, closed = "right", - fuzz = 1e-08 * stats::median(diff(breaks))) { + fuzz = NULL) { check_numeric(breaks) + fuzz <- fuzz %||% 1e-08 * stats::median(diff(breaks[is.finite(breaks)])) closed <- arg_match0(closed, c("right", "left")) breaks <- sort(breaks) From 0cc146fc6d5aa712040c31b5726846965f286ff6 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 27 Feb 2024 15:44:40 +0100 Subject: [PATCH 2/5] compute fuzz after sorting --- R/bin.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/bin.R b/R/bin.R index 787ed26c9d..7ed8cae564 100644 --- a/R/bin.R +++ b/R/bin.R @@ -1,11 +1,11 @@ bins <- function(breaks, closed = "right", fuzz = NULL) { check_numeric(breaks) - fuzz <- fuzz %||% 1e-08 * stats::median(diff(breaks[is.finite(breaks)])) closed <- arg_match0(closed, c("right", "left")) - breaks <- sort(breaks) + # Adapted base::hist - this protects from floating point rounding errors + fuzz <- fuzz %||% 1e-08 * stats::median(diff(breaks[is.finite(breaks)])) if (closed == "right") { fuzzes <- c(-fuzz, rep.int(fuzz, length(breaks) - 1)) } else { From 958ef2f81ff0b37f84b12c05bd4e45246f159d01 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 27 Feb 2024 15:45:16 +0100 Subject: [PATCH 3/5] protect against NA fuzzes --- R/bin.R | 3 +++ 1 file changed, 3 insertions(+) diff --git a/R/bin.R b/R/bin.R index 7ed8cae564..8ca4bff921 100644 --- a/R/bin.R +++ b/R/bin.R @@ -6,6 +6,9 @@ bins <- function(breaks, closed = "right", # Adapted base::hist - this protects from floating point rounding errors fuzz <- fuzz %||% 1e-08 * stats::median(diff(breaks[is.finite(breaks)])) + if (!is.finite(fuzz)) { # happens when 0 or 1 finite breaks are given + fuzz <- .Machine$double.eps * 1e3 + } if (closed == "right") { fuzzes <- c(-fuzz, rep.int(fuzz, length(breaks) - 1)) } else { From 57c409470b5f9f52cd6d382161092abe96eaba88 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 27 Feb 2024 15:48:45 +0100 Subject: [PATCH 4/5] add news bullet --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index e6f6dca449..be3f0c65a9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # ggplot2 (development version) +* `stat_bin()` deals with non-finite breaks better (@teunbrand, #5665). + # ggplot2 3.5.0 This is a minor release that turned out quite beefy. It is focused on From b2d4876359ee224687e65746274da60419cd9bf1 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 27 Feb 2024 15:59:55 +0100 Subject: [PATCH 5/5] add test --- tests/testthat/test-stat-bin.R | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/testthat/test-stat-bin.R b/tests/testthat/test-stat-bin.R index d15a19fcff..6ab5ec96b2 100644 --- a/tests/testthat/test-stat-bin.R +++ b/tests/testthat/test-stat-bin.R @@ -112,6 +112,13 @@ test_that("stat_bin() provides width (#3522)", { # Underlying binning algorithm -------------------------------------------- +test_that("bins() computes fuzz with non-finite breaks", { + test <- bins(breaks = c(-Inf, 1, Inf)) + expect_equal(test$fuzzy, test$breaks, tolerance = 1e-10) + difference <- test$fuzzy - test$breaks + expect_equal(difference[2], 1000 * .Machine$double.eps, tolerance = 0) +}) + comp_bin <- function(df, ...) { plot <- ggplot(df, aes(x = x)) + stat_bin(...) layer_data(plot)