diff --git a/NAMESPACE b/NAMESPACE index d2e3f0bd4..587092b8c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -37,6 +37,7 @@ S3method(validate,stan_data) export(Stack) export(add_class) export(analyse) +export(analysis_result) export(ancova) export(as_class) export(as_vcov) @@ -50,6 +51,7 @@ export(getStrategies) export(get_example_data) export(has_class) export(impute) +export(is.analysis_result) export(locf) export(longDataConstructor) export(method_approxbayes) @@ -72,6 +74,7 @@ import(R6) import(Rcpp) import(methods) importFrom(assertthat,assert_that) +importFrom(assertthat,has_attr) importFrom(glmmTMB,VarCorr) importFrom(glmmTMB,fixef) importFrom(glmmTMB,getME) diff --git a/R/analyse.R b/R/analyse.R index b313d74b5..e8bce04f2 100644 --- a/R/analyse.R +++ b/R/analyse.R @@ -32,12 +32,14 @@ #' mod_1 <- lm(data = dat, outcome ~ group) #' mod_2 <- lm(data = dat, outcome ~ group + covar) #' x <- list( -#' trt_1 = list( +#' analysis_result( +#' name = trt_1, #' est = coef(mod_1)[[group]], #' se = sqrt(vcov(mod_1)[group, group]), #' df = df.residual(mod_1) #' ), -#' trt_2 = list( +#' analysis_result( +#' name = trt_2, #' est = coef(mod_2)[[group]], #' se = sqrt(vcov(mod_2)[group, group]), #' df = df.residual(mod_2) @@ -349,7 +351,7 @@ print.analysis <- function(x, ...) { sprintf("Analysis Function: %s", x$fun_name), sprintf("Delta Applied: %s", !is.null(x$delta)), "Analysis Estimates:", - sprintf(" %s", names(x$results[[1]])), + as_ascii_table(analysis_info(x$results[[1]])), "" ) @@ -440,12 +442,13 @@ validate_analyse_pars <- function(results, pars) { ) assert_that( - length(names(results[[1]])) != 0, - all(vapply(results, function(x) !is.null(names(x)) & all(names(x) != ""), logical(1))), - msg = "Individual analysis results must be named lists" + length(results[[1]]) != 0, + all(vapply(results, function(Xs) + all(vapply(Xs, function(X) is.analysis_result(X), logical(1))), logical(1))), + msg = "Individual analysis result must be type of analysis_result" ) - results_names <- lapply(results, function(x) unique(names(x))) + results_names <- back_apply_at(results, function(x) x[['name']], 2) # get the "name" element of 2nd deepest level list which corresponds to analysis_result results_names_flat <- unlist(results_names, use.names = FALSE) results_names_count <- table(results_names_flat) @@ -487,3 +490,291 @@ validate_analyse_pars <- function(results, pars) { return(invisible(TRUE)) } + +#' Constructor of analysis result +#' +#' Construct an analysis result class object whose base type is a list +#' +#' @param name A character variable for the group name +#' @param est A double type numeric variable as the estimate +#' @param se A double type numeric variable as the standard error +#' @param df An integer type of numeric variable +#' @param meta A list type of variable as meta information +#' @details +#' - `se`, `df` and `meta` is optional +#' - `se` and `df` if given must be numeric values greater or equal to 0 +#' @return An object of "analysis_result" class +#' @examples +#' \dontrun{ +#' ana_res_obj <- analysis_result(name = 'trt', est = 1, se = 2, df = 3, meta = list(visit = 1)) +#' } +#' @export +analysis_result <- function (name, + est, + se = NULL, + df = NULL, + meta = NULL) { + + # constraints + is.numeric_or_NA <- make_chain(any, is.numeric, anyNA) + is.numeric_or_NA_or_NULL <- make_chain(any, is.numeric_or_NA, is.null) + is.list_or_NULL <- make_chain(any, is.list, is.null) + + # asssert type for required parameter (directly assert type) + assert_type(name, is.character) + assert_type(est, is.numeric) + + # assert type for optional parameter (always include NULL) + assert_type(se, is.numeric_or_NA_or_NULL) + assert_type(df, is.numeric_or_NA_or_NULL) + assert_type(meta, is.list_or_NULL) + + # assert length for required parameter + assert_anares_length(name, 1) + assert_anares_length(est, 1) + + # assert properties of optional parameters + if (!is.null(se) & !anyNA(se)) { + assert_anares_length(se, 1) + + assert_that( + se >= 0, + msg = "SE must be greater or equal to 0" + ) + } + + if (!is.null(df) & !anyNA(df)) { + assert_anares_length(df, 1) + + assert_that( + df >= 0, + msg = "DF must be greater or equal to 0" + ) + } + + value <- list(name = name, + est = est) + + # optional parameters + if (!is.null(se)) { + value[['se']] <- se + } + + if (!is.null(df)) { + value[['df']] <- df + } + + if (!is.null(meta)) { + value[['meta']] <- meta + } + + structure( + value, + meta = meta, + class = c("analysis_result", "list") + ) +} + +#' Convert object to analysis result class +#' +#' @param x The object to be converted to analysis_result class +#' @param ... Optional keywords parameters for adding missing elements to the object +#' @return An "analysis_result" class object with optionally updated elements +#' @examples +#' \dontrun{ +#' ana_res_obj <- as_analysis_result(list(est = 1, se = 2, df = 3), name = 'trt') +#' } +as_analysis_result <- function(x, ...) { + new_pars <- list(...) + + # coercion with generic function + x <- as.list(x) + + present <- ana_name_chker()('musthave_in_objnames') + + names_not_presented <- names(present(x))[!present(x)] + + # update list if required elements are not presented or if the provided name is an optional element of analysis_result object + updated_x <- x + for (name in names(new_pars)) { + if (name %in% names_not_presented | name %in% ana_name_chker()('optional')) { + updated_x[[name]] <- new_pars[[name]] + } + } + + # after updating check if all required elements are presented + assert_that(all(present(updated_x)), + msg = "Required parameters are not presented after updating") + + # order the list by names + ordered_x <- order_list_by_name(updated_x, ana_name_chker()('all')) + + # set attributes: meta & class + if ('meta' %in% names(ordered_x)) { + attr(ordered_x, 'meta') <- ordered_x[['meta']] + } + + as_class(ordered_x, c("analysis_result", "list")) +} + +#' Name checker for analysis_result object +#' +#' A higher order function returns an analysis name checker which is again a higher order function takes character vector as +#' type of dispatch message and returns selected check function or properties. +#' This function takes no argument. The point is to delay the evaluation and evaluate only when it is needed, similar idea as shiny ractive +#' @examples +#' \dontrun{ +#' anares_names_in_musthave <- ana_name_chker()('objnames_in_musthave') +#' musthave_in_anares_names <- ana_name_chker()('musthave_in_objnames') +#' musthave_names <- ana_name_chker()('musthave') +#' optional_names <- ana_name_chker()('optional') +#' all_names <- ana_name_chker()('all') +#' } +ana_name_chker <- function() namechecker('name', 'est', optional = c('se', 'df', 'meta')) + +#' Check if an object is in class analysis_result +#' +#' @param x Object to be checked +#' @return Logical value TRUE/FALSE +#' @details +#' This function does not only check the class attribute of the object. +#' It also checks constraints of the names of the elements in the list +#' @export +#' @importFrom assertthat has_attr +is.analysis_result <- function(x) { + + all( + has_attr(x, 'class'), + is.object(x), + 'analysis_result' %in% attr(x, 'class'), + typeof(x) == 'list', + all(ana_name_chker()('objnames_in_musthave')(x)), + all(ana_name_chker()('musthave_in_objnames')(x)) + ) +} + +#' Get printable analysis information from an example of analysis result +#' +#' @param example A subset of the result of the analysis object for getting enough info to print. It should not be the complete result of analysis object but a subset of it such as `anaObj$results[[1]]` +#' @param name_of_group A character variable for the name of group variable in the result of analysis which is defined from `analysis_result`. Default: `'name'` +#' @param name_of_meta A character variable for the name of meta data in the result of analysis which is defined from `analysis_result`. Default: 'meta' +#' @return A data.frame containing the information of the analysis result from the example +#' @examples +#' \dontrun{ +#' analysis_info(dat, name_of_group = 'name', name_of_meta = 'meta') +#' } +#' @importFrom assertthat has_attr +analysis_info <- function(example, name_of_group = 'name', name_of_meta = 'meta') { + + pars_no_meta <- list() + pars_with_meta <- list() + meta <- list() + var <- list() + + index <- function(i, body) { + list( + append(list(index=i), body) + ) + } + + for (i in seq_along(example)) { + item <- example[[i]] + + assert_that(is.analysis_result(item), + msg = "Object in example is not in analysis_result class") + + if (has_attr(item, name_of_meta)){ + meta <- append(meta, index(i, item[[name_of_meta]])) + var <- append(var, list(item[name_of_group])) + pars_with_meta <- append(pars_with_meta, index(i, item[names(item) != name_of_meta])) + } else { + pars_no_meta <- append(pars_no_meta, index(i, item)) + } + } + + base_left_join <- function(x, y, by) merge(x, y, by = by, all.x=TRUE) + + all_pars <- append(pars_with_meta, pars_no_meta) + + res_df <- base_bind_rows(all_pars) + + meta_df <- cbind(base_bind_rows(var), base_bind_rows(meta)) + + info_df <- tryCatch( + base_left_join(res_df, meta_df, by = c('index', name_of_group)), + error=function(e) res_df + ) + + subset(info_df, select = -index) +} + +#' Extract analysis_result from a list of analysis_results by matching names and values +#' +#' The function returns a list of all analysis_results in the input list that match the values with names specified via keywords parameters of the function. +#' If no value matches the specified name in any analysis_result containing in the given list or +#' the specified name does not existed in any analysis_result, the function returns an empty list `list()`. +#' +#' @param results A list of analysis_result +#' @param ... Keywords parameters with the name and value matching the element in analysis result to be extracted +#' @return A list of matched analysis results +#' @examples +#' \dontrun{ +#' results <- list( +#' analysis_result( +#' name = 'trt', +#' est = 1, +#' se = 2, +#' df = 3, +#' meta = list(visit = 'vis1') +#' ) +#' ) +#' +#' extract_analysis_result(results, name = 'trt') +#' extract_analysis_result(results, est = 1) +#' extract_analysis_result(results, name = 'trt', meta = list(visit = 'vis1')) +#' extract_analysis_result(results, name = 'trt2') +# +#' } +extract_analysis_result <- function(results, ...){ + dots <- list(...) + assert_that(all(!is.null(names(dots)), + length(names(dots)) > 0, + !any(grepl("^$", names(dots)))), + msg = "Invalide parameters. Only key-word parameters are valide.") + + meta <- list() + has_meta <- FALSE + if ('meta' %in% names(dots)) { + meta <- dots[['meta']] + dots[['meta']] <- NULL + has_meta <- TRUE + } + + names_match_values <- function(obj, named_values=dots) { + mapply(function(label, value) isTRUE(obj[[label]] == value), + names(named_values), named_values, SIMPLIFY = TRUE, USE.NAMES = FALSE) + } # When SIMPLIFY = TRUE, coercion can happen on logical(0) which generates WARNINGS. isTRUE is used to avoid coercion and make the code more robust + + extract_match <- function(obj, named_values=dots, constrain = identity) { + Filter(function(item) all( + names_match_values(constrain(item), named_values)), + obj) + } + + search_in_meta <- function(obj) obj[['meta']] + + extract_meta <- function(obj) extract_match(obj, named_values = meta, constrain = search_in_meta) + + tryCatch({ + matches_except_meta <- extract_match(results) + + if (has_meta) { + extract_meta(matches_except_meta) + } else { + matches_except_meta + } + }, + warning = function(w) { + list() + }) +} diff --git a/R/ancova.R b/R/ancova.R index 57a08af44..621bfca53 100644 --- a/R/ancova.R +++ b/R/ancova.R @@ -28,16 +28,28 @@ #' If no value for `visits` is provided then it will be set to #' `unique(data[[vars$visit]])`. #' -#' In order to meet the formatting standards set by [analyse()] the results will be collapsed -#' into a single list suffixed by the visit name, e.g.: +#' Visits as part of the meta information of the `analysis_result` object from results of [analyse()] can be accessed individually and are +#' are displayed in a column from the `print.analysis` output such like +#' ``` +#' ===================================== +#' name est se df visit +#' ------------------------------------- +#' trt -0.513 0.505 197 1 +#' trt -2.366 0.675 197 4 +#' lsm_ref 7.51 0.477 197 4 +#' lsm_alt 5.144 0.477 197 4 +#' ------------------------------------- +#' +#' ``` +#' Then list in analysis results has structure such as following. Each individual result is in class `analysis_result` #'``` #'list( -#' trt_visit_1 = list(est = ...), -#' lsm_ref_visit_1 = list(est = ...), -#' lsm_alt_visit_1 = list(est = ...), -#' trt_visit_2 = list(est = ...), -#' lsm_ref_visit_2 = list(est = ...), -#' lsm_alt_visit_2 = list(est = ...), +#' trt = analysis_result(name =, est = ..., meta = list(visit=1, ...)), +#' lsm_ref = analysis_result(name =, est = ..., meta = list(visit=1, ...)), +#' lsm_alt = analysis_result(name =, est = ..., meta = list(visit=1, ...)), +#' trt = analysis_result(name =, est = ..., meta = list(visit=2, ...)), +#' lsm_ref = analysis_result(name =, est = ..., meta = list(visit=2, ...)), +#' lsm_alt = analysis_result(name =, est = ..., meta = list(visit=2, ...)), #' ... #') #'``` @@ -139,8 +151,7 @@ ancova <- function(data, vars, visits = NULL, weights = c("proportional", "equal visits, function(x) { data2 <- data[data[[visit]] == x, ] - res <- ancova_single(data2, outcome, group, covariates, weights) - names(res) <- paste0(names(res), "_", x) + res <- ancova_single(data2, outcome, group, covariates, weights, x) return(res) } ) @@ -174,7 +185,7 @@ ancova <- function(data, vars, visits = NULL, weights = c("proportional", "equal #' } #' @seealso [ancova()] #' @importFrom stats lm coef vcov df.residual -ancova_single <- function(data, outcome, group, covariates, weights = c("proportional", "equal")) { +ancova_single <- function(data, outcome, group, covariates, weights = c("proportional", "equal"), ...) { weights <- match.arg(weights) assert_that( @@ -201,13 +212,15 @@ ancova_single <- function(data, outcome, group, covariates, weights = c("proport lsm1 <- do.call(lsmeans, args) x <- list( - trt = list( + analysis_result( + name = 'trt', est = coef(mod)[[group]], se = sqrt(vcov(mod)[group, group]), - df = df.residual(mod) + df = df.residual(mod), + meta = add_meta('visit', ...) ), - lsm_ref = lsm0, - lsm_alt = lsm1 + as_analysis_result(lsm0, name = 'lsm_ref', meta = add_meta('visit', ...)), + as_analysis_result(lsm1, name = 'lsm_alt', meta = add_meta('visit', ...)) ) return(x) } diff --git a/R/utilities.R b/R/utilities.R index cd4fa8cb0..840cc501a 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -513,7 +513,215 @@ as_dataframe <- function(x) { return(x2) } +#' Add meta information to customerize analysis function +#' +#' This function is used only internally for ancova +#' +#' @param var_name A character variable of the names of the elements to be added to meta +#' @param ... The values of the element to be added to meta. The number of items should be equal to the length of the name parameter +add_meta <- function (var_names, ...) { + var_values <- list(...) + prettier <- function(x) paste(x, collapse = ' ') + assert_that( + all(!is.null(var_names), + !is.null(var_values), + all(Vectorize(isTRUE)(!is.na(var_names))), + length(var_names) == length(var_values)), + msg = sprintf("Invalid parameters: `%s`, `%s`", prettier(var_names), prettier(var_values)) + ) + + names(var_values) <- var_names + var_values +} + +#' Assert variable's type +#' +#' @param what Variable to be asserted +#' @param how Type asserting functions: is.character, is.numeric, is.list, is.logic +#' @examples +#' \dontrun{ +#' assert_type(est, is.numeric) +#' } +assert_type <- function(what, + how, + whatname = deparse(substitute(what)), + howname = deparse(substitute(how))) { + + prettier <- function(...) gsub("_", " ", as.character(...)) + + type <- (function(s) sub(".*\\.", "", s))(howname) + assert_that(how(what), + msg = sprintf("%s of analysis_result is not %s", whatname, prettier(type)) + ) +} + +#' Create assert function comparing value +#' +#' @param how A function to generate value from the object to be asserted +#' @param where A character variable indicating the origin of the object to be asserted. Default: `NULL` +#' @param howname A character variable indicating the name of the how function. Default: `deparse(substitute(how))` +assert_value <- function(how, where = NULL, howname = deparse(substitute(how))) { + inwhere <- '' + if (!is.null(where)) inwhere <- paste(' in', where) + function(what, + should, + whatname = deparse(substitute(what))) { + + prettier <- function(x) paste(x, collapse = " ") + + assert_that(all(how(what) == should), + msg = sprintf("%s of %s%s `%s` is not %s", howname, whatname, inwhere, prettier(how(what)), prettier(should)) + ) + } +} +#' Assert length of the element in analysis_result +#' +#' @param what The element to be asserted +#' @param should The length expected +#' @param whatname The name of the element. Default: `deparse(substitute(what))` +assert_anares_length <- assert_value(length, where = 'analysis_result') +#' Make a chain of function calls with certain relation function +#' @param relation A relation function: `any` or `all` +#' @param ... Functions to be chained +#' @return A function taking arguments that are feed into chained functions +#' @examples +#' \dontrun{ +#' is.numeric_or_na <- make_chain(any, is.numeric, is.na) +#' is.numeric_or_na(NA) # returns TRUE +#' is.numeric_or_na(15) # returns TRUE +#' is.numeric_or_na('a') # returns FALSE +#' } +make_chain <- function(relation, ...) { + fs <- c(...) + function(...) relation(sapply(fs, function(f) isTRUE(f(...)))) +} +#' Order a named list by its names according to given character vector +#' @param L A list to be ordered +#' @param v A character contains the names in order +#' @return A list with names in order +#' @examples +#' \dontrun{ +#' L_ordered <- order_list_by_name(list(a=1,b='x',c=TRUE), c("c", "a", "d", "x", "b", "t")) +#' # returns a list `list(c=TRUE, a=1, b='x)` +#' +#' } +order_list_by_name <- function(L, v) { + ordered_pos <- match(v, names(L)) + ordered_pos <- ordered_pos[!is.na(ordered_pos)] + L[ordered_pos] +} +#' Convert nested list to data.frame +#' +#' @param nestlist A nested list to be converted to data.frame +#' @return A data.frame binding each sublist as row in the data.frame and with NA filled for missing values +base_bind_rows <- function(nestlist) { + nms <- unique(unlist(lapply(nestlist, names))) + frmls <- as.list(setNames(rep(NA, length(nms)), nms)) + dflst <- setNames(lapply(nms, function(x) call("unlist", as.symbol(x))), nms) + make_df <- as.function(c(frmls, call("do.call", "data.frame", dflst))) + + do.call(rbind, lapply(nestlist, function(x) do.call(make_df, x))) +} + +#' Create name checkers for object with message passing dispatch +#' +#' @param ... Character vectors for the reference to check against +#' @param optional Character vector of optional name. Default: NULL +#' @return A constructor to create checker functions with message passing dispatch +namechecker <- function(..., optional = NULL) { + + # compile the musthave list at the top level so that easier to maintain and update + musthave <- c(...) + + # message passing as a dispatch + function(msg) { + + # function to check if elements in list X exist in Y + XsInYs <- function(x, y) vapply(x, function(.x) .x %in% y, logical(1)) + + # wrapper to swap order of formal parameter of binary function + swap <- function(f) { + function(x, y) f(y, x) + } + + # higher-order function to create template for validators + chker_template <- function(musthave, wrapper=identity, f = XsInYs, .optional = optional) { + function(...) { + wrapper(f)(append(musthave, .optional), names(...)) + } + } + + # Validator to check if elements in musthave present in the object's name + # checker does not check against optional names. Only names in musthave have to be presented in the object + musthave_in_objnames <- chker_template(musthave, .optional = NULL) + + # Validator to check if object's name belongs to musthave + optional names (simply swap the order of arguments from present) + objnames_in_musthave <- chker_template(musthave, swap) + + dispatch <- list( + musthave_in_objnames = musthave_in_objnames, + objnames_in_musthave = objnames_in_musthave, + musthave = musthave, + optional = optional, + all = append(musthave, optional) + ) + + dispatch[[msg]] + } +} + +#' Higher-order function to compose function n-times +#' +#' Taking a function as f argument, this function convert it to another function apply this function n-times: +#' n = 1: f(x) ==> f(x) +#' n = 2: f(x) ==> f(f(x)) +#' n = 4: f(x) ==> f(f(f(f(x))) +#' @param f function to be converted to composed version +#' @param n times to be composed +#' @examples +#' \dontrun{ +#' add_one <- function (x) x + 1 +#' add_two <- compose_n(add_one, 2) +#' add_two(5) # This equivalents to add_one(add_one(5)) and returns 7 +#' +#' } +compose_n <- function(f, n) { + function(x) { + if (n <= 0) x + else f(compose_n(f, n-1)(x)) + } +} + +#' Apply function at last N levels of a nested list +#' +#' Recursively traverse a nested list and apply a function at the Nth level backward counting from deepest level +#' @param lst a list to be applied by the function +#' @param f a function to apply on `lst` +#' @param n a numeric value indicating the nth level to be applied backward counting from deepest level +#' @examples +#' \dontrun{ +#' dt <- list(a1=list( +#' b11=list(c111=1, c112=2,c113=3), +#' b12=list(c121=4, c122=5,c123=6)), +#' a2=list( +#' b21=list(c211=7, c212=8,c213=9), +#' b22=list(c221=10, c222=11,c223=12)) +#' ) +#' ) +#' +#' back_apply_at(dt, function(x) x+1, 1) # This will apply `function(x) x+1` to the deepest level of `dt`, i.e. 1st level counting backward from deepest level +#' +#'} +back_apply_at <- function(lst, f, n) { + lapply(lst, + function(sublst) { + nextNlevel <- compose_n(function(x) x[[1]], n-1) + if (!is.list(nextNlevel(sublst))) f(sublst) + else back_apply_at(sublst, f, n) + } + ) +} diff --git a/man/add_meta.Rd b/man/add_meta.Rd new file mode 100644 index 000000000..fbb10eaf4 --- /dev/null +++ b/man/add_meta.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{add_meta} +\alias{add_meta} +\title{Add meta information to customerize analysis function} +\usage{ +add_meta(var_names, ...) +} +\arguments{ +\item{...}{The values of the element to be added to meta. The number of items should be equal to the length of the name parameter} + +\item{var_name}{A character variable of the names of the elements to be added to meta} +} +\description{ +This function is used only internally for ancova +} diff --git a/man/ana_name_chker.Rd b/man/ana_name_chker.Rd new file mode 100644 index 000000000..f05023210 --- /dev/null +++ b/man/ana_name_chker.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/analyse.R +\name{ana_name_chker} +\alias{ana_name_chker} +\title{Name checker for analysis_result object} +\usage{ +ana_name_chker() +} +\description{ +A higher order function returns an analysis name checker which is again a higher order function takes character vector as +type of dispatch message and returns selected check function or properties. +This function takes no argument. The point is to delay the evaluation and evaluate only when it is needed, similar idea as shiny ractive +} +\examples{ +\dontrun{ +anares_names_in_musthave <- ana_name_chker()('objnames_in_musthave') +musthave_in_anares_names <- ana_name_chker()('musthave_in_objnames') +musthave_names <- ana_name_chker()('musthave') +optional_names <- ana_name_chker()('optional') +all_names <- ana_name_chker()('all') +} +} diff --git a/man/analyse.Rd b/man/analyse.Rd index d740ee131..6bb1ef340 100644 --- a/man/analyse.Rd +++ b/man/analyse.Rd @@ -43,12 +43,14 @@ i.e.:\preformatted{myfun <- function(dat, ...) \{ mod_1 <- lm(data = dat, outcome ~ group) mod_2 <- lm(data = dat, outcome ~ group + covar) x <- list( - trt_1 = list( + analysis_result( + name = trt_1, est = coef(mod_1)[[group]], se = sqrt(vcov(mod_1)[group, group]), df = df.residual(mod_1) ), - trt_2 = list( + analysis_result( + name = trt_2, est = coef(mod_2)[[group]], se = sqrt(vcov(mod_2)[group, group]), df = df.residual(mod_2) diff --git a/man/analysis_info.Rd b/man/analysis_info.Rd new file mode 100644 index 000000000..830097e15 --- /dev/null +++ b/man/analysis_info.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/analyse.R +\name{analysis_info} +\alias{analysis_info} +\title{Get printable analysis information from an example of analysis result} +\usage{ +analysis_info(example, name_of_group = "name", name_of_meta = "meta") +} +\arguments{ +\item{example}{A subset of the result of the analysis object for getting enough info to print. It should not be the complete result of analysis object but a subset of it such as \code{anaObj$results[[1]]}} + +\item{name_of_group}{A character variable for the name of group variable in the result of analysis which is defined from \code{analysis_result}. Default: \code{'name'}} + +\item{name_of_meta}{A character variable for the name of meta data in the result of analysis which is defined from \code{analysis_result}. Default: 'meta'} +} +\value{ +A data.frame containing the information of the analysis result from the example +} +\description{ +Get printable analysis information from an example of analysis result +} +\examples{ +\dontrun{ +analysis_info(dat, name_of_group = 'name', name_of_meta = 'meta') +} +} diff --git a/man/analysis_result.Rd b/man/analysis_result.Rd new file mode 100644 index 000000000..52b9d3306 --- /dev/null +++ b/man/analysis_result.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/analyse.R +\name{analysis_result} +\alias{analysis_result} +\title{Constructor of analysis result} +\usage{ +analysis_result(name, est, se = NULL, df = NULL, meta = NULL) +} +\arguments{ +\item{name}{A character variable for the group name} + +\item{est}{A double type numeric variable as the estimate} + +\item{se}{A double type numeric variable as the standard error} + +\item{df}{An integer type of numeric variable} + +\item{meta}{A list type of variable as meta information} +} +\value{ +An object of "analysis_result" class +} +\description{ +Construct an analysis result class object whose base type is a list +} +\details{ +\itemize{ +\item \code{se}, \code{df} and \code{meta} is optional +\item \code{se} and \code{df} if given must be numeric values greater or equal to 0 +} +} +\examples{ +\dontrun{ +ana_res_obj <- analysis_result(name = 'trt', est = 1, se = 2, df = 3, meta = list(visit = 1)) +} +} diff --git a/man/ancova.Rd b/man/ancova.Rd index 48af509e7..c1fa3843a 100644 --- a/man/ancova.Rd +++ b/man/ancova.Rd @@ -39,14 +39,25 @@ The function works as follows: If no value for \code{visits} is provided then it will be set to \code{unique(data[[vars$visit]])}. -In order to meet the formatting standards set by \code{\link[=analyse]{analyse()}} the results will be collapsed -into a single list suffixed by the visit name, e.g.:\preformatted{list( - trt_visit_1 = list(est = ...), - lsm_ref_visit_1 = list(est = ...), - lsm_alt_visit_1 = list(est = ...), - trt_visit_2 = list(est = ...), - lsm_ref_visit_2 = list(est = ...), - lsm_alt_visit_2 = list(est = ...), +Visits as part of the meta information of the \code{analysis_result} object from results of \code{\link[=analyse]{analyse()}} can be accessed individually and are +are displayed in a column from the \code{print.analysis} output such like\preformatted{ ===================================== + name est se df visit + ------------------------------------- + trt -0.513 0.505 197 1 + trt -2.366 0.675 197 4 + lsm_ref 7.51 0.477 197 4 + lsm_alt 5.144 0.477 197 4 + ------------------------------------- + +} + +Then list in analysis results has structure such as following. Each individual result is in class \code{analysis_result}\preformatted{list( + trt = analysis_result(name =, est = ..., meta = list(visit=1, ...)), + lsm_ref = analysis_result(name =, est = ..., meta = list(visit=1, ...)), + lsm_alt = analysis_result(name =, est = ..., meta = list(visit=1, ...)), + trt = analysis_result(name =, est = ..., meta = list(visit=2, ...)), + lsm_ref = analysis_result(name =, est = ..., meta = list(visit=2, ...)), + lsm_alt = analysis_result(name =, est = ..., meta = list(visit=2, ...)), ... ) } diff --git a/man/ancova_single.Rd b/man/ancova_single.Rd index 464e652aa..74e8e1b39 100644 --- a/man/ancova_single.Rd +++ b/man/ancova_single.Rd @@ -9,7 +9,8 @@ ancova_single( outcome, group, covariates, - weights = c("proportional", "equal") + weights = c("proportional", "equal"), + ... ) } \arguments{ diff --git a/man/as_analysis_result.Rd b/man/as_analysis_result.Rd new file mode 100644 index 000000000..8f005dcb4 --- /dev/null +++ b/man/as_analysis_result.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/analyse.R +\name{as_analysis_result} +\alias{as_analysis_result} +\title{Convert object to analysis result class} +\usage{ +as_analysis_result(x, ...) +} +\arguments{ +\item{x}{The object to be converted to analysis_result class} + +\item{...}{Optional keywords parameters for adding missing elements to the object} +} +\value{ +An "analysis_result" class object with optionally updated elements +} +\description{ +Convert object to analysis result class +} +\examples{ +\dontrun{ +ana_res_obj <- as_analysis_result(list(est = 1, se = 2, df = 3), name = 'trt') +} +} diff --git a/man/assert_anares_length.Rd b/man/assert_anares_length.Rd new file mode 100644 index 000000000..549828546 --- /dev/null +++ b/man/assert_anares_length.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{assert_anares_length} +\alias{assert_anares_length} +\title{Assert length of the element in analysis_result} +\usage{ +assert_anares_length(what, should, whatname = deparse(substitute(what))) +} +\arguments{ +\item{what}{The element to be asserted} + +\item{should}{The length expected} + +\item{whatname}{The name of the element. Default: \code{deparse(substitute(what))}} +} +\description{ +Assert length of the element in analysis_result +} diff --git a/man/assert_type.Rd b/man/assert_type.Rd new file mode 100644 index 000000000..bfa5366ee --- /dev/null +++ b/man/assert_type.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{assert_type} +\alias{assert_type} +\title{Assert variable's type} +\usage{ +assert_type( + what, + how, + whatname = deparse(substitute(what)), + howname = deparse(substitute(how)) +) +} +\arguments{ +\item{what}{Variable to be asserted} + +\item{how}{Type asserting functions: is.character, is.numeric, is.list, is.logic} +} +\description{ +Assert variable's type +} +\examples{ +\dontrun{ +assert_type(est, is.numeric) +} +} diff --git a/man/assert_value.Rd b/man/assert_value.Rd new file mode 100644 index 000000000..c595fa7e6 --- /dev/null +++ b/man/assert_value.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{assert_value} +\alias{assert_value} +\title{Create assert function comparing value} +\usage{ +assert_value(how, where = NULL, howname = deparse(substitute(how))) +} +\arguments{ +\item{how}{A function to generate value from the object to be asserted} + +\item{where}{A character variable indicating the origin of the object to be asserted. Default: \code{NULL}} + +\item{howname}{A character variable indicating the name of the how function. Default: \code{deparse(substitute(how))}} +} +\description{ +Create assert function comparing value +} diff --git a/man/back_apply_at.Rd b/man/back_apply_at.Rd new file mode 100644 index 000000000..a658c7925 --- /dev/null +++ b/man/back_apply_at.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{back_apply_at} +\alias{back_apply_at} +\title{Apply function at last N levels of a nested list} +\usage{ +back_apply_at(lst, f, n) +} +\arguments{ +\item{lst}{a list to be applied by the function} + +\item{f}{a function to apply on \code{lst}} + +\item{n}{a numeric value indicating the nth level to be applied backward counting from deepest level} +} +\description{ +Recursively traverse a nested list and apply a function at the Nth level backward counting from deepest level +} +\examples{ +\dontrun{ +dt <- list(a1=list( + b11=list(c111=1, c112=2,c113=3), + b12=list(c121=4, c122=5,c123=6)), + a2=list( + b21=list(c211=7, c212=8,c213=9), + b22=list(c221=10, c222=11,c223=12)) + ) + ) + +back_apply_at(dt, function(x) x+1, 1) # This will apply `function(x) x+1` to the deepest level of `dt`, i.e. 1st level counting backward from deepest level + +} +} diff --git a/man/base_bind_rows.Rd b/man/base_bind_rows.Rd new file mode 100644 index 000000000..009879be5 --- /dev/null +++ b/man/base_bind_rows.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{base_bind_rows} +\alias{base_bind_rows} +\title{Convert nested list to data.frame} +\usage{ +base_bind_rows(nestlist) +} +\arguments{ +\item{nestlist}{A nested list to be converted to data.frame} +} +\value{ +A data.frame binding each sublist as row in the data.frame and with NA filled for missing values +} +\description{ +Convert nested list to data.frame +} diff --git a/man/compose_n.Rd b/man/compose_n.Rd new file mode 100644 index 000000000..1d06537cf --- /dev/null +++ b/man/compose_n.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{compose_n} +\alias{compose_n} +\title{Higher-order function to compose function n-times} +\usage{ +compose_n(f, n) +} +\arguments{ +\item{f}{function to be converted to composed version} + +\item{n}{times to be composed} +} +\description{ +Taking a function as f argument, this function convert it to another function apply this function n-times: +n = 1: f(x) ==> f(x) +n = 2: f(x) ==> f(f(x)) +n = 4: f(x) ==> f(f(f(f(x))) +} +\examples{ +\dontrun{ +add_one <- function (x) x + 1 +add_two <- compose_n(add_one, 2) +add_two(5) # This equivalents to add_one(add_one(5)) and returns 7 + +} +} diff --git a/man/is.analysis_result.Rd b/man/is.analysis_result.Rd new file mode 100644 index 000000000..835b1024a --- /dev/null +++ b/man/is.analysis_result.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/analyse.R +\name{is.analysis_result} +\alias{is.analysis_result} +\title{Check if an object is in class analysis_result} +\usage{ +is.analysis_result(x) +} +\arguments{ +\item{x}{Object to be checked} +} +\value{ +Logical value TRUE/FALSE +} +\description{ +Check if an object is in class analysis_result +} +\details{ +This function does not only check the class attribute of the object. +It also checks constraints of the names of the elements in the list +} diff --git a/man/make_chain.Rd b/man/make_chain.Rd new file mode 100644 index 000000000..82193eb2b --- /dev/null +++ b/man/make_chain.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{make_chain} +\alias{make_chain} +\title{Make a chain of function calls with certain relation function} +\usage{ +make_chain(relation, ...) +} +\arguments{ +\item{relation}{A relation function: \code{any} or \code{all}} + +\item{...}{Functions to be chained} +} +\value{ +A function taking arguments that are feed into chained functions +} +\description{ +Make a chain of function calls with certain relation function +} +\examples{ +\dontrun{ +is.numeric_or_na <- make_chain(any, is.numeric, is.na) +is.numeric_or_na(NA) # returns TRUE +is.numeric_or_na(15) # returns TRUE +is.numeric_or_na('a') # returns FALSE +} +} diff --git a/man/namechecker.Rd b/man/namechecker.Rd new file mode 100644 index 000000000..1d03c55c4 --- /dev/null +++ b/man/namechecker.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{namechecker} +\alias{namechecker} +\title{Create name checkers for object with message passing dispatch} +\usage{ +namechecker(..., optional = NULL) +} +\arguments{ +\item{...}{Character vectors for the reference to check against} + +\item{optional}{Character vector of optional name. Default: NULL} +} +\value{ +A constructor to create checker functions with message passing dispatch +} +\description{ +Create name checkers for object with message passing dispatch +} diff --git a/man/order_list_by_name.Rd b/man/order_list_by_name.Rd new file mode 100644 index 000000000..8395bff29 --- /dev/null +++ b/man/order_list_by_name.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utilities.R +\name{order_list_by_name} +\alias{order_list_by_name} +\title{Order a named list by its names according to given character vector} +\usage{ +order_list_by_name(L, v) +} +\arguments{ +\item{L}{A list to be ordered} + +\item{v}{A character contains the names in order} +} +\value{ +A list with names in order +} +\description{ +Order a named list by its names according to given character vector +} +\examples{ +\dontrun{ +L_ordered <- order_list_by_name(list(a=1,b='x',c=TRUE), c("c", "a", "d", "x", "b", "t")) +# returns a list `list(c=TRUE, a=1, b='x)` + +} +} diff --git a/tests/testthat/test-analyse.R b/tests/testthat/test-analyse.R index 697d2ada1..6b5729b0c 100644 --- a/tests/testthat/test-analyse.R +++ b/tests/testthat/test-analyse.R @@ -10,8 +10,8 @@ test_that("basic constructions of `analysis` work as expected",{ x <- as_analysis( results = list( - list(p1 = list("est" = 1)), - list(p1 = list("est" = 2)) + list(analysis_result(name = 'p1', est = 1)), # A nested structure is necessary here. The top level is a full result list. The 2nd level is each imputation. The 3rd level is each individual analysis result inside each imputation. + list(analysis_result(name = 'p1', est = 2)) ), method = method_condmean(n_samples = 1) ) @@ -20,8 +20,8 @@ test_that("basic constructions of `analysis` work as expected",{ x <- as_analysis( results = list( - list(p1 = list("est" = 1)), - list(p1 = list("est" = 2)) + list(analysis_result(name = 'p1', est = 1)), + list(analysis_result(name = 'p1', est = 2)) ), method = method_condmean(type = "jackknife") ) @@ -30,8 +30,8 @@ test_that("basic constructions of `analysis` work as expected",{ x <- as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se" = 1)), - list(p1 = list("est" = 2, "df" = 3, "se" = 3)) + list(analysis_result(name = 'p1', est = 1, df = 4, se = 1)), + list(analysis_result(name = 'p1', est = 2, df = 3, se = 3)) ), method = method_bayes(n_samples = 2) ) @@ -40,8 +40,8 @@ test_that("basic constructions of `analysis` work as expected",{ x <- as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se" = 1)), - list(p1 = list("est" = 2, "df" = 3, "se" = 3)) + list(analysis_result(name = 'p1', est = 1, df = 4, se = 1)), + list(analysis_result(name = 'p1', est = 2, df = 3, se = 3)) ), method = method_approxbayes(n_samples = 2) ) @@ -50,8 +50,8 @@ test_that("basic constructions of `analysis` work as expected",{ x <- as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se" = NA)), - list(p1 = list("est" = 2, "df" = 3, "se" = NA)) + list(analysis_result(name = 'p1', est = 1, df = 4, se = NA)), + list(analysis_result(name = 'p1', est = 2, df = 3, se = NA)) ), method = method_bayes(n_samples = 2) ) @@ -60,13 +60,12 @@ test_that("basic constructions of `analysis` work as expected",{ x <- as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se" = NA)), - list(p1 = list("est" = 2, "df" = 3, "se" = NA)) + list(analysis_result(name = 'p1', est = 1, df = 4, se = NA)), + list(analysis_result(name = 'p1', est = 2, df = 3, se = NA)) ), method = method_approxbayes(n_samples = 2) ) expect_true(validate(x)) - }) @@ -76,8 +75,7 @@ test_that("incorrect constructions of as_analysis fail", { expect_error( as_analysis( results = list( - list(p1 = list("est" = 1)), - list(p1 = list("est" = 2)) + list(analysis_result(name = 'p1', est = 1), analysis_result(name = 'p1', est = 2)) ), method = method_condmean(n_samples = 2) ), @@ -87,8 +85,8 @@ test_that("incorrect constructions of as_analysis fail", { expect_error( as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se" = 1)), - list(p1 = list("est" = 2, "df" = 3, "se" = 3)) + list(analysis_result(name = 'p1', est = 1, df = 4, se = 1)), + list(analysis_result(name = 'p1', est = 2, df = 3, se = 3)) ), method = method_bayes(n_samples = 3) ), @@ -98,8 +96,8 @@ test_that("incorrect constructions of as_analysis fail", { expect_error( as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se" = 1)), - list(p1 = list("est" = 2, "df" = 3, "se" = 3)) + list(analysis_result(name = 'p1', est = 1, df = 4, se = 1)), + list(analysis_result(name = 'p1', est = 2, df = 3, se = 3)) ), method = method_approxbayes(n_samples = 3) ), @@ -111,80 +109,79 @@ test_that("incorrect constructions of as_analysis fail", { expect_error( as_analysis( results = list( - list(p1 = list("est1" = 1)), - list(p1 = list("est1" = 2)) + list(analysis_result(name = 'p1', est1 = 1), analysis_result(name = 'p1', est1 = 2)), ), method = method_condmean(n_samples = 1) ), - "`est`" + "unused argument \\(est1 = 1\\)" ) expect_error( as_analysis( results = list( - list(p1 = list("est" = 1, "df1" = 4, "se" = 1)), - list(p1 = list("est" = 2, "df1" = 3, "se" = 3)) + list(analysis_result(name = 'p1', est = 1, df1 = 4, se = 1)), + list(analysis_result(name = 'p1', est = 2, df1 = 3, se = 3)) ), method = method_approxbayes(n_samples = 2) ), - "`df`" + "unused argument \\(df1 = 4\\)" ) expect_error( as_analysis( results = list( - list(p1 = list("est" = 1, "df1" = 4, "se" = 1)), - list(p1 = list("est" = 2, "df1" = 3, "se" = 3)) + list(analysis_result(name = 'p1', est = 1, df1 = 4, se = 1)), + list(analysis_result(name = 'p1', est = 2, df1 = 3, se = 3)) ), method = method_bayes(n_samples = 2) ), - "`df`" + "unused argument \\(df1 = 4\\)" ) expect_error( as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se1" = 1)), - list(p1 = list("est" = 2, "df" = 3, "se1" = 3)) + list(analysis_result(name = 'p1', est = 1, df = 4, se1 = 1)), + list(analysis_result(name = 'p1', est = 2, df = 3, se1 = 3)) ), method = method_bayes(n_samples = 2) ), - "`se`" + "unused argument \\(se1 = 1\\)" ) expect_error( as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se1" = 1)), - list(p1 = list("est1" = 2, "df" = 3, "se1" = 3)) + list(analysis_result(name = 'p1', est = 1, df = 4, se1 = 1)), + list(analysis_result(name = 'p1', est1 = 2, df = 3, se1 = 3)) ), method = method_condmean(type = "jackknife") ), - "`est`" + "unused argument \\(se1 = 1\\)" ) - ### Inconsistent analysis parameters + ### Inconsistent analysis parameters ## Not sure what this is supposed to test expect_error( - as_analysis( - results = list( - list(p1 = list("est" = 1)), - list(p2 = list("est" = 2)) - ), - method = method_condmean(n_sample = 1) - ), - "identically named elements" - ) + as_analysis( + results = list( + list(analysis_result(name = 'p1', est = 1)), # 1st imputation contains one analysis with name "p1" + list(analysis_result(name = 'p2', est = 2)) # 2nd imputation contains one analysis with name "p2" + ), + method = method_condmean(n_sample = 1) + ), + "identically named elements" + ) expect_error( as_analysis( results = list( list(list("est" = 1)), - list(p1 = list("est" = 2)) + list(analysis_result(name = 'p1', est = 2)) ), method = method_condmean(n_sample = 1) ), - "results must be named lists" + "Individual analysis result must be type of analysis_result" ) expect_error( @@ -195,37 +192,37 @@ test_that("incorrect constructions of as_analysis fail", { ), method = method_condmean(n_sample = 1) ), - "results must be named lists" + "Individual analysis result must be type of analysis_result" ) ### Invalid values expect_error( as_analysis( results = list( - list(p1 = list("est" = NA)), - list(p1 = list("est" = 2)) + list(analysis_result(name = 'p1', est = NA)), + list(analysis_result(name = 'p1', est = 2)) ), method = method_condmean(n_sample = 1) ), - "`est` contains missing values" + "est of analysis_result is not numeric" ) expect_error( as_analysis( results = list( - list(p1 = list("est" = "a")), - list(p1 = list("est" = 2)) + list(analysis_result(name = 'p1', est = 'a')), + list(analysis_result(name = 'p1', est = 2)) ), method = method_condmean(n_sample = 1) ), - "result is type 'character'" + "est of analysis_result is not numeric" ) expect_error( as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se" = NA)), - list(p1 = list("est" = 2, "df" = 3, "se" = 3)) + list(analysis_result(name = 'p1', est = 1, df = 4, se = NA)), + list(analysis_result(name = 'p1', est = 2, df = 3, se = 3)) ), method = method_bayes(n_sample = 2) ), @@ -234,8 +231,8 @@ test_that("incorrect constructions of as_analysis fail", { x <- as_analysis( results = list( - list(p1 = list("est" = 1, "df" = 4, "se" = NA)), - list(p1 = list("est" = 2, "df" = 3, "se" = 3)) + list(analysis_result(name = 'p1', est = 1, df = 4, se = NA)), + list(analysis_result(name = 'p1', est = 2, df = 3, se = 3)) ), method = method_condmean(n_sample = 1) ) diff --git a/tests/testthat/test-analysis_result.R b/tests/testthat/test-analysis_result.R new file mode 100644 index 000000000..c0eed6ccb --- /dev/null +++ b/tests/testthat/test-analysis_result.R @@ -0,0 +1,231 @@ +# Test functions relavent to analysis_result class + +test_that("basic constructions of `analysis_result` work as expected", { + + expect_general <- function(x) { + expect_s3_class(x, c('analysis_result', 'list')) + expect_type(x, 'list') + expect_named(x) + } + + # with optional elements: df, meta + x <- analysis_result(name = 'trt', + est = 1, + se = 2, + df = 3, + meta = list(visit = 1)) + expect_general(x) + expect_length(x, 5) + expect_equal(names(x), c('name', 'est', 'se', 'df', 'meta')) + expect_true(assertthat::has_attr(x, 'meta')) + expect_equal(x$name, 'trt') + expect_equal(x$df, 3) + expect_equal(x$meta, list(visit = 1)) + + # without optional elements + x <- analysis_result(name = 'trt', + est = 1, + se = 2, + meta = list(visit = 1)) + expect_general(x) + expect_length(x, 4) + expect_equal(names(x), c('name', 'est', 'se', 'meta')) + expect_true(has_attr(x, 'meta')) + expect_equal(x$name, 'trt') + expect_equal(x$meta, list(visit = 1)) + + # without optional elements + x <- analysis_result(name = 'trt', + est = 1) + expect_general(x) + expect_length(x, 2) + expect_equal(names(x), c('name', 'est')) + expect_false(has_attr(x, 'meta')) + expect_equal(x$name, 'trt') + expect_equal(x$est, 1) + + # special input: se = NA + x <- analysis_result(name = 'trt', + est = 1, + se = NA) + expect_general(x) + + expect_length(x, 3) + expect_equal(names(x), c('name', 'est', 'se')) + expect_true(is.na(x$se)) + + # special input: df = NA + x <- analysis_result(name = 'trt', + est = 1, + se = 2, + df = NA) + expect_general(x) + expect_length(x, 4) + expect_equal(names(x), c('name', 'est', 'se', 'df')) + expect_true(is.na(x$df)) +}) + + +test_that("incorrect constructions of analysis_result fail", { + + # test parameter type + expect_error( + analysis_result(name = 1, + est = 1, + se = 2, + df = 3, + meta = list(visit = 1)), + "name of analysis_result is not character" + ) + + expect_error( + analysis_result(name = 'a', + est = 'b', + se = 2, + df = 3, + meta = list(visit = 1)), + "est of analysis_result is not numeric" + ) + + expect_error( + analysis_result(name = 'a', + est = 1, + se = list(), + df = 3, + meta = list(visit = 1)), + "se of analysis_result is not numeric or NA or NULL" + ) + + expect_error( + analysis_result(name = 'a', + est = 1, + se = 2, + df = data.frame(), + meta = list(visit = 1)), + "df of analysis_result is not numeric or NA or NULL" + ) + + expect_error( + analysis_result(name = 'a', + est = 1, + se = 2, + df = 3, + meta = 'b'), + "meta of analysis_result is not list or NULL" + ) + + # test parameter length + expect_error( + analysis_result(name = c('a', 'b'), + est = 1, + se = 2, + df = 3, + meta = list(visit = 1)), + "length of name in analysis_result `2` is not 1" + ) + + expect_error( + analysis_result(name = 'a', + est = c(1,2,3), + se = 2, + df = 3, + meta = list(visit = 1)), + "length of est in analysis_result `3` is not 1" + ) + + expect_error( + analysis_result(name = 'a', + est = 1, + se = c(1,2,3,4), + df = 3, + meta = list(visit = 1)), + "length of se in analysis_result `4` is not 1" + ) + + expect_error( + analysis_result(name = 'a', + est = 1, + se = 2, + df = c(1,2,3,4,5), + meta = list(visit = 1)), + "length of df in analysis_result `5` is not 1" + ) +}) + +# Test for as_analysis_result +# This test needs to be updated accordingly if ana_name_chker has been udpated +test_that("as_analysis_result works as expected", { + expect_general <- function(x) { + expect_s3_class(x, c("analysis_result", "list")) + expect_equal(typeof(x), "list") + expect_equal(x$name, 'a') + expect_equal(x$est, 1) + expect_equal(x$se, 2) + } + + x <- as_analysis_result(list(name='a', est=1, se = 2)) + expect_general(x) + + x <- as_analysis_result(list(name='a', est=1, se = 2, meta = list(visit = 1))) + expect_general(x) + expect_true(has_attr(x, 'meta')) + + # Test error input + expect_error(as_analysis_result(list(name='a'))) + expect_false('met' %in% names(as_analysis_result(list(name='a', est=1, se = 2, met = list(visit = 1))))) + } +) + + +test_that("ana_name_chker works as expected", { + f <- ana_name_chker() + expect_equal(class(f), "function") + expect_equal(class(f('musthave_in_objnames')), 'function') + expect_equal(class(f('objnames_in_musthave')), 'function') + expect_equal(typeof(f('musthave')), 'character') + expect_equal(typeof(f('optional')), 'character') +}) + +test_that("is.analysis_result works as expected", { + x <- analysis_result(name = 'trt', + est = 1, + se = 2, + df = 3, + meta = list(visit = 1)) + expect_true(is.analysis_result(x)) + expect_false(is.analysis_result(list(a=1))) + expect_false(is.analysis_result(structure(list(a=1), class=c('analyis_result', 'list')))) +}) + +test_that("analysis_info works as expected", { + + # check for normal input + test_names <- c('a', 'b', 'c', 'd', 'e', 'f', 'g') + test_ests <- seq(length(test_names)) + test_ses <- test_ests * 0.1 + test_dfs<- test_ests * 5 + test_metas <- lapply(test_ests, function(x) list(visit=x)) + + tab <- mapply(function(a,b,c,d,e) analysis_result(name=a, est=b, se=c, df=d, meta=e), + test_names, test_ests, test_ses, test_dfs, test_metas, SIMPLIFY = FALSE) + + x <- analysis_info(tab) + + expect_equal(class(x), "data.frame") + expect_equal(nrow(x), 7) + expect_equal(ncol(x), 5) + + + # check for NA and NULL input + test_ses[[2]] <- NA + test_dfs[[4]] <- NA + + tab <- mapply(function(a,b,c,d,e) analysis_result(name=a, est=b, se=c, df=d, meta=e), + test_names, test_ests, test_ses, test_dfs, test_metas, SIMPLIFY = FALSE) + tab[[1]] <- tab[[1]][-5] + tab[[1]] <- as_analysis_result(tab[[1]]) + x <- analysis_info(tab) + expect_true(is.na(x[[2, 3]])) + expect_true(is.na(x[[4, 4]])) + expect_true(is.na(x[[1, 5]])) +}) diff --git a/tests/testthat/test-ancova.R b/tests/testthat/test-ancova.R index eee1f1a49..fbb524f6d 100644 --- a/tests/testthat/test-ancova.R +++ b/tests/testthat/test-ancova.R @@ -29,16 +29,20 @@ test_that("ancova", { mod <- lm(out ~ age1 + age2 + grp, data = dat) result_expected <- list( - "trt_vis1" = list( - "est" = mod$coefficients[[4]], - "se" = sqrt(vcov(mod)[4, 4]), - "df" = df.residual(mod) + analysis_result( + name = 'trt', + est = mod$coefficients[[4]], + se = sqrt(vcov(mod)[4, 4]), + df = df.residual(mod), + meta = list(visit = 'vis1') ) ) - result_actual <- ancova( + results_actual <- ancova( dat, list(outcome = "out", group = "grp", covariates = c("age1", "age2"), visit = "visit") - )["trt_vis1"] + ) + + result_actual <- extract_analysis_result(results_actual, name = 'trt', meta = list(visit = 'vis1')) expect_equal(result_expected, result_actual) @@ -63,13 +67,17 @@ test_that("ancova", { mod <- lm(out ~ grp, data = dat) result_expected <- list( - "trt_ 1" = list( - "est" = mod$coefficients[[2]], - "se" = sqrt(vcov(mod)[2, 2]), - "df" = df.residual(mod) + analysis_result( + name = "trt", + est = mod$coefficients[[2]], + se = sqrt(vcov(mod)[2, 2]), + df = df.residual(mod), + meta = list(visit = " 1") ) ) - result_actual <- ancova(dat, list(outcome = "out", group = "grp", visit = "ivis"))["trt_ 1"] + results_actual <- ancova(dat, list(outcome = "out", group = "grp", visit = "ivis")) + + result_actual <- extract_analysis_result(results_actual, name = "trt", meta = list(visit = " 1")) expect_equal(result_expected, result_actual) @@ -93,14 +101,16 @@ test_that("ancova", { mod <- lm(out ~ age1 + age2 + grp, data = dat) result_expected <- list( - "trt_visit 1" = list( - "est" = mod$coefficients[[4]], - "se" = sqrt(vcov(mod)[4, 4]), - "df" = df.residual(mod) + analysis_result( + name = "trt", + est = mod$coefficients[[4]], + se = sqrt(vcov(mod)[4, 4]), + df = df.residual(mod), + meta = list(visit = "visit 1") ) ) - result_actual <- ancova( + results_actual <- ancova( dat, list( outcome = "out", @@ -109,7 +119,9 @@ test_that("ancova", { visit = "vis" ), visits = "visit 1" - )["trt_visit 1"] + ) + + result_actual <- extract_analysis_result(results_actual, name = "trt", meta = list(visit = "visit 1")) expect_equal(result_expected, result_actual) @@ -132,14 +144,16 @@ test_that("ancova", { mod <- lm(out ~ age1 + age2 + grp, data = filter(dat, vis == "visit 1")) result_expected <- list( - "trt_visit 1" = list( - "est" = mod$coefficients[[4]], - "se" = sqrt(vcov(mod)[4, 4]), - "df" = df.residual(mod) + analysis_result( + name = 'trt', + est = mod$coefficients[[4]], + se = sqrt(vcov(mod)[4, 4]), + df = df.residual(mod), + meta = list(visit = 'visit 1') ) ) - result_actual <- ancova( + results_actual <- ancova( dat, list( outcome = "out", @@ -148,11 +162,13 @@ test_that("ancova", { visit = "vis" ), visits = "visit 1" - )["trt_visit 1"] + ) + + result_actual <- extract_analysis_result(results_actual, name = "trt", meta = list(visit = "visit 1")) expect_equal(result_expected, result_actual) - result_actual <- ancova( + result_actuals <- ancova( dat, list( outcome = "out", @@ -161,11 +177,13 @@ test_that("ancova", { visit = "vis" ), visits = c("visit 1", "visit 2") - )["trt_visit 1"] + ) + + result_actual <- extract_analysis_result(results_actual, name = "trt", meta = list(visit = "visit 1")) expect_equal(result_expected, result_actual) - result_actual <- ancova( + results_actual <- ancova( dat, list( outcome = "out", @@ -179,22 +197,27 @@ test_that("ancova", { mod <- lm(out ~ age1 + age2 + grp, data = filter(dat, vis == "visit 2")) result_expected <- list( - "trt_visit 2" = list( - "est" = mod$coefficients[[4]], - "se" = sqrt(vcov(mod)[4, 4]), - "df" = df.residual(mod) + analysis_result( + name = "trt", + est = mod$coefficients[[4]], + se = sqrt(vcov(mod)[4, 4]), + df = df.residual(mod), + meta = list(visit = "visit 2") ) ) - expect_equal(result_expected, result_actual["trt_visit 2"]) + result_actual <- extract_analysis_result(results_actual, name = "trt", meta = list(visit = "visit 2")) - expect_equal( - names(result_actual), - c( - "trt_visit 1", "lsm_ref_visit 1", "lsm_alt_visit 1", - "trt_visit 2", "lsm_ref_visit 2", "lsm_alt_visit 2" - ) - ) + expect_equal(result_expected, result_actual) + + expect_equal(sapply(results_actual, function(x) x[['name']]), + c("trt","lsm_ref", "lsm_alt","trt","lsm_ref","lsm_alt")) + + expect_equal(sapply(results_actual, function(x) x[['meta']]), + list(visit = "visit 1", visit = "visit 1", visit = "visit 1", visit = "visit 2", visit ="visit 2", visit ="visit 2")) + + expect_equal(sapply(results_actual, function(x) attr(x, 'meta')), + list(visit = "visit 1", visit = "visit 1", visit = "visit 1", visit = "visit 2", visit ="visit 2", visit ="visit 2")) ################## # diff --git a/tests/testthat/test-utilities.R b/tests/testthat/test-utilities.R index 05da2027b..a9c273f26 100644 --- a/tests/testthat/test-utilities.R +++ b/tests/testthat/test-utilities.R @@ -241,3 +241,158 @@ test_that("Stack", { expect_equal(mstack$pop(3), list(7)) expect_error(mstack$pop(1), "items to return") }) + + +test_that("add_meta", { + expect_equal(add_meta('a', 1), list(a=1)) + expect_equal(add_meta(c('a','b','c'), '1','2',3), list(a='1', b='2',c=3)) + expect_equal(add_meta(list('a'), 1), list(a=1)) + expect_error(add_meta(c('a','b','c'), 1, 'c')) + expect_error(add_meta(c('a','b','c'))) + expect_error(add_meta(NULL, 'a')) + expect_error(add_meta(NA, 'a')) + expect_error(add_meta(c('a', NA), 1, 2)) +}) + + +test_that("assert_type", { + expect_true(assert_type('a', is.character)) + expect_true(assert_type(c('a', 'b', 'c'), is.character)) + expect_true(assert_type(1, is.numeric)) + expect_true(assert_type(c(1,2,3), is.numeric)) + expect_true(assert_type(NA, is.na)) + expect_true(assert_type(NULL, is.null)) + expect_true(assert_type(list(), is.list)) + expect_true(assert_type(data.frame(), is.data.frame)) + expect_true(assert_type(environment(), is.environment)) + expect_true(assert_type(function() NULL, is.function)) + expect_true(assert_type(factor(), is.factor)) + expect_error(assert_type(factor('a'), is.character)) + expect_error(assert_type('a', is.numeric)) + expect_error(assert_type(1, is.null)) + v1 <- NULL + expect_error(assert_type(v1, is.character)) + v2 <- NA + expect_error(assert_type(v2, is.numeric)) + expect_error(assert_type(1, length)) +}) + + +test_that("assert_value", { + expect_true(assert_value(max)(c(1,2,3), 3)) + expect_true(assert_value(length)(list(1,2), 2)) + expect_true(assert_value(names)(list(a=1,b=2,c=3), c('a', 'b', 'c'))) + expect_true(assert_value(mean)(c(a=1,b=2,c=3), 2)) + expect_true(assert_value(abs)(c(a=-1,b=2,c=-3), c(1,2,3))) + f <- function(x) x * 5 + expect_true(assert_value(f)(c(a=1,b=2,c=3), c(a=5,b=10,c=15))) + expect_error(assert_value(identity)(list(a=1,b=2,c=3), list(a=1,b=2,c=3))) + expect_error(assert_value(min)(c(a=1,b=2,c=3), 7)) + expect_error(assert_value(median)(c(a=1,b=2,c=3), 1)) +}) + +test_that("assert_anares_length", { + expect_true(assert_anares_length('a', 1)) + expect_true(assert_anares_length(c(1,2), 2)) + expect_true(assert_anares_length(list(a=1,b=2), 2)) + expect_true(assert_anares_length(data.frame(a=1, b=2), 2)) + expect_true(assert_anares_length(list(), 0)) + expect_true(assert_anares_length(NULL, 0)) + expect_error(assert_anares_length(c('a', 'b', 'c'), 2)) + expect_error(assert_anares_length(c(1,2,3), 2)) + expect_error(assert_anares_length(list(), 2)) + expect_error(assert_anares_length(1, 2)) + expect_error(assert_anares_length(NA, 2)) +}) + +test_that("make_chain", { + is.numeric_or_na <- make_chain(any, is.numeric, is.na) + expect_true(is.numeric_or_na(NA)) + expect_true(is.numeric_or_na(1)) + expect_false(is.numeric_or_na('a')) + expect_false(is.numeric_or_na(list())) + expect_error(make_chain(any, is.numeric, 'a')(1)) +}) + +test_that("order_list_by_name", { + expect_equal(order_list_by_name(list(a=1,b='x',c=TRUE), c("c", "a", "d", "x", "b", "t"))[[3]], 'x') + expect_true(names(order_list_by_name(list(t=1,v='x'), c("c", "a", "d", "x", "b", "t"))) == 't') + expect_true(all(names(order_list_by_name(list(t=1,v='x',z=2,m=list(), q='t', w=data.frame()), c("z", "t","q", "m"))) == c("z", "t","q", "m"))) + expect_length(order_list_by_name(list(u=1,v='x'), c("c", "a", "d", "x", "b", "t")), 0) + expect_length(order_list_by_name(list(u=1,v='x'), c("c")), 0) +}) + +test_that("base_bind_rows", { + l1 <- list(list(a=1,b=2), list(b=3, c=4)) + expect_equal(nrow(base_bind_rows(l1)), 2) + expect_equal(ncol(base_bind_rows(l1)), 3) + expect_equal(base_bind_rows(l1)[1, 2], 2) + l2 <- list(list(a=1,b=2, c=3), list(a=1, b=3, c=4), list(a=9, b=10, c=12)) + expect_equal(nrow(base_bind_rows(l2)), 3) + expect_equal(base_bind_rows(l2)[2,3], 4) + expect_true(is.na(base_bind_rows(l1)[3,1])) + expect_error(base_bind_rows(list(list(a=1,b=2), list(b=3, c=data.frame())))) + l3 <- list(c(a=1,b=2), c(a=3, c=4)) + expect_error(base_bind_rows(l3)) +}) + +test_that("namechecker", { + chker <- namechecker('a', 'b', 'c', optional = c('d', 'e', 'f')) + expect_type(chker, "closure") + expect_equal(chker('musthave'), c('a', 'b', 'c')) + expect_equal(chker('optional'), c('d', 'e', 'f')) + expect_equal(chker('all'), c('a', 'b', 'c', 'd', 'e', 'f')) + expect_type(chker('musthave_in_objnames'), "closure") + expect_type(chker('objnames_in_musthave'), "closure") + expect_true(all(chker('musthave_in_objnames')(list(a=1)) == list(a=TRUE, b=FALSE, c=FALSE))) + expect_true(all(chker('musthave_in_objnames')(list(a=1, b = 2, c = 3, d = 4)) == list(a=TRUE, b=TRUE, c=TRUE))) + expect_true(all(chker('objnames_in_musthave')(list(b=2, a = 1, d = 2, e = 4, x = 5)) == list(b=TRUE, a=TRUE, d=TRUE, e = TRUE, x = FALSE))) + expect_true(all(chker('objnames_in_musthave')(list(f = 1, x = list(a=2))) == list(f=TRUE, x=FALSE))) + expect_true(all(chker('objnames_in_musthave')(list(y = 1, x = list(a=2))) == list(y=FALSE, x=FALSE))) +}) + +test_that("compose_n", { + # addition + add_one <- function (x) x + 1 + add_five <- compose_n(add_one, 5) + expect_equal(add_five(3), 8) + + # yin yang + flip <- function(x) -x + odd_numbers <- seq(1, 99, 2) + even_numbers <- seq(2, 100, 2) + yin <- sapply(odd_numbers, function(x) compose_n(flip, x)) + yang <- sapply(even_numbers, function(x) compose_n(flip, x)) + expect_true(all(sapply(yin, do.call, list(100)) < 0)) + expect_true(all(sapply(yang, do.call, list(100)) > 0)) + + # base case + expect_equal(compose_n(abs, 0)(-5), -5) +}) + +test_that("back_apply_at", { + x <- list( + a1=list( + b11=list(c111=1, c112=2,c113=3), + b12=list(c121=4, c122=5,c123=6)), + a2=list( + b21=list(c211=7, c212=8,c213=9), + b22=list(c221=10, c222=11,c223=12)) + ) + + y <- back_apply_at(x, function(x) x+1, 1) + expect_equal(y$a2$b22$c221, 11) + expect_equal(y$a1$b11$c113, 4) + expect_identical(y$a1$b12, list(c121=5, c122=6,c123=7)) + + z <- back_apply_at(x, class, 2) + expect_equal(z$a1$b11, "list") + expect_equal(z$a2$b21, "list") + expect_error(z$a1$b11$c111) + + zz <- back_apply_at(x, length, 3) + expect_equal(zz$a1, 2) + expect_equal(zz$a2, 2) + expect_error(zz$a2$b21) + expect_error(zz$a1$b12$c121) +})