tests/testthat/test-model_def.R

context("Model definition unit tests")

# Random number generation -----------------------------------------------------
alpha <- matrix(c(75, 25, 33, 67), byrow = TRUE, ncol = 2)
colnames(alpha) <- rownames(alpha) <- c("A", "B")
params <- list(
  alpha = alpha,
  gamma_mean = c(A = 900, B = 1500, C = 2000),
  normal_mean = c(A = 5, B = 10, C = 3),
  unif_min= 2,
  unif_max = 3
)

rng_def <- define_rng({
  beta_mean <- c(.2, .8, 0)
  beta_sd <- c(0, .1, 0)
  list( 
    dir1 = dirichlet_rng(alpha), 
    dir2 = dirichlet_rng(unname(alpha)),
    dir3 = dirichlet_rng(unname(alpha), names = alpha_names),
    gamma = gamma_rng(mean = gamma_mean,
                      sd = gamma_mean), 
    beta = beta_rng(mean = beta_mean,
                   sd = beta_sd,
                   names = beta_colnames),
    unif_vec = uniform_rng(min = unif_min, max = unif_max),
    unif_dt = uniform_rng(min = c(2, 3), max = c(4, 5)),
    normal = normal_rng(mean = normal_mean,
                       sd = rep(0, 3)),
    mvnorm = multi_normal_rng(mu = c(5, 10),
                             Sigma = matrix(c(10, 3, 3, 2) , nrow = 2, ncol = 2)),
    fixed_vec = fixed(est = 2),
    fixed_dt = fixed(est = c(2, 3)),
    custom_vec = custom(x = c(1L, 2L, 3L)),
    custom_dt = custom(x = matrix(1:4, nrow = 2, ncol = 2))
  )
}, n = 2, beta_colnames = c("A", "B", "C"), 
   alpha_names = tpmatrix_names(c("A", "B"), prefix = "")) 
rng <- eval_rng(x = rng_def, params)

test_that("eval_rng() runs without error", {
  expect_true(inherits(rng, "eval_rng"))
})

test_that("Can add elements to eval_rng() objects", {
  rng2 <- c(rng, list(z = 2))
  expect_true(inherits(rng2, "eval_rng"))
  expect_equal(rng2$z, 2)
  expect_equal(length(rng2), length(rng) + 1)
  expect_equal(attr(rng, "n"), attr(rng2, "n"))
  
  rng3 <- c(rng, z = 2)
  expect_equal(rng2, rng3)
})
 
test_that("eval_rng has correct number of samples", {
  n <- sapply(rng, function (z) if (length(dim(z)) < 2) length(z) else nrow(z))
  expect_true(all(n == rng_def$n))
})

test_that("dirichlet_rng has correct column names", {
  expect_equal(colnames(rng$dir1), rng_def$alpha_names)
  expect_equal(colnames(rng$dir2), tpmatrix_names(c("s1", "s2"), prefix = ""))
  expect_equal(colnames(rng$dir3), rng_def$alpha_names)
})

test_that("uv_rng() produces correct random variates for each column", {
  expect_equal(c(as.matrix(rng$normal)), 
               as.numeric(rep(params$normal_mean, each = rng_def$n)))
})

test_that("uv_rng() produces correct vector or data.table", {
  expect_true(inherits(rng$unif_vec, "numeric"))
  expect_true(inherits(rng$unif_dt, "data.table"))
})

test_that("mom_fun_rng produces fixed samples is sd == 0", {
  expect_true(all(rng$beta$A == .2))
  expect_true(all(rng$beta$C == 0))
  expect_equal(which(colnames(rng$beta) == "A"), 1)
  expect_equal(which(colnames(rng$beta) == "C"), 3)
})

test_that("fixed() produces vector or data.table", {
  expect_true(inherits(rng$fixed_vec, "numeric"))
  expect_true(inherits(rng$fixed_dt, "data.table"))
})

test_that("custom() produces vector or data.table", {
  expect_true(inherits(rng$custom_vec, "integer"))
  expect_true(inherits(rng$custom_dt, "data.table"))
})

test_that("custom() produces correct column names", {
  rng_def <- define_rng({
    custom3 <- custom(x = matrix(1:4, ncol = 2, nrow = 2))
    names(custom3) <- c("A", "B")
    list(
      custom1 = custom(x = matrix(1:4, ncol = 2, nrow = 2)),
      custom2 = custom(x = matrix(1:4, ncol = 2, nrow = 2), 
                       names = c("c1", "c2")),
      custom3 = custom3
    )
  }, n = 2)
  rng <- eval_rng(rng_def)
  expect_equal(colnames(rng$custom1), c("v1", "v2"))
  expect_equal(colnames(rng$custom2), c("c1", "c2"))
  expect_equal(colnames(rng$custom3), c("A", "B"))
})

