tests/testthat/test-analyze-fitcurves.R

# Golden tests for FitCurves (legacy analyze.R functions)
# These tests capture the expected behavior of FitCurves to prevent regressions
# during future modernization efforts (API-008)

# Helper function to compare numeric values with tolerance
expect_equal_numeric <- function(actual, expected, tolerance = 1e-4) {
  expect_equal(actual, expected, tolerance = tolerance)
}

# Test data setup
test_that("FitCurves test data is available", {
  data(apt, package = "beezdemand")
  expect_true(exists("apt"))
  expect_true(is.data.frame(apt))
  expect_true(all(c("id", "x", "y") %in% names(apt)))
})

# =============================================================================
# Golden Tests for FitCurves with HS equation
# =============================================================================

test_that("FitCurves with HS equation and fixed k produces correct results", {
  data(apt, package = "beezdemand")
  test_ids <- c(19, 30, 38)
  test_data <- apt[apt$id %in% test_ids, ]

  # This should produce a warning about zeros being dropped
  result <- suppressWarnings(FitCurves(test_data, "hs", k = 2))

  # Check structure

  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 3)
  expect_true(all(c("id", "Equation", "Q0d", "K", "Alpha", "R2", "Pmaxd", "Omaxd") %in% names(result)))
  expect_true(all(c("alpha_star", "alpha_star_se") %in% names(result)))

  # Golden values for ID 19
  row_19 <- result[result$id == 19, ]
  expect_equal_numeric(row_19$Q0d, 10.158664, tolerance = 0.01)
  expect_equal(row_19$K, 2)
  expect_equal_numeric(row_19$Alpha, 0.002047574, tolerance = 1e-5)
  expect_equal_numeric(
    row_19$alpha_star,
    -row_19$Alpha / log(1 - 1 / (row_19$K * log(10))),
    tolerance = 1e-8
  )
  expect_true(is.finite(row_19$alpha_star_se) && row_19$alpha_star_se >= 0)
  expect_equal_numeric(row_19$R2, 0.9804182, tolerance = 0.01)
  expect_equal_numeric(row_19$Pmaxd, 13.86976, tolerance = 0.1)
  expect_equal_numeric(row_19$Omaxd, 44.43035, tolerance = 0.1)

  # Golden values for ID 30
  row_30 <- result[result$id == 30, ]
  expect_equal_numeric(row_30$Q0d, 2.807366, tolerance = 0.01)
  expect_equal_numeric(row_30$Alpha, 0.005865523, tolerance = 1e-5)
  expect_equal_numeric(row_30$R2, 0.7723159, tolerance = 0.01)

  # Golden values for ID 38
  row_38 <- result[result$id == 38, ]
  expect_equal_numeric(row_38$Q0d, 4.497456, tolerance = 0.01)
  expect_equal_numeric(row_38$Alpha, 0.004203441, tolerance = 1e-5)
})

test_that("FitCurves with HS equation warns about zeros", {
  data(apt, package = "beezdemand")
  # ID 38 has zeros in the data
  test_data <- apt[apt$id == 38, ]

  expect_warning(
    FitCurves(test_data, "hs", k = 2),
    "Zeros found in data"
  )
})

# =============================================================================
# Golden Tests for FitCurves with Koffarnus equation
# =============================================================================

test_that("FitCurves with Koff equation and fixed k produces correct results", {
  data(apt, package = "beezdemand")
  test_ids <- c(19, 30, 38)
  test_data <- apt[apt$id %in% test_ids, ]

  result <- FitCurves(test_data, "koff", k = 2)

  # Check structure
  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 3)

  # Golden values for ID 19
  row_19 <- result[result$id == 19, ]
  expect_equal(row_19$Equation, "koff")
  expect_equal_numeric(row_19$Q0d, 10.072114, tolerance = 0.01)
  expect_equal_numeric(row_19$Alpha, 0.002003155, tolerance = 1e-5)
  expect_equal_numeric(
    row_19$alpha_star,
    -row_19$Alpha / log(1 - 1 / (row_19$K * log(10))),
    tolerance = 1e-8
  )
  expect_true(is.finite(row_19$alpha_star_se) && row_19$alpha_star_se >= 0)
  expect_equal_numeric(row_19$R2, 0.9676372, tolerance = 0.01)
  expect_equal_numeric(row_19$Pmaxd, 14.29914, tolerance = 0.1)

  # Golden values for ID 30
  row_30 <- result[result$id == 30, ]
  expect_equal_numeric(row_30$Q0d, 2.967428, tolerance = 0.01)
  expect_equal_numeric(row_30$Alpha, 0.006381213, tolerance = 1e-5)

  # Golden values for ID 38
  row_38 <- result[result$id == 38, ]
  expect_equal_numeric(row_38$Q0d, 4.605634, tolerance = 0.01)
  expect_equal_numeric(row_38$Alpha, 0.004874198, tolerance = 1e-5)
})

# =============================================================================
# Golden Tests for FitCurves with aggregation
# =============================================================================

