diff options
Diffstat (limited to 'R/multistart.R')
-rw-r--r-- | R/multistart.R | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/R/multistart.R b/R/multistart.R new file mode 100644 index 00000000..61ef43dc --- /dev/null +++ b/R/multistart.R @@ -0,0 +1,224 @@ +#' Perform a hierarchical model fit with multiple starting values +#' +#' The purpose of this method is to check if a certain algorithm for fitting +#' nonlinear hierarchical models (also known as nonlinear mixed-effects models) +#' will reliably yield results that are sufficiently similar to each other, if +#' started with a certain range of reasonable starting parameters. It is +#' inspired by the article on practical identifiabiliy in the frame of nonlinear +#' mixed-effects models by Duchesne et al (2021). +#' +#' @param object The fit object to work with +#' @param n How many different combinations of starting parameters should be +#' used? +#' @param cores How many fits should be run in parallel (only on posix platforms)? +#' @param cluster A cluster as returned by [parallel::makeCluster] to be used +#' for parallel execution. +#' @param \dots Passed to the update function. +#' @param x The multistart object to print +#' @return A list of [saem.mmkin] objects, with class attributes +#' 'multistart.saem.mmkin' and 'multistart'. +#' @seealso [parplot], [llhist] +#' +#' @references Duchesne R, Guillemin A, Gandrillon O, Crauste F. Practical +#' identifiability in the frame of nonlinear mixed effects models: the example +#' of the in vitro erythropoiesis. BMC Bioinformatics. 2021 Oct 4;22(1):478. +#' doi: 10.1186/s12859-021-04373-4. +#' @export +#' @examples +#' \dontrun{ +#' library(mkin) +#' dmta_ds <- lapply(1:7, function(i) { +#' ds_i <- dimethenamid_2018$ds[[i]]$data +#' ds_i[ds_i$name == "DMTAP", "name"] <- "DMTA" +#' ds_i$time <- ds_i$time * dimethenamid_2018$f_time_norm[i] +#' ds_i +#' }) +#' names(dmta_ds) <- sapply(dimethenamid_2018$ds, function(ds) ds$title) +#' dmta_ds[["Elliot"]] <- rbind(dmta_ds[["Elliot 1"]], dmta_ds[["Elliot 2"]]) +#' dmta_ds[["Elliot 1"]] <- dmta_ds[["Elliot 2"]] <- NULL +#' +#' f_mmkin <- mmkin("DFOP", dmta_ds, error_model = "tc", cores = 7, quiet = TRUE) +#' f_saem_full <- saem(f_mmkin) +#' f_saem_full_multi <- multistart(f_saem_full, n = 16, cores = 16) +#' parplot(f_saem_full_multi, lpos = "topleft") +#' illparms(f_saem_full) +#' +#' f_saem_reduced <- update(f_saem_full, no_random_effect = "log_k2") +#' illparms(f_saem_reduced) +#' # On Windows, we need to create a cluster first. When working with +#' # such a cluster, we need to export the mmkin object to the cluster +#' # nodes, as it is referred to when updating the saem object on the nodes. +#' library(parallel) +#' cl <- makePSOCKcluster(12) +#' f_saem_reduced_multi <- multistart(f_saem_reduced, n = 16, cluster = cl) +#' parplot(f_saem_reduced_multi, lpos = "topright") +#' stopCluster(cl) +#' } +multistart <- function(object, n = 50, + cores = if (Sys.info()["sysname"] == "Windows") 1 else parallel::detectCores(), + cluster = NULL, ...) +{ + UseMethod("multistart", object) +} + +#' @rdname multistart +#' @export +multistart.saem.mmkin <- function(object, n = 50, cores = 1, + cluster = NULL, ...) { + call <- match.call() + if (n <= 1) stop("Please specify an n of at least 2") + + mmkin_object <- object$mmkin + + mmkin_parms <- parms(mmkin_object, errparms = FALSE, + transformed = object$transformations == "mkin") + start_parms <- apply( + mmkin_parms, 1, + function(x) stats::runif(n, min(x), max(x))) + + saem_call <- object$call + saem_call[[1]] <- saem + saem_call[[2]] <- mmkin_object + i_startparms <- which(names(saem_call) == "degparms_start") + + fit_function <- function(x) { + + new_startparms <- str2lang( + paste0(capture.output(dput(start_parms[x, ])), + collapse = "")) + + if (length(i_startparms) == 0) { + saem_call <- c(as.list(saem_call), degparms_start = new_startparms) + saem_call <- as.call(saem_call) + } else { + saem_call[i_startparms] <- new_startparms + } + + ret <- eval(saem_call) + + return(ret) + } + + if (is.null(cluster)) { + res <- parallel::mclapply(1:n, fit_function, mc.cores = cores) + } else { + res <- parallel::parLapply(cluster, 1:n, fit_function) + } + attr(res, "orig") <- object + attr(res, "start_parms") <- start_parms + attr(res, "call") <- call + class(res) <- c("multistart.saem.mmkin", "multistart") + return(res) +} + +#' @export +status.multistart <- function(object, ...) { + all_summary_warnings <- character() + + result <- lapply(object, + function(fit) { + if (inherits(fit, "try-error")) return("E") + else { + return("OK") + } + }) + result <- unlist(result) + + class(result) <- "status.multistart" + return(result) +} + +#' @export +status.multistart.saem.mmkin <- function(object, ...) { + all_summary_warnings <- character() + + result <- lapply(object, + function(fit) { + if (inherits(fit$so, "try-error")) return("E") + else { + return("OK") + } + }) + result <- unlist(result) + + class(result) <- "status.multistart" + return(result) +} + +#' @export +print.status.multistart <- function(x, ...) { + class(x) <- NULL + print(table(x, dnn = NULL)) + if (any(x == "OK")) cat("OK: Fit terminated successfully\n") + if (any(x == "E")) cat("E: Error\n") +} + +#' @rdname multistart +#' @export +print.multistart <- function(x, ...) { + cat("<multistart> object with", length(x), "fits:\n") + print(status(x)) +} + +#' @rdname multistart +#' @export +best <- function(object, ...) +{ + UseMethod("best", object) +} + +#' @export +#' @return The object with the highest likelihood +#' @rdname multistart +best.default <- function(object, ...) +{ + return(object[[which.best(object)]]) +} + +#' @return The index of the object with the highest likelihood +#' @rdname multistart +#' @export +which.best <- function(object, ...) +{ + UseMethod("which.best", object) +} + +#' @rdname multistart +#' @export +which.best.default <- function(object, ...) +{ + llfunc <- function(object) { + ret <- try(logLik(object)) + if (inherits(ret, "try-error")) return(NA) + else return(ret) + } + ll <- sapply(object, llfunc) + return(which.max(ll)) +} + +#' @export +update.multistart <- function(object, ..., evaluate = TRUE) { + call <- attr(object, "call") + # For some reason we get multistart.saem.mmkin in call[[1]] when using multistart + # from the loaded package so we need to fix this so we do not have to export + # multistart.saem.mmkin + call[[1]] <- multistart + + update_arguments <- match.call(expand.dots = FALSE)$... + + if (length(update_arguments) > 0) { + update_arguments_in_call <- !is.na(match(names(update_arguments), names(call))) + } + + for (a in names(update_arguments)[update_arguments_in_call]) { + call[[a]] <- update_arguments[[a]] + } + + update_arguments_not_in_call <- !update_arguments_in_call + if(any(update_arguments_not_in_call)) { + call <- c(as.list(call), update_arguments[update_arguments_not_in_call]) + call <- as.call(call) + } + if(evaluate) eval(call, parent.frame()) + else call +} |