tests/testthat/test-tf-ggplot-aesthetic-equivalence.R

# Tests for aesthetic specification equivalence in tf_ggplot
# Verifies that aesthetics can be specified in constructor or geom layer with identical results

test_that("tf aesthetics in constructor vs geom layer are equivalent - basic case", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 3, n_points = 5)

  # Method 1: tf aesthetic in constructor
  p1 <- tf_ggplot(data, aes(tf = func)) + geom_line()

  # Method 2: tf aesthetic in geom layer
  p2 <- tf_ggplot(data) + geom_line(aes(tf = func))

  # Build both plots
  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should have identical data structure
  expect_equal(nrow(built1$data[[1]]), nrow(built2$data[[1]]))
  expect_equal(ncol(built1$data[[1]]), ncol(built2$data[[1]]))
  expect_equal(names(built1$data[[1]]), names(built2$data[[1]]))

  # Should have same number of groups
  expect_equal(
    length(unique(built1$data[[1]]$group)),
    length(unique(built2$data[[1]]$group))
  )

  # x and y values should be identical (allowing for floating point differences)
  expect_equal(sort(built1$data[[1]]$x), sort(built2$data[[1]]$x))
  expect_equal(
    sort(built1$data[[1]]$y),
    sort(built2$data[[1]]$y),
    tolerance = 1e-10
  )
})

test_that("tf aesthetics with color mapping are equivalent", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 4, n_points = 6)

  # Method 1: aesthetics in constructor
  p1 <- tf_ggplot(data, aes(tf = func, color = group)) + geom_line()

  # Method 2: aesthetics in geom layer
  p2 <- tf_ggplot(data) +
    geom_line(aes(tf = func, color = group))

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should have identical structure
  expect_equal(dim(built1$data[[1]]), dim(built2$data[[1]]))

  # Color mappings should be identical
  expect_equal(sort(built1$data[[1]]$colour), sort(built2$data[[1]]$colour))

  # Groups should be identical
  expect_equal(sort(built1$data[[1]]$group), sort(built2$data[[1]]$group))

  # Check that color is consistent within groups for both methods
  check_color_consistency <- function(plot_data) {
    color_by_group <- split(plot_data$colour, plot_data$group)
    all(sapply(color_by_group, function(x) length(unique(x)) == 1))
  }

  expect_true(check_color_consistency(built1$data[[1]]))
  expect_true(check_color_consistency(built2$data[[1]]))
})

test_that("mixed tf and regular aesthetics are equivalent", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 3, n_points = 5)
  data$lw_var <- c(1, 2, 3)
  data$alpha_var <- c(0.3, 0.7, 0.9)

  # Method 1: mixed aesthetics in constructor
  p1 <- tf_ggplot(
    data,
    aes(tf = func, color = group, linewidth = lw_var, alpha = alpha_var)
  ) +
    geom_line()

  # Method 2: mixed aesthetics in geom layer
  p2 <- tf_ggplot(data) +
    geom_line(aes(
      tf = func,
      color = group,
      linewidth = lw_var,
      alpha = alpha_var
    ))

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should have identical dimensions and structure
  expect_equal(dim(built1$data[[1]]), dim(built2$data[[1]]))
  expect_setequal(names(built1$data[[1]]), names(built2$data[[1]]))

  # All aesthetic mappings should be identical
  aesthetic_cols <- c("colour", "linewidth", "alpha")
  for (col in aesthetic_cols) {
    expect_equal(
      sort(built1$data[[1]][[col]]),
      sort(built2$data[[1]][[col]]),
      info = paste("Mismatch in", col, "aesthetic")
    )
  }
})

