Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit tests #13

Merged
merged 47 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5b98624
draft unit tests [skip ci]
stevegbrooks Nov 6, 2023
e9bdac4
fix bmcpmod tests
stevegbrooks Nov 6, 2023
7133056
update bmcpmod tests
stevegbrooks Nov 6, 2023
42bcfa6
delete bad tests
stevegbrooks Nov 6, 2023
2a92c95
input validation for assessDesign
stevegbrooks Nov 6, 2023
5025fe5
initial checkmate setup and implementation
Xyarz Nov 6, 2023
56b2ed0
further checkmate calls added
Xyarz Nov 6, 2023
908f80d
finished n_patients validaiton tests
stevegbrooks Nov 7, 2023
f2d29f1
use ints for patients in setup
stevegbrooks Nov 7, 2023
d5ff395
renamed test-BMCPMod.R
stevegbrooks Nov 7, 2023
0ea1c22
Merge branch 'unit_tests' into unit-tests
stevegbrooks Nov 7, 2023
7903440
Merge pull request #4 from stevegbrooks/unit-tests
Xyarz Nov 7, 2023
8dcaa00
finished assessDesign base case and param validation tests; added che…
stevegbrooks Nov 8, 2023
e6ac275
minor
stevegbrooks Nov 8, 2023
13d620c
Merge pull request #5 from stevegbrooks/unit_tests
Xyarz Nov 10, 2023
a88c0b9
minor changes
stevegbrooks Nov 17, 2023
96490ed
added more unit tests to BMCPMod.R; light edits and refactoring of si…
stevegbrooks Nov 17, 2023
ff0c35c
added test for BayesMCPi
stevegbrooks Nov 29, 2023
2b109a8
improved the BayesMCPi test
stevegbrooks Nov 29, 2023
7e8dc6c
added test for getPostProb
stevegbrooks Nov 29, 2023
b1eaec1
Merge branch 'unit_tests' of https://github.com/Boehringer-Ingelheim/…
stevegbrooks Nov 29, 2023
2e6eae6
added test for addModelWeights
stevegbrooks Nov 29, 2023
08773ca
added test for getGenAIC
stevegbrooks Nov 29, 2023
7ccb1b6
check to assert for getModelFits
stevegbrooks Nov 30, 2023
d213765
change back to check
Nov 30, 2023
34ba23d
fix modeling test
Nov 30, 2023
d42a8c4
using mockr for mock functions - fixed addSig test
Nov 30, 2023
b634911
test coverage > 90% for posterior.R
Nov 30, 2023
a6d9f41
minor
Nov 30, 2023
47b8861
minor
Nov 30, 2023
94d7b16
added test for getModelData
Nov 30, 2023
922ac4e
minor bug fixes
Xyarz Nov 30, 2023
e6868fa
merge
Xyarz Dec 11, 2023
35b7e7e
merge
Dec 14, 2023
970c0e6
merge fixes
Dec 14, 2023
74d48b5
Merge branch 'feature/restructure' of https://github.com/Boehringer-I…
Dec 15, 2023
d6eb38b
Merge branch 'feature/restructure' of https://github.com/Boehringer-I…
Dec 15, 2023
b5d3c77
minor change
Dec 20, 2023
ab1e6ad
merged with feature/restructure branch
Dec 20, 2023
4d22744
minor change in examples and test bug fixes
Dec 20, 2023
12729a9
merged with main and readded tests
Xyarz Dec 20, 2023
c0d4e2f
added testes and pushed test coverage above 80 percentage
Xyarz Dec 20, 2023
ca0b89c
minor
Xyarz Dec 20, 2023
8acb9a2
minor fix in vignette
Xyarz Dec 21, 2023
eef01bf
change of getPrior helper in vignettes
Xyarz Dec 21, 2023
a2e0dd3
checkmate fix
Xyarz Dec 21, 2023
cf0e8dd
fix merge problems
Xyarz Dec 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: BayesianMCPMod
Title: Bayesian MCPMod
Version: 0.1.3-3
Version: 0.1.4-3
Authors@R: c(
person("Boehringer Ingelheim Pharma GmbH & Co. KG",
role = c("cph", "fnd")),
Expand All @@ -11,8 +11,8 @@ Authors@R: c(
role = "aut",
email = "[email protected]"),
person("Steven", "Brooks",
role = "aut",
email = "lars.andersen@boehringer-ingelheim.com"),
role = "ctb",
email = "steven.brooks@boehringer-ingelheim.com"),
person("Sebastian", "Bossert",
role = "aut",
email = "[email protected]")
Expand All @@ -32,7 +32,8 @@ Imports:
clinDR,
knitr,
rmarkdown,
dplyr
dplyr,
checkmate
Suggests:
testthat (>= 3.0.0)
Depends:
Expand Down
37 changes: 36 additions & 1 deletion R/BMCPMod.R
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
#' @title assessDesign
#'.
#' @description This function performs simulation based trial design evaluations for a set of specified dose-response models

