Nothing
b_test_mark <- coefficient_test_mark
b_test_built_mark <- function(plot, tag) {
ggplot2::ggplot_build(plot)$data[[layer_index(plot, tag)]]
}
test_that("addition evaluates no source callbacks or models and each build fits once", {
rows <- data.frame(x = 1:10, y = 2 * (1:10) + 1)
callbacks <- fits <- 0L
source <- function(data) { callbacks <<- callbacks + 1L; data }
suppressMessages(trace("lm", where = asNamespace("stats"), print = FALSE,
tracer = function() fits <<- fits + 1L))
withr::defer(suppressMessages(untrace("lm", where = asNamespace("stats"))))
base <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) + ggplot2::geom_point(data = source)
out <- base + geom_b(show_b0 = FALSE, run = 1)
expect_equal(callbacks, 0L)
expect_equal(fits, 0L)
for (i in 1:2) {
expect_no_warning(ggplot2::ggplot_build(out))
expect_equal(callbacks, i)
expect_equal(fits, i)
}
fit <- lm(y ~ x, rows)
callbacks <- fits <- 0L
out <- base + geom_b(model = fit)
expect_equal(callbacks, 0L)
expect_equal(fits, 0L)
expect_no_warning(ggplot2::ggplot_build(out))
expect_equal(callbacks, 1L)
expect_equal(fits, 0L)
})
test_that("temporary inference follows final inherited and owned mappings", {
rows <- data.frame(x = 1:10, y = 11 * (1:10))
base <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) + ggplot2::geom_point()
out <- base + geom_b(show_b0 = FALSE, run = 1) + ggplot2::aes(y = y * 2)
rise <- b_test_built_mark(out, "b1")
expect_equal(rise$yend - rise$y, 22)
owned <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) +
ggplot2::geom_point(ggplot2::aes(y = y * 3)) + geom_b(show_b0 = FALSE, run = 1) +
ggplot2::aes(y = y * 2)
rise <- b_test_built_mark(owned, "b1")
expect_equal(rise$yend - rise$y, 33)
local <- base + geom_b(data = function(data) data[1:6, ], show_b0 = FALSE, run = 1) +
ggplot2::aes(y = y * 2)
rise <- b_test_built_mark(local, "b1")
expect_equal(rise$yend - rise$y, 22)
local_owned <- base + geom_b(mapping = ggplot2::aes(y = y * 3),
data = function(data) data[1:6, ], show_b0 = FALSE, run = 1) + ggplot2::aes(y = y * 2)
rise <- b_test_built_mark(local_owned, "b1")
expect_equal(rise$yend - rise$y, 33)
})
test_that("observing an identity source preserves its within-panel row order", {
rows <- data.frame(g = factor(rep(c("b", "a"), 4)), y = 1:8)
base <- ggplot2::ggplot(rows, ggplot2::aes(g, y)) + ggplot2::geom_point()
original <- ggplot2::ggplot_build(base)$data[[1L]]
expect_equal(ggplot2::ggplot_build(base + geom_b())$data[[1L]][names(original)], original)
})
test_that("nested builds cannot replace the outer coefficient frame", {
rows <- data.frame(x = 1:10, y = 2 * (1:10))
nested <- FALSE
inner <- NULL
errors <- 0L
stat <- ggplot2::ggproto(NULL, ggplot2::Stat,
compute_group = function(data, scales) {
if (!nested) {
nested <<- TRUE
on.exit(nested <<- FALSE)
tryCatch(ggplot2::ggplot_build(inner), error = function(error) {
errors <<- errors + 1L
})
}
data.frame()
})
trigger <- ggplot2::layer(geom = ggplot2::GeomBlank, stat = stat,
position = "identity")
out <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) + ggplot2::geom_point() +
geom_model() + trigger + geom_b(show_b0 = FALSE, run = 1)
inner <- replace_plot_data(out, transform(rows, y = y + 100))
expect_no_warning(rise <- b_test_built_mark(out, "b1"))
expect_equal(rise$y, 2 * rise$x)
expect_equal(b_test_built_mark(inner, "b1")$y, rise$y + 100)
expect_equal(b_test_built_mark(out, "b1")$y, rise$y)
inner <- inner + ggplot2::geom_point(ggplot2::aes(size = ggplot2::after_stat(stop("nested failure"))))
expect_no_warning(recovered <- b_test_built_mark(out, "b1"))
expect_equal(errors, 1L)
expect_equal(recovered$y, rise$y)
})
test_that("placeholder rows are neutral to facets named x or y", {
rows <- data.frame(x = rep(1:3, each = 2), y = rep(c(3, 5, 7), each = 2),
f = rep(letters[1:3], each = 2))
fit <- lm(y ~ x, rows)
for (facet in c("x", "y", "f")) {
out <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) + ggplot2::geom_point() +
ggplot2::facet_wrap(stats::reformulate(facet)) +
geom_b(model = fit, run = 1, run_x = 1, show_b0 = FALSE)
expect_no_warning(rise <- b_test_built_mark(out, "b1"))
expect_equal(as.integer(rise$PANEL), 1:3)
expect_equal(rise$y, rep(3, 3))
expect_equal(rise$yend, rep(5, 3))
}
})
test_that("local-source selection hands callbacks to the planner without evaluating them", {
rows <- data.frame(x = 1:4, y = 2:5)
calls <- 0L
callback <- function(data) {
calls <<- calls + 1L
data[1:2, ]
}
base <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) +
ggplot2::geom_point(data = callback)
# Isolate the planning handoff; selection itself must not run the callback.
testthat::local_mocked_bindings(
b_layer_spec = function(object, model, args, fn, call, source_is_local = FALSE) {
expect_equal(calls, 0L)
selected <- object$layers[[1L]]
expect_true(is.function(selected$data))
expect_equal(selected$layer_data(rows), rows[1:2, ])
expect_equal(calls, 1L)
list(plot = object, marks = list())
},
b_defer_spec = identity
)
expect_no_warning(base + geom_b(mapping = ggplot2::aes(x, y)))
expect_equal(calls, 1L)
})
test_that("deferred placement follows replaced plot rows and local callbacks", {
withr::local_options(lifecycle_verbosity = "quiet")
rows <- data.frame(x = 1:10, y = c(3, 3, 6, 7, 8, 10, 12, 13, 15, 19))
fit <- lm(y ~ x, rows)
replacement <- transform(rows, x = x + 100, y = y + 50)
for (model in list(NULL, fit)) {
for (callback in list(NULL, function(d) d[seq_len(6), ])) {
base <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) + ggplot2::geom_point()
original <- base + geom_b(data = callback, model = model, show_b0 = FALSE, run = 1)
changed <- replace_plot_data(original, replacement)
early <- replace_plot_data(base, replacement) +
geom_b(data = callback, model = model, show_b0 = FALSE, run = 1)
expect_no_warning(actual <- b_test_built_mark(changed, "b1"))
expect_equal(actual, b_test_built_mark(early, "b1"))
expect_gt(actual$x, 100)
used <- if (is.null(callback)) replacement else callback(replacement)
expected_fit <- model %||% lm(y ~ x, used)
expect_equal(actual$y, unname(predict(expected_fit, data.frame(x = actual$x))))
expect_equal(b_test_built_mark(original, "b1")$x, b_test_mark(original, "b1")$data$x)
expect_equal(b_test_built_mark(replace_plot_data(original, replacement), "b1"), actual)
}
}
})
test_that("deferred categorical components accept fewer, more, and reordered levels", {
withr::local_options(lifecycle_verbosity = "quiet")
rows <- data.frame(g = factor(rep(c("a", "b"), each = 3)), y = 1:6)
replacements <- list(
data.frame(g = factor(rep(c("a", "b", "c", "d"), each = 3)), y = 1:12),
data.frame(g = factor(rep("a", 3)), y = 1:3),
data.frame(g = factor(rep(c("b", "a", "c"), each = 3), levels = c("b", "a", "c")), y = 1:9)
)
for (callback in list(NULL, function(d) d[d$y > 1, ])) {
base <- ggplot2::ggplot(rows, ggplot2::aes(g, y)) + ggplot2::geom_point()
original <- base + geom_b(data = callback, run_x = 1)
for (replacement in replacements) {
out <- replace_plot_data(original, replacement)
expect_no_warning(built <- ggplot2::ggplot_build(out))
tags <- vapply(out$layers, function(l) attr(l, "coursekata_layer") %||% "", character(1))
arrows <- do.call(rbind, built$data[grepl("^bk_([0-9]+|more)$", tags)])
used <- if (is.null(callback)) replacement else callback(replacement)
means <- tapply(used$y, used$g, mean)
expect_equal(nrow(arrows), length(means) - 1L)
if (nrow(arrows)) {
expect_equal(arrows$y, rep(unname(means[[1L]]), nrow(arrows)))
expect_equal(arrows$yend, as.numeric(means[-1L]))
}
expect_no_warning(ggplot2::ggplotGrob(out))
overflow <- built$data[[layer_index(out, "bk_more_label")]]
if (nrow(overflow) > 1L) {
labels <- ggplot2::layer_grob(out, layer_index(out, "bk_more_label"))[[1L]]
expect_length(labels$children, nrow(overflow))
expect_true(all(vapply(labels$children, inherits, logical(1), "text")))
}
expect_equal(nrow(b_test_built_mark(original, "bk_2")), 1L)
}
}
})
test_that("canonical orientation determines the coefficient outcome axis", {
rows <- data.frame(x = 1:10, y = c(3, 3, 6, 7, 8, 10, 12, 13, 15, 19))
base <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) + ggplot2::geom_point()
for (orientation in c("x", "y")) {
fit <- if (orientation == "x") lm(y ~ x, rows) else lm(x ~ y, rows)
out <- base + geom_model(orientation = orientation) + geom_b(show_b0 = FALSE, run = 1)
for (flip in c(FALSE, TRUE)) {
plot <- if (flip) out + ggplot2::coord_flip() else out
rise <- b_test_built_mark(plot, "b1")
outcome <- if (orientation == "x") "y" else "x"
predictor <- if (orientation == "x") "x" else "y"
expect_equal(rise[[paste0(outcome, "end")]] - rise[[outcome]], unname(coef(fit)[[2L]]))
expect_equal(rise[[outcome]], unname(coef(fit)[[1L]] + coef(fit)[[2L]] * rise[[predictor]]))
grob <- ggplot2::layer_grob(plot, layer_index(plot, "b1"))[[1L]]
expect_length(grob$x0, 1L)
physical <- if (flip) predictor else outcome
expect_gt(abs(as.numeric(grob[[paste0(physical, "1")]]) - as.numeric(grob[[paste0(physical, "0")]])), 0)
}
}
})
test_that("standalone inference carries mapped weights to the canonical model", {
rows <- data.frame(x = 1:8, y = c(1, 2, 4, 2, 8, 3, 4, 20), w = c(rep(1, 7), 20))
base <- ggplot2::ggplot(rows, ggplot2::aes(x, y, weight = w)) + ggplot2::geom_point()
fit <- lm(y ~ x, rows, weights = w)
for (model in list(NULL, geom_model())) {
expect_no_warning(out <- base + model + geom_b(show_b0 = FALSE, run = 1))
expect_no_warning(rise <- b_test_built_mark(out, "b1"))
expect_equal(rise$yend - rise$y, unname(coef(fit)[[2L]]))
expect_equal(rise$y, unname(predict(fit, data.frame(x = rise$x))))
}
})
test_that("explicit placement works on one-row and constant-predictor facets", {
rows <- data.frame(x = 1:10, y = (1:10) * 2 + 1)
fit <- lm(y ~ x, rows)
for (panel in c(FALSE, TRUE)) {
source <- if (panel) data.frame(x = c(2, 2, 7, 7), y = c(3, 4, 5, 6), g = c("a", "a", "b", "b")) else rows[1, ]
base <- ggplot2::ggplot(source, ggplot2::aes(x, y)) + ggplot2::geom_point()
if (panel) base <- base + ggplot2::facet_wrap(~g)
expect_no_warning(out <- base + geom_b(model = fit, run = 2, run_x = 3, show_b0 = FALSE))
expect_no_warning(rise <- b_test_built_mark(out, "b1"))
expect_equal(nrow(rise), if (panel) 2L else 1L)
expect_equal(rise$x, rep(3, nrow(rise)))
expect_equal(rise$y, rep(7, nrow(rise)))
expect_equal(rise$yend, rep(11, nrow(rise)))
}
})
test_that("unsupported coefficient claims fail with native diagnostics", {
rows <- data.frame(x = 1:10, z = c(2, 1, 5, 3, 8, 6, 4, 10, 7, 9), y = 1:10)
base <- ggplot2::ggplot(rows, ggplot2::aes(x, y)) + ggplot2::geom_point()
expect_error(base + geom_b(model = lm(y ~ x:z, rows)), "geom_b.*one predictor")
expect_error(base + geom_b(model = lm(y ~ x + offset(z), rows)), "geom_b.*offset")
expect_error(base + geom_model(position = ggplot2::position_nudge(y = 10)) + geom_b(), "geom_b.*identity model position")
expect_error(ggplot2::ggplotGrob(base + geom_b() + ggplot2::coord_polar()), "geom_b.*Cartesian")
})
test_that("the native coefficient constructor and gf doors share every mark", {
models <- list(lm(Thumb ~ Height, Fingers), lm(Thumb ~ Sex, Fingers),
lm(Thumb ~ 1, Fingers))
mappings <- list(ggplot2::aes(Height, Thumb), ggplot2::aes(Sex, Thumb),
ggplot2::aes(Height, Thumb))
for (i in seq_along(models)) {
base <- ggplot2::ggplot(Fingers, mappings[[i]]) + ggplot2::geom_point()
native <- base + geom_b(model = models[[i]])
for (door in list(gf_b, gf_coef)) {
translated <- door(base, models[[i]])
left <- ggplot2::ggplot_build(native)$data
right <- ggplot2::ggplot_build(translated)$data
expect_equal(left, right)
for (j in seq_along(left)[-1L]) {
# Compare actual segment endpoints / text positions; a segments grob
# has no $x member, so checking it would be vacuous.
a <- ggplot2::layer_grob(native, j)[[1L]]
b <- ggplot2::layer_grob(translated, j)[[1L]]
columns <- if (inherits(a, "segments")) c("x0", "y0", "x1", "y1") else c("x", "y")
if (inherits(a, c("segments", "text", "points"))) {
expect_true(length(a[[columns[[1L]]]]) > 0)
for (column in columns) {
convert <- if (startsWith(column, "x")) grid::convertX else grid::convertY
expect_equal(convert(a[[column]], "npc", valueOnly = TRUE),
convert(b[[column]], "npc", valueOnly = TRUE))
}
expect_equal(a$gp, b$gp)
}
}
}
}
})
test_that("inferred triangles attach to the censored fit rather than whole-data coefficients", {
for (limits in list(c(60, 72), c(55, 68))) {
base <- gf_point(Thumb ~ Height, data = Fingers) |>
gf_lims(x = limits) |> gf_model()
out <- suppressWarnings(base |> gf_b(show_b0 = FALSE))
built <- suppressWarnings(ggplot2::ggplot_build(out))
line <- built$data[[layer_index(out, "model")]]
rise <- built$data[[layer_index(out, "b1")]]
run <- built$data[[layer_index(out, "run")]]
kept <- subset(Fingers, Height >= limits[[1L]] & Height <= limits[[2L]])
fit <- lm(Thumb ~ Height, kept)
expect_true(all(is.finite(unlist(rise[c("x", "y", "xend", "yend")]))))
expect_equal(rise$y, unname(predict(fit, data.frame(Height = rise$x))))
expect_equal(run$y, unname(predict(fit, data.frame(Height = run$xend))))
expect_equal(rise$yend - rise$y, unname(coef(fit)[[2L]]) * (run$xend - run$x))
expect_equal(line$y, unname(predict(fit, data.frame(Height = line$x))))
rise_grob <- suppressWarnings(ggplot2::layer_grob(out, layer_index(out, "b1")))[[1L]]
run_grob <- suppressWarnings(ggplot2::layer_grob(out, layer_index(out, "run")))[[1L]]
expect_length(rise_grob$y0, 1L)
expect_equal(as.numeric(rise_grob$y0),
as.numeric(run_grob$y0) -
(rise$yend - rise$y) /
diff(built$layout$panel_params[[1L]]$y.range))
}
})
test_that("transformed inferred coordinates are inverted exactly once", {
base <- gf_point(Thumb ~ Height, data = Fingers) + ggplot2::scale_x_log10() +
ggplot2::scale_y_log10()
out <- base |> gf_model() |> gf_b(show_b0 = FALSE, run = 0.02)
fit <- lm(log10(Thumb) ~ log10(Height), Fingers)
built <- ggplot2::ggplot_build(out)
rise <- built$data[[layer_index(out, "b1")]]
run <- built$data[[layer_index(out, "run")]]
expect_equal(run$xend - run$x, 0.02)
expect_equal(rise$y, unname(coef(fit)[[1L]] + coef(fit)[[2L]] * rise$x))
expect_equal(rise$yend - rise$y, unname(coef(fit)[[2L]]) * 0.02)
expect_equal(b_test_mark(out, "run_label")$data$label, "0.02")
expect_equal(ggplot2::ggplot_build(out + ggplot2::coord_flip())$data, built$data)
})
test_that("source callbacks and pinned mappings keep row alignment and RNG state", {
set.seed(23)
rows <- Fingers
rows$Height[c(3, 11)] <- NA_real_
base <- ggplot2::ggplot(rows, ggplot2::aes(log(Height), shuffle(Thumb))) +
ggplot2::geom_point(data = function(d) d[seq_len(60), ])
seed <- .Random.seed
out <- suppressWarnings(base + geom_b(show_b0 = FALSE, run = 0.02))
expect_identical(.Random.seed, seed)
source <- ggplot2::ggplot_build(out)$data[[1L]]
x <- source$x
y <- source$y
fit <- lm(y ~ x)
rise <- b_test_mark(out, "b1")$data
run <- b_test_mark(out, "run")$data
expect_equal(nrow(source), 60L)
expect_equal(rise$y, unname(coef(fit)[[1L]] + coef(fit)[[2L]] * rise$x))
expect_equal(rise$yend - rise$y, unname(coef(fit)[[2L]]) * (run$xend - run$x))
expect_null(attr(base, "coursekata_pins"))
expect_null(base$layers[[1L]]$mapping)
})
test_that("explicit claims retain coefficients through model metadata and layer copies", {
fit <- lm(Thumb ~ Height, subset(Fingers, Sex == "male"))
base <- gf_point(Thumb ~ Height, data = Fingers)
for (model_plot in list(base + geom_model(model = fit), gf_model(base, fit))) {
layer <- model_plot$layers[[layer_index(model_plot, "model")]]
expect_identical(model_layer_fit(layer), fit)
expect_identical(model_layer_fit(layer_with(layer)), fit)
expect_identical(model_layer_fit(tag_layer(layer, "another")), fit)
out <- model_plot |> gf_b(run = 2)
expect_equal(b_test_mark(out, "b0")$data$y, unname(coef(fit)[[1L]]))
rise <- b_test_mark(out, "b1")$data
expect_equal(rise$yend - rise$y, unname(coef(fit)[[2L]]) * 2)
}
weighted <- lm(Thumb ~ Height, Fingers, weights = seq_len(nrow(Fingers)))
out <- base + geom_b(model = weighted, run = 2)
rise <- b_test_mark(out, "b1")$data
expect_equal(rise$yend - rise$y, unname(coef(weighted)[[2L]]) * 2)
for (model_plot in list(base + geom_model(model = weighted), gf_model(base, weighted))) {
expect_identical(
model_layer_fit(model_plot$layers[[layer_index(model_plot, "model")]]),
weighted
)
out <- model_plot + geom_b(run = 2)
rise <- b_test_mark(out, "b1")$data
expect_equal(rise$yend - rise$y, unname(coef(weighted)[[2L]]) * 2)
}
})
test_that("native diagnostics name the public constructor and reject ambiguous claims", {
base <- ggplot2::ggplot(Fingers, ggplot2::aes(Height, Thumb)) + ggplot2::geom_point()
expect_error(base + geom_b(model = Thumb ~ Height), "geom_b.*annotates a model that has been fit")
expect_error(base + geom_b(model = lm(Thumb ~ Height - 1, Fingers)), "starting from b0")
expect_error(base + geom_b(model = lm(Thumb ~ Height, Fingers), color = ~Sex), "not a mapping")
expect_error(base + ggplot2::coord_polar() + geom_b(), "cartesian")
expect_error(base + geom_model() + geom_model() + geom_b(), "one model")
expect_error(base + geom_model(formula = y ~ poly(x, 2)) + geom_b(), "linear model")
transformed <- ggplot2::ggplot(Fingers, ggplot2::aes(Height, log(Thumb))) +
ggplot2::geom_point()
expect_error(ggplot2::ggplot_build(transformed + geom_b(model = lm(Thumb ~ Height, Fingers))), "outcome")
expect_error(base + ggplot2::facet_wrap(~Sex) + geom_b(), "faceted plot")
expect_error(ggplot2::ggplot_build(base + geom_b() + ggplot2::facet_wrap(~Sex)), "geom_b.*faceted plot")
expect_no_error(base + ggplot2::scale_x_continuous(limits = c(1, 2)) +
geom_b(model = lm(Thumb ~ Height, Fingers)))
})
test_that("one annotation object can be reused without changing its source plots", {
layer <- geom_b(show_b0 = FALSE)
first <- ggplot2::ggplot(Fingers, ggplot2::aes(Height, Thumb)) + ggplot2::geom_point()
second <- ggplot2::ggplot(Fingers, ggplot2::aes(Weight, Thumb)) + ggplot2::geom_point()
a <- first + layer
b <- second + layer
expect_false(identical(b_test_mark(a, "b1")$data, b_test_mark(b, "b1")$data))
expect_length(first$layers, 1L)
expect_length(second$layers, 1L)
expect_null(layer$model)
})
test_that("categorical arrows follow coefficient levels through reordered scales", {
rows <- data.frame(g = factor(rep(c("a", "b", "c"), each = 3)),
y = c(2, 3, 4, 7, 8, 9, 11, 12, 13))
fit <- lm(y ~ g, rows)
base <- ggplot2::ggplot(rows, ggplot2::aes(g, y)) + ggplot2::geom_point() +
ggplot2::scale_x_discrete(limits = c("c", "a", "b"))
for (out in list(base + geom_b(model = fit), base + geom_b())) {
expect_equal(b_test_mark(out, "bk_2")$data$x, 3 - 0.18)
expect_equal(b_test_mark(out, "bk_3")$data$x, 1 - 0.18)
expect_equal(b_test_mark(out, "b0")$data$y, 3)
expect_equal(b_test_mark(out, "bk_2")$data$yend, 8)
expect_equal(b_test_mark(out, "bk_3")$data$yend, 12)
}
})
test_that("late scales and limits replan coefficients and reset on every build", {
base <- gf_point(Thumb ~ Height, data = Fingers) |> gf_model()
annotated <- base |> gf_b(show_b0 = FALSE)
original_layer <- base$layers[[layer_index(base, "model")]]
for (change in list(ggplot2::scale_x_continuous(limits = c(55, 68)),
ggplot2::scale_x_log10(), ggplot2::scale_x_reverse())) {
early <- suppressWarnings(gf_b(base + change, show_b0 = FALSE))
late <- annotated + change
a <- suppressWarnings(ggplot2::ggplot_build(early))
b <- suppressWarnings(ggplot2::ggplot_build(late))
for (tag in c("b1", "run", "b1_label", "run_label")) {
expect_equal(a$data[[layer_index(early, tag)]], b$data[[layer_index(late, tag)]])
}
# A build of a derived plot must not leave transformed/censored rows in
# the shared plan when the original is built again.
repeated <- ggplot2::ggplot_build(annotated)
expect_equal(repeated$data[[layer_index(annotated, "b1")]]$y,
b_test_mark(annotated, "b1")$data$y)
}
expect_identical(original_layer$stat, StatModel)
expect_identical(base$layers[[layer_index(base, "model")]], original_layer)
with_intercept <- base |> gf_b()
expect_no_warning(transformed <- ggplot2::ggplot_build(with_intercept + ggplot2::scale_x_log10()))
expect_equal(transformed$data[[layer_index(with_intercept, "b0")]]$x, 0)
})
test_that("a supplied fit stays fixed while late limits move automatic placement", {
fit <- lm(Thumb ~ Height, Fingers)
base <- gf_point(Thumb ~ Height, data = Fingers)
late <- (base + geom_b(model = fit, show_b0 = FALSE)) |>
gf_lims(x = c(55, 68))
early <- (base |> gf_lims(x = c(55, 68))) + geom_b(model = fit, show_b0 = FALSE)
a <- ggplot2::ggplot_build(late)$data[[layer_index(late, "b1")]]
b <- ggplot2::ggplot_build(early)$data[[layer_index(early, "b1")]]
expect_equal(a, b)
expect_true(is.finite(a$x))
expect_equal(a$y, unname(predict(fit, data.frame(Height = a$x))))
})
test_that("coefficient components never refit the canonical model during a build", {
out <- gf_point(Thumb ~ Height, data = Fingers) |> gf_model() |> gf_b()
calls <- 0L
# Trace the real function so lm's data-masked weights expression keeps its
# ordinary evaluation environment; a forwarding mock changes that contract.
suppressMessages(trace("lm", where = asNamespace("stats"), print = FALSE,
tracer = function() calls <<- calls + 1L))
withr::defer(suppressMessages(untrace("lm", where = asNamespace("stats"))))
expect_no_warning(built <- ggplot2::ggplot_build(out))
expect_equal(calls, 1L)
expect_equal(nrow(built$data[[layer_index(out, "model")]]), 80L)
expect_equal(nrow(built$data[[layer_index(out, "b1")]]), 1L)
expect_no_warning(ggplot2::ggplot_build(out))
expect_equal(calls, 2L)
})
test_that("explicit coefficients are transformed for drawing without changing their claim", {
fit <- lm(Thumb ~ Height, Fingers)
out <- (gf_point(Thumb ~ Height, data = Fingers) |>
gf_b(fit, run = 5, run_x = 60, show_b0 = FALSE)) +
ggplot2::scale_x_log10() + ggplot2::scale_y_log10()
built <- ggplot2::ggplot_build(out)
rise <- built$data[[layer_index(out, "b1")]]
run <- built$data[[layer_index(out, "run")]]
predictions <- unname(predict(fit, data.frame(Height = c(60, 65))))
expect_equal(rise$x, log10(60))
expect_equal(c(rise$y, rise$yend), log10(predictions))
expect_equal(run$xend, log10(65))
expect_equal(built$data[[layer_index(out, "run_label")]]$label, "5")
})
test_that("deferred categorical roles survive levels restored by a later scale", {
rows <- data.frame(g = factor(rep(c("a", "b", "c"), each = 3)),
y = c(2, 3, 4, 7, 8, 9, 11, 12, 13))
base <- ggplot2::ggplot(rows, ggplot2::aes(g, y)) + ggplot2::geom_point() +
geom_model()
complete <- base + ggplot2::scale_x_discrete(limits = c("a", "b", "c")) + geom_b()
expected <- ggplot2::ggplot_build(complete)
for (visible in list(c("a", "b"), "a", c("b", "c"))) {
partial <- suppressWarnings(base + ggplot2::scale_x_discrete(limits = visible) + geom_b())
restored <- suppressMessages(partial + ggplot2::scale_x_discrete(limits = c("a", "b", "c")))
built <- ggplot2::ggplot_build(restored)
for (tag in c("b0", "bk_2", "bk_2_label", "bk_3", "bk_3_label")) {
expect_false(is.na(layer_index(restored, tag)))
actual <- built$data[[layer_index(restored, tag)]]
expect_equal(nrow(actual), 1L)
expect_equal(actual, expected$data[[layer_index(complete, tag)]])
}
suppressed <- suppressWarnings(ggplot2::ggplot_build(partial))
expect_equal(nrow(suppressed$data[[layer_index(partial, "bk_3")]]), 0L)
expect_equal(ggplot2::ggplot_build(restored)$data, built$data)
}
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.