tests/testthat/test-monotonic.R

context("monotonic")

# Simulations are a bit time-consuming
skip_on_cran()

# Simulate data from a monotonically increasing function
set.seed(123123)
x <- runif(80) * 4 - 1
x <- sort(x)
f <- exp(4 * x) / (1 + exp(4 * x))
y <- f + rnorm(80) * 0.1
mod_data <- data.frame(y = y, x = x, z = rnorm(80), time = 1:80)

test_that("k must be an even integer for s(bs = 'moi')", {
  expect_error(
    mvgam(y ~ s(x, bs = 'moi', k = 11), data = mod_data, family = gaussian()),
    "Argument 'k(bs = 'moi')'  must be an even integer",
    fixed = TRUE
  )

  expect_error(
    mvgam(y ~ s(x, bs = 'moi', k = 1), data = mod_data, family = gaussian()),
    "Basis dimension is too small",
    fixed = TRUE
  )
})

test_that("monotonic only works for one dimensional smooths", {
  expect_error(
    mvgam(
      y ~ s(x, z, bs = 'moi', k = 10),
      data = mod_data,
      family = gaussian()
    ),
    "Monotonic basis only handles 1D smooths",
    fixed = TRUE
  )
})

test_that("monotonic for observation models working properly", {
  mod <- mvgam(
    y ~ z + s(x, bs = 'moi', k = 18),
    data = mod_data,
    family = gaussian(),
    run_model = FALSE
  )

  # Monotonic indices should be in the model_data
  expect_true("b_idx_s_x_" %in% names(mod$model_data))

  # The smooth should be an MOI class
  expect_true(inherits(mod$mgcv_model$smooth[[1]], 'moi.smooth'))

  # The coefficients should be fixed to be non-negative
  expect_true(any(grepl(
    'b[b_idx_s_x_] = abs(b_raw[b_idx_s_x_]) * 1;',
    mod$model_file,
    fixed = TRUE
  )))

  # Repeat a check for decreasing functions
  mod <- mvgam(
    y ~ z + s(x, bs = 'mod', k = 18),
    data = mod_data,
    family = gaussian(),
    run_model = FALSE
  )

  # The smooth should be an MOD class
  expect_true(inherits(mod$mgcv_model$smooth[[1]], 'mod.smooth'))

  # The coefficients should be fixed to be non-positive
  expect_true(any(grepl(
    'b[b_idx_s_x_] = abs(b_raw[b_idx_s_x_]) * -1;',
    mod$model_file,
    fixed = TRUE
  )))
})

test_that("monotonic for process models working properly", {
  mod <- mvgam(
    y ~ 0,
    trend_formula = ~ z + s(x, bs = 'moi', k = 18),
    trend_model = RW(),
    data = mod_data,
    family = gaussian(),
    run_model = FALSE
  )

  # Monotonic indices should be in the model_data
  expect_true("b_trend_idx_s_x_" %in% names(mod$model_data))

  # The smooth should be an MOI class
  expect_true(inherits(mod$trend_mgcv_model$smooth[[1]], 'moi.smooth'))

  # The coefficients should be fixed to be non-negative
  expect_true(any(grepl(
    'b_trend[b_trend_idx_s_x_] = abs(b_raw_trend[b_trend_idx_s_x_]) * 1;',
    mod$model_file,
    fixed = TRUE
  )))

  # And for decreasing
  mod <- mvgam(
    y ~ 0,
    trend_formula = ~ z + s(x, bs = 'mod', k = 18),
    trend_model = RW(),
    data = mod_data,
    family = gaussian(),
    run_model = FALSE
  )

  # The smooth should be an MOD class
  expect_true(inherits(mod$trend_mgcv_model$smooth[[1]], 'mod.smooth'))

  # The coefficients should be fixed to be non-positive
  expect_true(any(grepl(
    'b_trend[b_trend_idx_s_x_] = abs(b_raw_trend[b_trend_idx_s_x_]) * -1;',
    mod$model_file,
    fixed = TRUE
  )))
})
nicholasjclark/mvgam documentation built on April 17, 2025, 9:39 p.m.