Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
782d08b
add change function and lst2df function to utility
pengguanya Apr 27, 2022
670d82e
wrap pool to return data.frame instead of list
pengguanya Apr 27, 2022
4150c76
use analysis_results object in ancova instead of named list
pengguanya May 6, 2022
6bf4538
update analysis function to handle analysis_result object
pengguanya May 6, 2022
14f532c
add meta data to lsm0 and lsm1 in ancova
pengguanya May 6, 2022
22fd021
add util function of add meta
pengguanya May 6, 2022
caf8a2d
update example of analysis function in the header
pengguanya May 6, 2022
6a97460
update print.analysis function to print analysis info in a tabular fo…
pengguanya May 6, 2022
0f8c654
remove wrapper's call
pengguanya May 6, 2022
1e61805
fix import error %in% is in base not dplyr
pengguanya May 9, 2022
80d48c7
update docs
pengguanya May 9, 2022
5ef2b84
fix header example
pengguanya May 9, 2022
7860198
Merge branch '255-01' of github.com:insightsengineering/rbmi into 255-01
pengguanya May 9, 2022
e87eeee
add validation for object class and exceptoin handling
pengguanya May 9, 2022
2f5feef
fix exception handling
pengguanya May 9, 2022
9eba1d3
use assertthat instead of stopifnot and git rid of non-base functions…
pengguanya May 9, 2022
deb1ee9
add util funs
pengguanya May 10, 2022
9858161
update analysis
pengguanya May 10, 2022
606eeac
update doc
pengguanya May 10, 2022
1d25df0
udpate namespace
pengguanya May 10, 2022
4c04422
update base_bind_rows and use it in analysis instead of dplyr::bind_rows
pengguanya May 11, 2022
cde28a1
move namechecker function from analyse.R to utilities.R since it is m…
pengguanya May 11, 2022
453f13a
use assert_that instead of stopifnot
pengguanya May 11, 2022
02f37be
lazily evaluate namechecker to avoid name error
pengguanya May 11, 2022
ac96498
fix header
pengguanya May 11, 2022
1dc88fd
update header
pengguanya May 11, 2022
2e282c1
use base R instead of rlang. add more paramters to function
pengguanya May 11, 2022
d0b3cff
update doc
pengguanya May 11, 2022
4d276e4
update header and comment
pengguanya May 11, 2022
e6f6091
use anonymous function instead of partial function to avoid using purrr
pengguanya May 11, 2022
5b51468
change keyword parameter to position parameter so that when required …
pengguanya May 11, 2022
23d23e1
add assert functions
pengguanya May 11, 2022
0cfd00c
update doc
pengguanya May 11, 2022
eff5d4a
add test
pengguanya May 12, 2022
5453a16
update test
pengguanya May 12, 2022
2f6f9b5
use better variable name and update function header
pengguanya May 13, 2022
ecb1e6e
udpate add_meta to change how parameter is called and make it more ro…
pengguanya May 13, 2022
0e16edb
allow NA for se and use anyNA instead of is.na to be more robust
pengguanya May 13, 2022
dd8a6c5
change se from required to optional parameter for analysis_result
pengguanya May 13, 2022
059a2cb
update header and doc
pengguanya May 13, 2022
1e4a8fc
update doc
pengguanya May 13, 2022
fe8977e
not export as_analysis_result
pengguanya May 18, 2022
09b4b58
add test for new analysis functions
pengguanya May 18, 2022
a687a2c
modify test for original analysis functions
pengguanya May 19, 2022
74ba1d6
update namespace
pengguanya May 19, 2022
7de156f
update utils
pengguanya May 20, 2022
9330ff8
doc for new utils funcionts
pengguanya May 20, 2022
bbdc170
update check in as_analysis
pengguanya May 20, 2022
1a0d47c
update test for analysis functions
pengguanya May 20, 2022
f0e88b1
fix wrong counting of analysis names
pengguanya May 20, 2022
2f7956d
add test for util functions compose_n and back_apply_at
pengguanya May 21, 2022
56e9b65
add extract analysis result function
pengguanya May 21, 2022
5b4b53a
update test-ancova
pengguanya May 21, 2022
302f351
add assertation to extract_analysis_result
pengguanya May 22, 2022
02975cb
update test ancova
pengguanya May 22, 2022
ebc556f
update all tests functions in test-ancova
pengguanya May 23, 2022
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
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
305 changes: 298 additions & 7 deletions R/analyse.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]])),
""
)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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()
})
}
Loading