test_that("multiple tf aesthetics are equivalent", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_multi_tf_data(n_funcs = 2, n_points = 4)

  # Method 1: multiple tf aesthetics in constructor
  p1 <- tf_ggplot(data, aes(tf_x = func1, tf_y = func2)) + geom_point()

  # Method 2: multiple tf aesthetics in geom layer
  p2 <- tf_ggplot(data) +
    geom_point(aes(tf_x = func1, tf_y = func2))

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should have identical structure
  expect_equal(dim(built1$data[[1]]), dim(built2$data[[1]]))

  # x and y coordinates should be identical
  expect_equal(sort(built1$data[[1]]$x), sort(built2$data[[1]]$x))
  expect_equal(sort(built1$data[[1]]$y), sort(built2$data[[1]]$y))

  # Groups should be identical
  expect_equal(sort(built1$data[[1]]$group), sort(built2$data[[1]]$group))
})

test_that("ribbon tf aesthetics are equivalent", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_band_tf_data(n_funcs = 2, n_points = 5)

  # Method 1: ribbon aesthetics in constructor
  p1 <- tf_ggplot(data, aes(tf_ymin = lower_func, tf_ymax = upper_func)) +
    geom_ribbon(alpha = 0.3)

  # Method 2: ribbon aesthetics in geom layer
  p2 <- tf_ggplot(data) +
    geom_ribbon(
      aes(tf_ymin = lower_func, tf_ymax = upper_func),
      alpha = 0.3
    )

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should have identical structure
  expect_equal(dim(built1$data[[1]]), dim(built2$data[[1]]))

  # ymin and ymax should be identical
  expect_equal(sort(built1$data[[1]]$ymin), sort(built2$data[[1]]$ymin))
  expect_equal(sort(built1$data[[1]]$ymax), sort(built2$data[[1]]$ymax))

  # Alpha should be identical
  expect_equal(built1$data[[1]]$alpha, built2$data[[1]]$alpha)
})

test_that("aesthetic inheritance and override work correctly", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 3, n_points = 5)

  # Base aesthetics in constructor, additional in geom
  p1 <- tf_ggplot(data, aes(tf = func)) +
    geom_line(aes(color = group))

  # All aesthetics in geom layer
  p2 <- tf_ggplot(data) +
    geom_line(aes(tf = func, color = group))

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should produce identical results
  expect_equal(dim(built1$data[[1]]), dim(built2$data[[1]]))
  expect_equal(sort(built1$data[[1]]$colour), sort(built2$data[[1]]$colour))
  expect_equal(sort(built1$data[[1]]$x), sort(built2$data[[1]]$x))
  expect_equal(sort(built1$data[[1]]$y), sort(built2$data[[1]]$y))
})

test_that("aesthetic override in geom layer works", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 3, n_points = 5)
  data$alt_group <- factor(c("X", "Y", "Z"))

  # Constructor aesthetic overridden in geom
  p1 <- tf_ggplot(data, aes(tf = func, color = group)) +
    geom_line(aes(color = alt_group)) # Override color aesthetic

  # Direct specification in geom
  p2 <- tf_ggplot(data, aes(tf = func)) +
    geom_line(aes(color = alt_group))

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should use alt_group for coloring in both cases
  expect_equal(sort(built1$data[[1]]$colour), sort(built2$data[[1]]$colour))

  # Should have 3 unique colors (X, Y, Z)
  expect_equal(length(unique(built1$data[[1]]$colour)), 3)
  expect_equal(length(unique(built2$data[[1]]$colour)), 3)
})

test_that("faceting works equivalently with different aesthetic specifications", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 6, n_points = 4)
  data$treatment <- factor(rep(c("A", "B"), each = 3))

  # Method 1: aesthetics in constructor
  p1 <- tf_ggplot(data, aes(tf = func)) +
    geom_line() +
    facet_wrap(~treatment)

  # Method 2: aesthetics in geom layer
  p2 <- tf_ggplot(data) +
    geom_line(aes(tf = func)) +
    facet_wrap(~treatment)

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should have identical panel structure
  expect_equal(dim(built1$layout$layout), dim(built2$layout$layout))
  expect_equal(built1$layout$layout$PANEL, built2$layout$layout$PANEL)

  # Plot data should be identical
  expect_equal(dim(built1$data[[1]]), dim(built2$data[[1]]))
  expect_equal(sort(built1$data[[1]]$PANEL), sort(built2$data[[1]]$PANEL))
})