test_that("custom() produces a warning if n > n_samples", {
  rng_def <- define_rng({
    list(
      custom = custom(x = 1)
    )
  }, n = 2)
  expect_warning(eval_rng(rng_def),
                 paste0("The number of requested draws for the probabilistic ",
                        "sensitivity analysis (PSA), 'n', is larger than the number ",
                        "of previously sampled values from the probability ",
                        "distribution of interest. Samples for the PSA have ",
                        "consequently been drawn with replacement."), fixed = TRUE)
})

test_that("custom() works with n = 1", {
  rng_def <- define_rng({
    list(
      custom = custom(matrix(1:4, ncol = 2, nrow = 2))
    )
  }, n = 1)
  expect_equal(colnames(eval_rng(rng_def)$custom), c("v1", "v2"))
})

test_that("Column names for multi-parameter RNG is as expcted", {
  expect_equal(colnames(rng$gamma), names(params$gamma_mean))
  expect_equal(colnames(rng$beta), rng_def$beta_colnames)
  expect_equal(colnames(rng$mvnorm), c("v1", "v2"))
})

test_that("multi_normal_rng() returns correct output when n = 1", {
  fun <- function(n, m = 0, V = 1){
    define_rng({ 
      list(x = multi_normal_rng(mu = m, Sigma = V))
    }, n = n, m = m, V = V)
  }
  expect_true(inherits(eval_rng(fun(1))$x, "numeric"))
  expect_true(
    inherits(eval_rng(fun(1, m = c(0, 0), V =  matrix(c(10,3,3,2),2,2)))$x,
             "data.table")
  )
})

test_that("define_rng() must return a list", {
  rng_def <- define_rng({
    data.frame(2)
  })
  expect_error(eval_rng(rng_def),
               "define_rng() must return a list.", fixed = TRUE)
})

test_that("define_rng() has incorrect number of samples", {
  rng_def <- define_rng({
    list(
      x = gamma_rng(mean = c(10, 10),
                    sd = c(1, 1)),
      y = c(1, 2)
    )
  }, n = 3)
  expect_error(eval_rng(rng_def, check = TRUE),
               paste0("The number of samples produced by define_rng() must be ",
               "equal to n unless a scalar (of length 1) is returned."), 
               fixed = TRUE)
})

# Concatenate and as.list methods for random number generation -----------------
test_that("concatenate method for eval_rng objects", {
  p <- c(rng, new1 = 2, new2 = 3)
  expect_equal(length(p), length(rng) + 2)
  expect_true(all(c("new1", "new2") %in% names(p)))
  expect_equal(p$new1, 2)
  expect_equal(attr(p, "n"), attr(rng, "n"))
  expect_equal(attr(p, "checked"), FALSE)
})

test_that("as.list method for eval_rng objects", {
  l <- as.list(rng)
  expect_true(all(names(l) == names(rng)))
  expect_equal(class(l), "list")
})

# Summary and print methods for random number generation -----------------------
test_that( "summary.eval_rng() works as expected", {
  s <- summary(rng)
  expect_equal(colnames(s), c("param", "mean", "sd", "2.5%", "97.5%"))
  expect_true(!any(grepl("\\.", s$param)))
})

test_that( "summary.eval_rng() uses sep argument", {
  s <- summary(rng, sep = ".")
  expect_true(any(grepl("\\.", s$param)))
})

test_that( "summary.eval_rng() uses probs argument", {
  s <- summary(rng, probs = .5)
  expect_equal(colnames(s), c("param", "mean", "sd", "50%"))
})

test_that( "print.eval_rng() works as expected", {
  expect_output(print(rng), "A summary of the \"eval_rng\" object:")
})

# Model definition works as expected -------------------------------------------
# Setup an example
## Data
strategies <- data.table(strategy_id = 1:2,
                         strategy_name = c("S1", "S2"))
patients <- data.table(patient_id = 1)
hesim_dat <- hesim_data(strategies = strategies,
                        patients = patients)
data <- expand(hesim_dat)

## Random number generation
rng_def <- define_rng({
  alpha <- matrix(c(1251, 350, 116, 17,
                    0, 731, 512, 15,
                    0, 0, 1312, 437,
                    0, 0, 0, 469),
                  nrow = 4, byrow = TRUE)
  rownames(alpha) <- colnames(alpha) <- c("A", "B", "C", "D")
  list(
    p_mono = dirichlet_rng(alpha),
    rr_s2 = .509,
    utility = 1,
    c_s1 = 2278,
    c_s2 = 4500,
    c_med = gamma_rng(mean = c(A = 2756, B = 3052, C = 9007),
                     sd = c(A = 2756, B = 3052, C = 9007))
  )
}, n = 2)

## Helper function
test_eval_model <- function(tparams_def, rng_def){
  model_def <- define_model(
    tparams_def = tparams_def,
    rng_def = rng_def
  )
  return(eval_model(model_def, data))
}

# Test
test_that("define_tparams() can be a list", {
  tparams_def1 <- define_tparams({
    list(
      costs = list(medical = c_med)
    )
  })
  tparams_def2 <- define_tparams({
    list(
      costs = list(drug = ifelse(strategy_name == "S1", c_s1, c_s2))
    )
  })  
  model_def <- define_model(
    tparams_def = list(tparams_def1, tparams_def2),
    rng_def = rng_def,
    n_states = 4
  )
  expect_equal(names(eval_model(model_def, data)$costs), c("medical", "drug"))
})

