-
Notifications
You must be signed in to change notification settings - Fork 3
Extend normal outcome functions to two-sided decisions #28
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
base: develop
Are you sure you want to change the base?
Changes from all commits
740d8cd
1cb3052
65a3676
e50a012
04cf33e
45bc029
3edbdea
0a3fd9a
c6c0e78
e54313a
fece330
a78941d
61f3948
66dbdda
63a468f
55ba904
5b87a4c
8bb70ef
86b80d7
66da991
225a604
711b59d
51b3875
a81b407
995d7b8
d59b8ef
74dc200
028dc93
21b4112
4bb97ee
791e46d
f0329a7
0476813
721b28e
096829d
d107581
7593ee1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,3 +37,6 @@ src/stan_files/.*\.hpp$ | |
| ^Makefile$ | ||
| ^LICENSE$ | ||
| ^vignettes/articles$ | ||
| ^\.vscode$ | ||
| ^[.]?air[.]toml$ | ||
| ^\.lintr$ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| linters: linters_with_defaults( | ||
| line_length_linter(120), | ||
| object_name_linter = NULL, | ||
| commented_code_linter = NULL | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,12 @@ | |
| #' @param pc Vector of critical cumulative probabilities. | ||
| #' @param qc Vector of respective critical values. Must match the length of `pc`. | ||
| #' @param lower.tail Logical; if `TRUE` (default), probabilities | ||
| #' are \eqn{P(X \leq x)}, otherwise, \eqn{P(X > x)}. | ||
| #' are \eqn{P(X \leq x)}, otherwise, \eqn{P(X > x)}. Either length 1 or same | ||
| #' length as `pc`. | ||
danielinteractive marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #' @param x Two-sided decision function. | ||
| #' | ||
| #' @details The function creates a one-sided decision function which | ||
| #' @details For `lower.tail` being either `TRUE` or `FALSE`, | ||
| #' the function creates a one-sided decision function which | ||
| #' takes two arguments. The first argument is expected to be a mixture | ||
| #' (posterior) distribution. This distribution is tested whether it | ||
| #' fulfills all the required threshold conditions specified with the | ||
|
|
@@ -25,6 +28,10 @@ | |
| #' | ||
| #' \deqn{\Pi_i H_i(P(\theta \leq q_{c,i}) - p_{c,i} ).} | ||
| #' | ||
| #' For the case of a boolen vector given to `lower.tail` the | ||
| #' direction of each decision aligns respectively, and a two-sided | ||
| #' decision function is created. | ||
| #' | ||
| #' When the second argument is set to `TRUE` a distance metric is | ||
| #' returned component-wise per defined condition as | ||
| #' | ||
|
|
@@ -36,7 +43,9 @@ | |
| #' | ||
| #' @family design1S | ||
| #' | ||
| #' @return The function returns a decision function which takes two | ||
| #' @return The function returns a decision function (of class | ||
| #' `decision1S` for one-sided, and of class `decision1S_2sided` | ||
| #' for two-sided decisions) which takes two | ||
danielinteractive marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #' arguments. The first argument is expected to be a mixture | ||
| #' (posterior) distribution which is tested if the specified | ||
| #' conditions are met. The logical second argument determines if the | ||
|
|
@@ -45,6 +54,11 @@ | |
| #' log-space, i.e. the distance is 0 at the decision boundary, | ||
| #' negative for a 0 decision and positive for a 1 decision. | ||
| #' | ||
| #' For two-sided decision functions, the two components can be | ||
| #' extracted with functions [lower()] and [upper()]. The distance | ||
| #' as calculated by the decision function is returned as a list with | ||
| #' components `lower` and `upper`. | ||
| #' | ||
| #' @references Neuenschwander B, Rouyrre N, Hollaender H, Zuber E, | ||
| #' Branson M. A proof of concept phase II non-inferiority | ||
| #' criterion. *Stat. in Med.*. 2011, 30:1618-1627 | ||
|
|
@@ -53,7 +67,7 @@ | |
| #' | ||
| #' # see Neuenschwander et al., 2011 | ||
| #' | ||
| #' # example is for a time-to-event trial evaluating non-inferiority | ||
| #' # example is for a time-to-event trial evaluating non-inferiority (NI) | ||
| #' # using a normal approximation for the log-hazard ratio | ||
| #' | ||
| #' # reference scale | ||
|
|
@@ -99,9 +113,52 @@ | |
| #' # here with HR of 0.8 for 40 events | ||
| #' decComb(postmix(flat_prior, m = log(0.8), n = 40)) | ||
| #' | ||
| #' # A two-sided decision function can be useful to determine if | ||
| #' # certain intermediate (i.e. neither "go" nor "stop") decisions | ||
| #' # are to be made based on the posterior distribution. | ||
| #' # For example, in the above situation we might have an intermediate | ||
| #' # scenario where the trial is significant for non-inferiority but | ||
| #' # the mean estimate is in an intermediate range, say between theta_c | ||
| #' # theta_f: | ||
| #' theta_f <- 0.3 | ||
| #' decCombIntermediate <- decision1S( | ||
| #' c(1 - alpha, 0.5, 0.8), | ||
| #' c(theta_ni, theta_c, theta_f), | ||
| #' lower.tail = c(TRUE, FALSE, TRUE) | ||
| #' ) | ||
| #' # Not fulfilled for the prior: | ||
| #' decCombIntermediate(flat_prior) | ||
| #' # But for a hypothetical trial outcome with HR 1.2 and 300 events: | ||
| #' decCombIntermediate(postmix(flat_prior, m = log(1.2), n = 300)) | ||
| #' | ||
| #' @export | ||
| decision1S <- function(pc = 0.975, qc = 0, lower.tail = TRUE) { | ||
| assert_that(length(pc) == length(qc)) | ||
| assert_numeric(pc) | ||
| assert_numeric(qc, len = length(pc)) | ||
| assert_logical(lower.tail) | ||
| assert_true(length(lower.tail) == 1L || length(lower.tail) == length(pc)) | ||
| lower.tail <- scalar_if_same(lower.tail) | ||
|
|
||
| is_two_sided <- length(lower.tail) > 1 | ||
|
|
||
| if (is_two_sided) { | ||
| return(create_decision1S_2sided(pc, qc, lower.tail)) | ||
| } else { | ||
| return(create_decision1S_1sided(pc, qc, lower.tail)) | ||
| } | ||
| } | ||
|
|
||
| #' @keywords internal | ||
| scalar_if_same <- function(x) { | ||
| if (length(x) > 1 && all(x == x[1])) { | ||
| return(x[1]) | ||
| } | ||
| x | ||
| } | ||
|
|
||
| #' Internal Constructor for 1 Sample One-sided Decision Function | ||
| #' @keywords internal | ||
| create_decision1S_1sided <- function(pc, qc, lower.tail) { | ||
| lpc <- log(pc) | ||
| fun <- function(mix, dist = FALSE) { | ||
| test <- pmix(mix, qc, lower.tail = lower.tail, log.p = TRUE) - lpc | ||
|
|
@@ -113,21 +170,75 @@ decision1S <- function(pc = 0.975, qc = 0, lower.tail = TRUE) { | |
| attr(fun, "pc") <- pc | ||
| attr(fun, "qc") <- qc | ||
| attr(fun, "lower.tail") <- lower.tail | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since there is no lower/upper attribute, one would get null when requesting these. Wouldn't it make more sense to have lower/upper attribute being defined for whatever the 1S is for... the other one should be set to NULL, which probably does not need defintion.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand, that would also be possible. So far I was trying to be fully backwards compatible with this enhancement, i.e. I did not want to change the current one-sided objects or structure. Therefore I did not try to add attributes here or change the overall class structure for one-sided. If you think that is not important, then I can try to make the one-sided and two-sided more consistent with each other (i.e. closer to the more general two-sided) |
||
| class(fun) <- c("decision1S", "function") | ||
| fun | ||
| } | ||
|
|
||
| #' Internal Constructor for 1 Sample Two-sided Decision Function | ||
| #' @keywords internal | ||
| create_decision1S_2sided <- function(pc, qc, lower.tail) { | ||
| use_lower <- which(lower.tail) | ||
| use_upper <- which(!lower.tail) | ||
| assert_true(length(use_lower) > 0 && length(use_upper) > 0) | ||
|
|
||
| lower_part <- create_decision1S_1sided(pc[use_lower], qc[use_lower], TRUE) | ||
| upper_part <- create_decision1S_1sided(pc[use_upper], qc[use_upper], FALSE) | ||
|
|
||
| fun <- function(mix, dist = FALSE) { | ||
| dl <- lower_part(mix, dist) | ||
| du <- upper_part(mix, dist) | ||
| if (dist) { | ||
| return(list(lower = dl, upper = du)) | ||
| } | ||
| as.numeric(all(dl > 0) && all(du > 0)) | ||
| } | ||
| attr(fun, "lower") <- lower_part | ||
| attr(fun, "upper") <- upper_part | ||
|
|
||
| class(fun) <- c("decision1S_2sided", "function") | ||
| fun | ||
| } | ||
|
|
||
| #' @rdname decision1S | ||
| #' @export | ||
| print.decision1S <- function(x, ...) { | ||
| cat("1 sample decision function\n") | ||
| cat("Conditions for acceptance:\n") | ||
| lower <- function(x) { | ||
| assert_multi_class(x, c("decision1S_2sided", "decision2S_2sided")) | ||
| attr(x, "lower") | ||
| } | ||
|
|
||
| #' @rdname decision1S | ||
| #' @export | ||
| upper <- function(x) { | ||
| assert_multi_class(x, c("decision1S_2sided", "decision2S_2sided")) | ||
| attr(x, "upper") | ||
| } | ||
|
|
||
| #' @keywords internal | ||
| print_decision1S_1sided <- function(x) { | ||
| qc <- attr(x, "qc") | ||
| pc <- attr(x, "pc") | ||
| low <- attr(x, "lower.tail") | ||
| cmp <- ifelse(low, "<=", ">") | ||
| for (i in seq_along(qc)) { | ||
| cat(paste0("P(theta ", cmp, " ", qc[i], ") > ", pc[i], "\n")) | ||
| } | ||
| cat(paste0("P(theta ", cmp, " ", qc, ") > ", pc, "\n"), sep = "") | ||
| } | ||
|
|
||
| #' @export | ||
| print.decision1S <- function(x, ...) { | ||
| cat("1 sample decision function\n") | ||
| cat("Conditions for acceptance:\n") | ||
| print_decision1S_1sided(x) | ||
| invisible(x) | ||
| } | ||
|
|
||
| #' @export | ||
| print.decision1S_2sided <- function(x, ...) { | ||
| cat("1 sample decision function (two-sided)\n") | ||
| cat("Conditions for acceptance:\n") | ||
| cat("Lower tail conditions:\n") | ||
| print_decision1S_1sided(lower(x)) | ||
| cat("Upper tail conditions:\n") | ||
| print_decision1S_1sided(upper(x)) | ||
| invisible(x) | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.