test_that("custom arg parameter works equivalently", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 2, n_points = 11)
  custom_arg <- seq(0, 1, length.out = 6)

  # Method 1: aesthetics in constructor with custom arg
  p1 <- tf_ggplot(data, aes(tf = func), arg = custom_arg) + geom_line()

  # Method 2: aesthetics in geom layer with custom arg
  p2 <- tf_ggplot(data, arg = custom_arg) +
    geom_line(aes(tf = func))

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should use custom arg in both cases
  expect_equal(sort(unique(built1$data[[1]]$x)), custom_arg)
  expect_equal(sort(unique(built2$data[[1]]$x)), custom_arg)

  # Should have identical structure
  expect_equal(dim(built1$data[[1]]), dim(built2$data[[1]]))
  expect_equal(sort(built1$data[[1]]$x), sort(built2$data[[1]]$x))
  expect_equal(sort(built1$data[[1]]$y), sort(built2$data[[1]]$y))
})

test_that("multiple layers with different aesthetic specifications work", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 2, n_points = 5)

  # Mix of constructor and geom-level aesthetics across multiple layers
  p1 <- tf_ggplot(data, aes(tf = func)) +
    geom_line(aes(color = group)) +
    geom_point(aes(color = group), size = 2)

  # All aesthetics specified at geom level
  p2 <- tf_ggplot(data) +
    geom_line(aes(tf = func, color = group)) +
    geom_point(aes(tf = func, color = group), size = 2)

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Should have same number of layers
  expect_equal(length(built1$data), length(built2$data))

  # Each layer should be identical
  for (i in seq_along(built1$data)) {
    expect_equal(
      dim(built1$data[[i]]),
      dim(built2$data[[i]]),
      info = paste("Layer", i, "dimensions differ")
    )
    expect_equal(
      sort(built1$data[[i]]$x),
      sort(built2$data[[i]]$x),
      info = paste("Layer", i, "x values differ")
    )
    expect_equal(
      sort(built1$data[[i]]$y),
      sort(built2$data[[i]]$y),
      info = paste("Layer", i, "y values differ")
    )
    expect_equal(
      sort(built1$data[[i]]$colour),
      sort(built2$data[[i]]$colour),
      info = paste("Layer", i, "color values differ")
    )
  }
})

test_that("data transformation is identical regardless of aesthetic specification method", {
  skip_if_not_installed("ggplot2")
  skip_if_no_tf_ggplot()

  data <- create_test_tf_data(n_funcs = 3, n_points = 7)
  data$extra_var <- letters[1:3]

  # Complex aesthetic mapping
  aesthetic_mapping <- aes(tf = func, color = group, linetype = extra_var)

  # Method 1: in constructor
  p1 <- tf_ggplot(data, aesthetic_mapping) + geom_line()

  # Method 2: in geom layer
  p2 <- tf_ggplot(data) + geom_line(aesthetic_mapping)

  built1 <- ggplot_build(p1)
  built2 <- ggplot_build(p2)

  # Extract and sort all data for comparison
  data1 <- built1$data[[1]][order(built1$data[[1]]$group, built1$data[[1]]$x), ]
  data2 <- built2$data[[1]][order(built2$data[[1]]$group, built2$data[[1]]$x), ]

  # Reset row names for comparison
  rownames(data1) <- NULL
  rownames(data2) <- NULL

  # Should be completely identical
  common_cols <- sort(intersect(names(data1), names(data2)))
  expect_equal(data1[common_cols], data2[common_cols])
})

Try the tidyfun package in your browser

Any scripts or data that you put into this service are public.

tidyfun documentation built on April 24, 2026, 5:06 p.m.