test_that("define_model() works with rng_def = NULL", {
  rng <- eval_rng(rng_def) 
  tparams_def <- define_tparams({
    list(tpmatrix = tpmatrix(1, 0, 0, 1))
  })
  model_def <- define_model(tparams_def, rng_def = NULL, params = rng)
  m <- eval_model(model_def, data)
  
  expect_equivalent(m$tpmatrix, tpmatrix(1, 0, 0, 1))
  rdata <- data[rep(1:nrow(data), rng_def$n)]
  expect_equal(m$id[[1]]$strategy_id, rdata$strategy_id)
  expect_equal(m$id[[1]]$patient_id, rdata$patient_id)
  expect_equal(m$id[[1]]$sample, rep(1:rng_def$n, each = nrow(data)))
})

# Model definition throws errors -----------------------------------------------
test_that( "define_rng() returns list elements of the right class", {
  error_msg <- paste0("Each element returned by define_rng() must either be ", 
                      "a vector or a tabular object.")
  
  # Error if element is a list
  rng_def <- define_rng({list(list(y = 3))})
  tparams_def <- define_tparams({
    list(z = 3)
  })
  expect_error(test_eval_model(tparams_def, rng_def),
               error_msg, fixed = TRUE)
  
  # Error if element is a 3D array
  rng_def <- define_rng({list(y = array(1, dim = c(1, 1, 1)))})
  expect_error(test_eval_model(tparams_def, rng_def),
               error_msg, fixed = TRUE)
  
  # Should work with a matrix
  rng_def <- define_rng({list(y = matrix(1))})
  tparams_def <- define_tparams({
    list(tpmatrix = tpmatrix(1, 0, 0, 1))
  })
  mod <- test_eval_model(list(tparams_def), rng_def)
  expect_equivalent(mod$tpmatrix, tpmatrix(1, 0, 0, 1))
})

test_that("define_model() requires either params or rng_def to be non NULL", {
  expect_error(
    define_model(
      tparams_def = 2, rng_def = NULL, params = NULL
    ),
    "'rng_def' and 'params' cannot both be NULL."
  )
})

test_that( "eval_tparams() must return a list", {
  tparams_def <- define_tparams({
    data.frame(2)
  })
  expect_error(test_eval_model(tparams_def, rng_def),
               "define_tparams() must return a list", fixed = TRUE)
})

test_that( "tpmatrix() must be square", {
  tparams_def <- define_tparams({
    list(
      tpmatrix = tpmatrix(
        C, p_mono$A_B, p_mono$A_C
      )
    )
  })
  expect_error(test_eval_model(tparams_def, rng_def),
               "tpmatrix() must be a square matrix", fixed = TRUE)
})

test_that("tpmatrix in define_tparams() must be square", {
  tparams_def <- define_tparams({
    list(
      tpmatrix = cbind(
        1 - p_mono$A_B - p_mono$A_C,
        p_mono$A_B, 
        p_mono$A_C
      )
    )
  })
  expect_error(test_eval_model(tparams_def, rng_def),
               "tpmatrix in define_tparams() must be a square matrix",
               fixed = TRUE)
})

test_that( "Number of states cannot be NULL", {
  tparams_def <- define_tparams({
    list(utility = utility)
  })
  expect_error(test_eval_model(tparams_def, rng_def),
               "'n_states' cannot be NULL.")
})

test_that( "Number of states in state value models must be correct", {
  tparams_def <- define_tparams({
    # Costs
    list(
      tpmatrix = tpmatrix(
        C, p_mono$A_B, p_mono$A_C,
        0, C, p_mono$A_D,
        0, 0, 1
      ),
      costs = list(medical = c_med),
      utility = utility
    )
  })
  expect_error(test_eval_model(tparams_def, rng_def),
               paste0("The number of columns in each element of 'costs' must ",
                      "equal 'n_states' - 1."),
               fixed = TRUE)
  
  # Utility
  tparams_def <- define_tparams({
    # Costs
    list(utility = cbind(1, 1)
    )
  })
  model_def <- define_model(
    tparams_def = tparams_def,
    rng_def = rng_def,
    n_states = 4
  )
  expect_error(eval_model(model_def, data),
               paste0("The number of columns in 'utility' must ",
                      "equal 'n_states' - 1."),
               fixed = TRUE)
  
})

test_that("costs in define_tparams() must be a list", {
  tparams_def <- define_tparams({
    list(
      costs = c_med
    )
  })
  model_def <- define_model(
    tparams_def = tparams_def,
    rng_def = rng_def,
    n_states = 3
  )
  expect_error(eval_model(model_def, data),
               "The 'costs' element returned by define_tparams() must be a list",
               fixed = TRUE)
})

Try the hesim package in your browser

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

hesim documentation built on Sept. 4, 2022, 1:06 a.m.