Check warning on line 3 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=3,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 124 characters.
#'
#' @param n_patients Vector specifying the planned number of patients per dose group

Check warning on line 5 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=5,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 84 characters.
#' @param mods An object of class "Mods" as specified in the DoseFinding package.

Check warning on line 6 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=6,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 81 characters.
#' @param prior_list a prior_list object specifying the utilized prior for the different dose groups

Check warning on line 7 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=7,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 101 characters.

Check warning on line 7 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=7,col=101,[trailing_whitespace_linter] Trailing whitespace is superfluous.
#' @param sd a positive value, specification of assumed sd

Check warning on line 8 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=8,col=59,[trailing_whitespace_linter] Trailing whitespace is superfluous.
#' @param n_sim number of simulations to be performed
#' @param alpha_crit_val (unadjusted) critical value to be used for the MCT testing step. Passed to the getCritProb function for the calculation of adjusted critical values (on the probability scale). Default is 0.05.

Check warning on line 10 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=10,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 217 characters.
#' @param simple boolean variable, defining whether simplified fit will be applied. Passed to the getModelFits function. Default FALSE.

Check warning on line 11 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=11,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 135 characters.
#' @param reestimate boolean variable, defining whether critical value should be calculated with re-estimated contrasts (see getCritProb function for more details). Default FALSE

Check warning on line 12 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=12,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 178 characters.
#' @param contr Allows specification of a fixed contrasts matrix. Default NULL
#' @param dr_means a vector, allows specification of individual (not model based) assumed effects per dose group. Default NULL

Check warning on line 14 in R/BMCPMod.R

View workflow job for this annotation

GitHub Actions / lint