test_that("FitCurves with Mean aggregation produces correct results", {
  data(apt, package = "beezdemand")
  test_ids <- c(19, 30, 38)
  test_data <- apt[apt$id %in% test_ids, ]

  result <- FitCurves(test_data, "hs", k = 2, agg = "Mean")

  # Check structure - should be single row
  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 1)
  expect_equal(as.character(result$id), "mean")

  # Golden values
  expect_equal_numeric(result$Q0d, 6.170368, tolerance = 0.01)
  expect_equal_numeric(result$Alpha, 0.003859777, tolerance = 1e-5)
  expect_equal_numeric(result$R2, 0.9729868, tolerance = 0.01)
})

test_that("FitCurves with Pooled aggregation produces correct results", {
  data(apt, package = "beezdemand")
  test_ids <- c(19, 30, 38)
  test_data <- apt[apt$id %in% test_ids, ]

  result <- suppressWarnings(FitCurves(test_data, "hs", k = 2, agg = "Pooled"))

  # Check structure - should be single row
  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 1)
  expect_equal(as.character(result$id), "pooled")

  # Golden values (lower R2 expected for pooled data)
  expect_equal_numeric(result$Q0d, 4.993143, tolerance = 0.01)
  expect_equal_numeric(result$Alpha, 0.003598877, tolerance = 1e-5)
  expect_equal_numeric(result$R2, 0.3060571, tolerance = 0.01)
})

# =============================================================================
# Golden Tests for FitCurves with k="range"
# =============================================================================

test_that("FitCurves with k='range' produces correct results", {
  data(apt, package = "beezdemand")
  test_ids <- c(19, 30, 38)
  test_data <- apt[apt$id %in% test_ids, ]

  result <- suppressWarnings(FitCurves(test_data, "hs", k = "range"))

  # Check structure
  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 3)

  # All should have the same K value (calculated from range)
  expect_equal_numeric(result$K[1], 1.077236, tolerance = 0.01)
  expect_equal(result$K[1], result$K[2])
  expect_equal(result$K[1], result$K[3])

  # Golden values for ID 19
  row_19 <- result[result$id == 19, ]
  expect_equal_numeric(row_19$Q0d, 10.454333, tolerance = 0.01)
  expect_equal_numeric(row_19$Alpha, 0.004395264, tolerance = 1e-5)
})

# =============================================================================
# Input validation tests
# =============================================================================

test_that("FitCurves validates required inputs", {
  data(apt, package = "beezdemand")

  # Missing data
  expect_error(FitCurves(), "Need to provide a dataframe")

  # Missing equation
  expect_error(FitCurves(apt), "Need to specify an equation")

  # Invalid aggregation
  expect_error(
    FitCurves(apt[apt$id == 19, ], "hs", k = 2, agg = "invalid"),
    "No correct agg specified"
  )
})

test_that("FitCurves handles constrainq0 validation", {
  data(apt, package = "beezdemand")
  test_data <- apt[apt$id == 19, ]

  # constrainq0 must be numeric
  expect_error(
    FitCurves(test_data, "hs", k = 2, constrainq0 = "abc"),
    "Q0 constraint must be a number"
  )
})

# =============================================================================
# Detailed output tests
# =============================================================================

test_that("FitCurves detailed=TRUE returns list with model objects", {
  data(apt, package = "beezdemand")
  test_data <- apt[apt$id == 19, ]

  result <- suppressWarnings(FitCurves(test_data, "hs", k = 2, detailed = TRUE))

  # Should be a list with 4 elements
  expect_type(result, "list")
  expect_length(result, 4)

  # First element is the results dataframe
  expect_s3_class(result[[1]], "data.frame")
  expect_equal(nrow(result[[1]]), 1)

  # Second element contains model fits
  expect_type(result[[2]], "list")

  # Third element contains individual data
  expect_type(result[[3]], "list")

  # Fourth element contains new data
  expect_type(result[[4]], "list")
})

# =============================================================================
# Column specification tests
# =============================================================================

test_that("FitCurves works with custom column names", {
  data(apt, package = "beezdemand")
  test_data <- apt[apt$id == 19, ]

  # Rename columns
  names(test_data) <- c("subject", "price", "consumption")

  result <- suppressWarnings(
    FitCurves(test_data, "hs", k = 2,
              xcol = "price", ycol = "consumption", idcol = "subject")
  )

  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 1)
})

# =============================================================================
# Edge case tests
# =============================================================================

test_that("FitCurves handles single subject", {
  data(apt, package = "beezdemand")
  test_data <- apt[apt$id == 19, ]

  result <- suppressWarnings(FitCurves(test_data, "hs", k = 2))

  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 1)
  # Note: id is returned as character
  expect_equal(as.character(result$id), "19")
})

test_that("FitCurves handles data with no zeros (koff equation)", {
  data(apt, package = "beezdemand")
  # Filter to data without zeros
  test_data <- apt[apt$id == 19 & apt$y > 0, ]

  # Should not produce warning for koff equation
  result <- FitCurves(test_data, "koff", k = 2)

  expect_s3_class(result, "data.frame")
  expect_equal(nrow(result), 1)
})

Try the beezdemand package in your browser

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

beezdemand documentation built on March 3, 2026, 9:07 a.m.