file=R/BMCPMod.R,line=14,col=81,[line_length_linter] Lines should not be more than 80 characters. This line is 127 characters.
#'
#' @return returns success probabilities for the different assumed dose-response shapes, attributes also includes information around average success rate (across all assumed models) and prior Effective sample size
#'
Expand All @@ -24,7 +24,6 @@
#' DG_2 = RBesT::mixnorm(comp1 = c(w = 1, m = 1.2, s = 11), sigma = 2) ,
#' DG_3 = RBesT::mixnorm(comp1 = c(w = 1, m = 1.3, s = 11), sigma = 2) ,
#' DG_4 = RBesT::mixnorm(comp1 = c(w = 1, m = 2, s = 13) ,sigma = 2))
#'
#' n_patients <- c(40,60,60,60,60)
#' success_probabilities <- assessDesign(
#' n_patients = n_patients,
Expand Down Expand Up @@ -53,6 +52,15 @@

) {

checkmate::assert_vector(n_patients, len = length(attr(mods, "doses")), any.missing = FALSE)
checkmate::check_class(mods, classes = "Mods")
checkmate::check_list(prior_list, names = "named", len = length(attr(mods, "doses")), any.missing = FALSE)
# sensitive to how DoseFinding labels their attributes for "Mods" class
checkmate::check_double(n_sim, lower = 1, upper = Inf)
checkmate::check_double(alpha_crit_val, lower = 0, upper = 1)
checkmate::check_logical(simple)
# TODO: check that prior_list has 'sd_tot' attribute, and that it's numeric

dose_levels <- attr(mods, "doses")

data <- simulateData(
Expand Down Expand Up @@ -172,6 +180,11 @@

) {

checkmate::check_class(mods, classes = "Mods")
checkmate::check_double(dose_levels, lower = 0, any.missing = FALSE, len = length(attr(mods, "doses")))
checkmate::check_double(dose_weights, any.missing = FALSE, len = length(attr(mods, "doses")))
checkmate::check_list(prior_list, names = "named", len = length(attr(mods, "doses")), any.missing = FALSE)

# frequentist & re-estimation
if (!is.null(se_new_trial) &
is.null(dose_weights) & is.null(prior_list) & is.null(sd_posterior)) {
Expand Down Expand Up @@ -267,6 +280,11 @@

) {

checkmate::check_class(mods, classes = "Mods")
checkmate::check_double(dose_levels, lower = 0, any.missing = FALSE, len = length(dose_weights))
checkmate::check_double(dose_weights, any.missing = FALSE, len = length(dose_levels))
checkmate::check_double(alpha_crit_val, lower = 0, upper = 1)

contr <- getContr(mods = mods,
dose_levels = dose_levels ,
dose_weights = dose_weights,
Expand Down Expand Up @@ -329,6 +347,17 @@

) {

checkmate::check_class(posterior_list, "postList")
checkmate::check_class(contr, "optContr")
checkmate::check_class(crit_prob_adj, "numeric")
checkmate::check_logical(simple)

if (inherits(posterior_list, "postList")) {

posterior_list <- list(posterior_list)

}

if (inherits(posterior_list, "postList")) {

posterior_list <- list(posterior_list)
Expand Down Expand Up @@ -453,6 +482,12 @@
crit_prob_adj

) {

checkmate::check_class(posterior_list, "postList")
checkmate::check_class(contr, "optContr")
checkmate::check_class(crit_prob_adj, "numeric")
checkmate::check_numeric(crit_prob_adj, lower = 0, upper = Inf)

if (inherits(posterior_list, "postList")) {

posterior_list <- list(posterior_list)
Expand Down
8 changes: 6 additions & 2 deletions R/modelling.R
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ getModelFits <- function (

) {

checkmate::check_list(models, any.missing = FALSE)
checkmate::check_double(dose_levels, lower = 0, any.missing = FALSE, len = length(models))
checkmate::check_class(posterior, "postList")
checkmate::check_logical(simple)
models <- unique(gsub("\\d", "", models))

getModelFit <- ifelse(simple, getModelFitSimple, getModelFitOpt)
Expand Down Expand Up @@ -137,7 +141,7 @@ getModelFitOpt <- function (
ub <- c(Inf, Inf, 1.5 * max(dose_levels), 0.5 * max(dose_levels))
expr_i <- quote(sum((post_combs$means[i, ] - (theta[1] + theta[2] / (1 + exp((theta[3] - dose_levels) / theta[4]))))^2 / (post_combs$vars[i, ])))},
{
stop (GENERAL$ERROR$MODEL_OPTIONS)})
stop ("error")})

simple_fit <- getModelFitSimple(
model = model,
Expand Down Expand Up @@ -247,7 +251,7 @@ predictModelFit <- function (
model_fit$coeffs["eMax"],
model_fit$coeffs["ed50"],
model_fit$coeffs["delta"])},
{stop(GENERAL$ERROR$MODEL_OPTIONS)})
{stop("error")})

return (pred_vals)

Expand Down
9 changes: 9 additions & 0 deletions R/plot.R
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ plot.modelFits <- function (
...

) {
checkmate::check_class(x, "modelFits")
checkmate::check_logical(gAIC)
checkmate::check_logical(avg_fit)
checkmate::check_logical(cr_intv)
checkmate::check_double(alpha_CrI, lower = 0, upper = 1)
checkmate::check_logical(cr_bands)
checkmate::check_double(alpha_CrB, lower = 0, upper = 1, len = 2)
checkmate::check_integer(n_bs_smpl, lower = 1, upper = Inf)
checkmate::check_string(acc_color, na.ok = TRUE)

plot_res <- 1e2
model_fits <- x
Expand Down
22 changes: 18 additions & 4 deletions R/posterior.R
Xyarz marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

#' @title getPosterior
#'
#' @description Either the patient level data or both mu_hat as well as sd_hat must to be provided. If patient level data is provided mu_hat and se_hat are calculated within the function using a linear model.
Expand Down Expand Up @@ -36,6 +35,12 @@ getPosterior <- function(
calc_ess = FALSE

) {
checkmate::check_data_frame(data, null.ok = TRUE)
checkmate::check_list(prior_list, names = "named", any.missing = FALSE)
checkmate::check_vector(mu_hat, any.missing = FALSE, null.ok = TRUE)
checkmate::check_double(mu_hat, null.ok = TRUE, lower = -Inf, upper = Inf)
checkmate::check_vector(se_hat, any.missing = FALSE, null.ok = TRUE)
checkmate::check_double(se_hat, null.ok = TRUE, lower = 0, upper = Inf)

if (!is.null(mu_hat) && !is.null(se_hat) && is.null(data)) {

Expand All @@ -55,7 +60,7 @@ getPosterior <- function(
stop ("Either 'data' or 'mu_hat' and 'se_hat' must not be NULL.")

}

if (length(posterior_list) == 1) {

posterior_list <- posterior_list[[1]]
Expand All @@ -75,9 +80,19 @@ getPosteriorI <- function(
calc_ess = FALSE

) {

checkmate::check_data_frame(data_i, null.ok = TRUE)
checkmate::check_list(prior_list, names = "named", any.missing = FALSE)
checkmate::check_vector(mu_hat, any.missing = FALSE, null.ok = TRUE)
checkmate::check_double(mu_hat, null.ok = TRUE, lower = -Inf, upper = Inf)
checkmate::check_vector(se_hat, any.missing = FALSE, null.ok = TRUE)
checkmate::check_double(se_hat, null.ok = TRUE, lower = 0, upper = Inf)

if (is.null(mu_hat) && is.null(se_hat)) {

checkmate::check_data_frame(data_i, null.ok = FALSE)
checkmate::assert_names(names(data_i), must.include = "response")
checkmate::assert_names(names(data_i), must.include = "dose")

anova_res <- stats::lm(data_i$response ~ factor(data_i$dose) - 1)
mu_hat <- summary(anova_res)$coefficients[, 1]
se_hat <- summary(anova_res)$coefficients[, 2]
Expand All @@ -92,7 +107,6 @@ getPosteriorI <- function(
} else {

stop ("Both mu_hat and se_hat must be provided.")

}

post_list <- mapply(RBesT::postmix, prior_list, m = mu_hat, se = se_hat,
Expand Down
9 changes: 8 additions & 1 deletion R/simulation.R
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ simulateData <- function(

) {

checkmate::check_vector(n_patients, any.missing = FALSE, len = length(dose_levels))
checkmate::check_double(dose_levels, lower = 0, any.missing = FALSE, len = length(n_patients))
checkmate::check_double(sd, len = 1, null.ok = FALSE, lower = 0, upper = Inf)
checkmate::check_class(mods, classes = "Mods")
checkmate::check_numeric(n_sim, lower = 0, upper = Inf, len = 1)
checkmate::check_string(true_model, null.ok = TRUE)

if (!is.null(true_model)) {

n_sim <- 1
Expand Down Expand Up @@ -106,7 +113,7 @@ getModelData <- function (

) {

model_data <- sim_data[, c("simulation", "dose", model_name)]
model_data <- sim_data[, c("simulation", "dose", model_name)]
colnames(model_data)[3] <- "response"

return (model_data)
Expand Down
1 change: 0 additions & 1 deletion man/assessDesign.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

156 changes: 156 additions & 0 deletions tests/testthat/setup.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#' @title getPriorList
#'
#' @param hist_data historical trial summary level data,
#' needs to be provided as a dataframe. Including information of the
#' estimates and variability.
#' @param dose_levels vector of the different doseage levels
#' @param dose_names character vector of dose levels,
#' default NULL and will be automatically created
#' based on the dose levels parameter.
#' @param robust_weight needs to be provided as a numeric
#' value for the weight of the robustification component
#'
getPriorList <- function (

hist_data,
dose_levels,
dose_names = NULL,
robust_weight

) {

checkmate::check_data_frame(hist_data)
checkmate::assert_double(dose_levels, lower = 0, any.missing = FALSE)
checkmate::check_string(dose_names, null.ok = TRUE)
checkmate::check_vector(dose_names, null.ok = TRUE, len = length(dose_levels))
checkmate::check_numeric(robust_weight, len = 1, null.ok = FALSE)

sd_tot <- with(hist_data, sum(sd * n) / sum(n))

gmap <- RBesT::gMAP(
formula = cbind(est, se) ~ 1 | trial,
weights = hist_data$n,
data = hist_data,
family = gaussian,
beta.prior = cbind(0, 100 * sd_tot),
tau.dist = "HalfNormal",
tau.prior = cbind(0, sd_tot / 4))

prior_ctr <- RBesT::automixfit(gmap)

prior_ctr <- suppressMessages(RBesT::robustify(
priormix = prior_ctr,
weight = robust_weight,
sigma = sd_tot))


prior_trt <- RBesT::mixnorm(
comp1 = c(w = 1, m = summary(prior_ctr)[1], n = 1),
sigma = sd_tot,
param = "mn")

prior_list <- c(list(prior_ctr),
rep(x = list(prior_trt),
times = length(dose_levels[-1])))

if (is.null(dose_names)) {
dose_names <- c("Ctr", paste0("DG_", seq_along(dose_levels[-1])))
}

names(prior_list) <- dose_names

return (prior_list)

}


getPostProb <- function (

contr_j, # j: dose level
post_combs_i # i: simulation outcome

) {

## Test statistic = sum over all components of
## posterior weight * normal probability distribution of
## critical values for doses * estimated mean / sqrt(product of critical values for doses)

## Calculation for each component of the posterior
contr_theta <- apply(post_combs_i$means, 1, `%*%`, contr_j)
contr_var <- apply(post_combs_i$vars, 1, `%*%`, contr_j^2)
contr_weights <- post_combs_i$weights

## P(c_m * theta > 0 | Y = y) for a shape m (and dose j)
post_probs <- sum(contr_weights * stats::pnorm(contr_theta / sqrt(contr_var)))

return (post_probs)

}

# Create minimal test case
n_hist_trials = 2

hist_data <- data.frame(
trial = seq(1, n_hist_trials, 1),
est = rep(1, n_hist_trials),
se = rep(1, n_hist_trials),
sd = rep(1, n_hist_trials),
n = rep(1, n_hist_trials)
)

n_patients <- c(2, 1)
dose_levels <- c(0, 2.5)
mean <- c(8, 12)
sd <- c(0.5, 0.8)

mods <- DoseFinding::Mods(
linear = NULL,
doses = dose_levels
)


prior_list <- getPriorList(
hist_data = hist_data,
dose_levels = dose_levels,
robust_weight = 0.5
)

n_sim = 1
alpha_crit_val = 0.05
simple = TRUE

data <- simulateData(
n_patients = n_patients,
dose_levels = dose_levels,
sd = sd,
mods = mods,
n_sim = n_sim
)

posterior_list <- getPosterior(
data = getModelData(data, names(mods)[1]),
prior_list = prior_list
)

contr_mat = getContr(
mods = mods,
dose_levels = dose_levels,
dose_weights = n_patients,
prior_list = prior_list
)

crit_pval = getCritProb(
mods = mods,
dose_levels = dose_levels,
dose_weights = n_patients,
alpha_crit_val = alpha_crit_val
)

# eval_design <- assessDesign(
# n_patients = n_patients,
# mods = mods,
# prior_list = prior_list,
# n_sim = n_sim,
# alpha_crit_val = alpha_crit_val,
# simple = TRUE
# )
Loading
Loading