From b5b446b718b15ccaae5b197e147fc1358f0f564e Mon Sep 17 00:00:00 2001 From: Johannes Ranke Date: Fri, 6 Nov 2020 00:03:29 +0100 Subject: Fast analytical solutions for saemix, update.mmkin Also, use logit transformation for g and for solitary formation fractions, addressing #10. --- NAMESPACE | 3 + NEWS.md | 4 + R/mkinerrmin.R | 2 +- R/mkinfit.R | 18 +- R/mmkin.R | 32 +- R/saemix.R | 213 ++++-- R/transform_odeparms.R | 61 +- docs/dev/news/index.html | 2 + docs/dev/pkgdown.yml | 2 +- docs/dev/reference/Rplot001.png | Bin 19062 -> 1011 bytes docs/dev/reference/Rplot002.png | Bin 24396 -> 17289 bytes docs/dev/reference/Rplot003.png | Bin 6583 -> 15451 bytes docs/dev/reference/Rplot004.png | Bin 28700 -> 13203 bytes docs/dev/reference/Rplot005.png | Bin 26806 -> 15024 bytes docs/dev/reference/Rplot006.png | Bin 26623 -> 13474 bytes docs/dev/reference/Rplot007.png | Bin 26730 -> 18546 bytes docs/dev/reference/Rplot008.png | Bin 0 -> 11762 bytes docs/dev/reference/Rplot009.png | Bin 0 -> 21414 bytes docs/dev/reference/Rplot010.png | Bin 0 -> 5643 bytes docs/dev/reference/Rplot011.png | Bin 0 -> 26192 bytes docs/dev/reference/Rplot012.png | Bin 0 -> 23686 bytes docs/dev/reference/Rplot013.png | Bin 0 -> 23097 bytes docs/dev/reference/Rplot014.png | Bin 0 -> 18299 bytes docs/dev/reference/Rplot015.png | Bin 0 -> 23097 bytes docs/dev/reference/mkinfit-1.png | Bin 0 -> 67810 bytes docs/dev/reference/mkinfit.html | 154 ++-- docs/dev/reference/mmkin-1.png | Bin 115683 -> 114048 bytes docs/dev/reference/mmkin-2.png | Bin 113464 -> 110392 bytes docs/dev/reference/mmkin-3.png | Bin 100799 -> 97556 bytes docs/dev/reference/mmkin-4.png | Bin 70430 -> 70005 bytes docs/dev/reference/mmkin-5.png | Bin 66958 -> 66093 bytes docs/dev/reference/mmkin.html | 87 ++- docs/dev/reference/saemix-1.png | Bin 50490 -> 46062 bytes docs/dev/reference/saemix-10.png | Bin 0 -> 17011 bytes docs/dev/reference/saemix-11.png | Bin 0 -> 64787 bytes docs/dev/reference/saemix-12.png | Bin 0 -> 59464 bytes docs/dev/reference/saemix-13.png | Bin 0 -> 7742 bytes docs/dev/reference/saemix-2.png | Bin 60503 -> 47804 bytes docs/dev/reference/saemix-3.png | Bin 18094 -> 40694 bytes docs/dev/reference/saemix-4.png | Bin 82011 -> 38144 bytes docs/dev/reference/saemix-5.png | Bin 0 -> 39988 bytes docs/dev/reference/saemix-6.png | Bin 0 -> 39720 bytes docs/dev/reference/saemix-7.png | Bin 0 -> 7742 bytes docs/dev/reference/saemix-8.png | Bin 0 -> 35501 bytes docs/dev/reference/saemix-9.png | Bin 0 -> 7742 bytes docs/dev/reference/saemix.html | 974 ++++++++++++++++++++------ docs/dev/reference/transform_odeparms.html | 159 +++-- man/mkinfit.Rd | 18 +- man/mmkin.Rd | 2 +- man/saemix.Rd | 61 +- man/transform_odeparms.Rd | 19 +- test.log | 25 +- tests/testthat/DFOP_FOCUS_C_messages.txt | 349 ++++----- tests/testthat/FOCUS_2006_D.csf | 2 +- tests/testthat/summary_DFOP_FOCUS_C.txt | 4 +- tests/testthat/test_schaefer07_complex_case.R | 4 +- 56 files changed, 1454 insertions(+), 741 deletions(-) create mode 100644 docs/dev/reference/Rplot008.png create mode 100644 docs/dev/reference/Rplot009.png create mode 100644 docs/dev/reference/Rplot010.png create mode 100644 docs/dev/reference/Rplot011.png create mode 100644 docs/dev/reference/Rplot012.png create mode 100644 docs/dev/reference/Rplot013.png create mode 100644 docs/dev/reference/Rplot014.png create mode 100644 docs/dev/reference/Rplot015.png create mode 100644 docs/dev/reference/mkinfit-1.png create mode 100644 docs/dev/reference/saemix-10.png create mode 100644 docs/dev/reference/saemix-11.png create mode 100644 docs/dev/reference/saemix-12.png create mode 100644 docs/dev/reference/saemix-13.png create mode 100644 docs/dev/reference/saemix-5.png create mode 100644 docs/dev/reference/saemix-6.png create mode 100644 docs/dev/reference/saemix-7.png create mode 100644 docs/dev/reference/saemix-8.png create mode 100644 docs/dev/reference/saemix-9.png diff --git a/NAMESPACE b/NAMESPACE index 9554a0d6..ef5b72e7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -31,6 +31,7 @@ S3method(residuals,mkinfit) S3method(summary,mkinfit) S3method(summary,nlme.mmkin) S3method(update,mkinfit) +S3method(update,mmkin) S3method(update,nlme.mmkin) export(CAKE_export) export(DFOP.solution) @@ -110,10 +111,12 @@ importFrom(stats,na.fail) importFrom(stats,nlminb) importFrom(stats,nobs) importFrom(stats,optimize) +importFrom(stats,plogis) importFrom(stats,predict) importFrom(stats,pt) importFrom(stats,qchisq) importFrom(stats,qf) +importFrom(stats,qlogis) importFrom(stats,qnorm) importFrom(stats,qt) importFrom(stats,residuals) diff --git a/NEWS.md b/NEWS.md index 706e0ae8..60276151 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # mkin 0.9.50.4 (unreleased) +- 'transform_odeparms', 'backtransform_odeparms': Use logit transformation for solitary fractions like the g parameter of the DFOP model, or formation fractions for a pathway to only one target variable + +- 'update' method for 'mmkin' objects + - 'plot', 'summary' and 'print' methods for 'nlme.mmkin' objects - 'saemix_model', 'saemix_data': Helper functions to fit nonlinear mixed-effects models for mmkin row objects using the saemix package diff --git a/R/mkinerrmin.R b/R/mkinerrmin.R index 1994aed0..f52692ba 100644 --- a/R/mkinerrmin.R +++ b/R/mkinerrmin.R @@ -107,7 +107,7 @@ mkinerrmin <- function(fit, alpha = 0.05) if (obs_var == fit$obs_vars[[1]]) { special_parms = c("alpha", "log_alpha", "beta", "log_beta", "k1", "log_k1", "k2", "log_k2", - "g", "g_ilr", "tb", "log_tb") + "g", "g_ilr", "g_qlogis", "tb", "log_tb") n.optim <- n.optim + length(intersect(special_parms, names(parms.optim))) } diff --git a/R/mkinfit.R b/R/mkinfit.R index 1b1bb73d..7fa1c56e 100644 --- a/R/mkinfit.R +++ b/R/mkinfit.R @@ -89,12 +89,11 @@ if(getRversion() >= '2.15.1') utils::globalVariables(c("name", "time", "value")) #' models and the break point tb of the HS model. If FALSE, zero is used as #' a lower bound for the rates in the optimisation. #' @param transform_fractions Boolean specifying if formation fractions -#' constants should be transformed in the model specification used in the -#' fitting for better compliance with the assumption of normal distribution -#' of the estimator. The default (TRUE) is to do transformations. If TRUE, -#' the g parameter of the DFOP and HS models are also transformed, as they -#' can also be seen as compositional data. The transformation used for these -#' transformations is the [ilr()] transformation. +#' should be transformed in the model specification used in the fitting for +#' better compliance with the assumption of normal distribution of the +#' estimator. The default (TRUE) is to do transformations. If TRUE, +#' the g parameter of the DFOP model is also transformed. Transformations +#' are described in [transform_odeparms]. #' @param quiet Suppress printing out the current value of the negative #' log-likelihood after each improvement? #' @param atol Absolute error tolerance, passed to [deSolve::ode()]. Default @@ -187,15 +186,14 @@ if(getRversion() >= '2.15.1') utils::globalVariables(c("name", "time", "value")) #' #' # Fit the model quietly to the FOCUS example dataset D using defaults #' fit <- mkinfit(SFO_SFO, FOCUS_D, quiet = TRUE) -#' # Since mkin 0.9.50.3, we get a warning about non-normality of residuals, -#' # so we try an alternative error model +#' plot_sep(fit) +#' # As lower parent values appear to have lower variance, we try an alternative error model #' fit.tc <- mkinfit(SFO_SFO, FOCUS_D, quiet = TRUE, error_model = "tc") #' # This avoids the warning, and the likelihood ratio test confirms it is preferable #' lrtest(fit.tc, fit) #' # We can also allow for different variances of parent and metabolite as error model #' fit.obs <- mkinfit(SFO_SFO, FOCUS_D, quiet = TRUE, error_model = "obs") -#' # This also avoids the warning about non-normality, but the two-component error model -#' # has significantly higher likelihood +#' # The two-component error model has significantly higher likelihood #' lrtest(fit.obs, fit.tc) #' parms(fit.tc) #' endpoints(fit.tc) diff --git a/R/mmkin.R b/R/mmkin.R index 6f088de0..f3c07e42 100644 --- a/R/mmkin.R +++ b/R/mmkin.R @@ -64,8 +64,9 @@ #' #' @export mmkin mmkin <- function(models = c("SFO", "FOMC", "DFOP"), datasets, - cores = detectCores(), cluster = NULL, ...) + cores = parallel::detectCores(), cluster = NULL, ...) { + call <- match.call() parent_models_available = c("SFO", "FOMC", "DFOP", "HS", "SFORB", "IORE", "logistic") n.m <- length(models) n.d <- length(datasets) @@ -100,12 +101,14 @@ mmkin <- function(models = c("SFO", "FOMC", "DFOP"), datasets, } if (is.null(cluster)) { - results <- parallel::mclapply(as.list(1:n.fits), fit_function, mc.cores = cores) + results <- parallel::mclapply(as.list(1:n.fits), fit_function, + mc.cores = cores, mc.preschedule = FALSE) } else { results <- parallel::parLapply(cluster, as.list(1:n.fits), fit_function) } attributes(results) <- attributes(fit_indices) + attr(results, "call") <- call class(results) <- "mmkin" return(results) } @@ -187,3 +190,28 @@ print.mmkin <- function(x, ...) { } } + +#' @export +update.mmkin <- function(object, ..., evaluate = TRUE) +{ + call <- attr(object, "call") + + 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 +} diff --git a/R/saemix.R b/R/saemix.R index f8714cf2..8632c1a4 100644 --- a/R/saemix.R +++ b/R/saemix.R @@ -20,47 +20,40 @@ #' @importFrom saemix saemixData saemixModel #' @importFrom stats var #' @examples +#' \dontrun{ +#' library(saemix) #' ds <- lapply(experimental_data_for_UBA_2019[6:10], #' function(x) subset(x$data[c("name", "time", "value")])) #' names(ds) <- paste("Dataset", 6:10) -#' sfo_sfo <- mkinmod(parent = mkinsub("SFO", "A1"), +#' f_mmkin_parent_p0_fixed <- mmkin("FOMC", ds, cores = 1, +#' state.ini = c(parent = 100), fixed_initials = "parent", quiet = TRUE) +#' m_saemix_p0_fixed <- saemix_model(f_mmkin_parent_p0_fixed["FOMC", ]) +#' d_saemix_parent <- saemix_data(f_mmkin_parent_p0_fixed) +#' saemix_options <- list(seed = 123456, displayProgress = FALSE, +#' save = FALSE, save.graphs = FALSE, nbiter.saemix = c(200, 80)) +#' f_saemix_p0_fixed <- saemix(m_saemix_p0_fixed, d_saemix_parent, saemix_options) +#' +#' f_mmkin_parent <- mmkin(c("SFO", "FOMC", "DFOP"), ds, quiet = TRUE) +#' m_saemix_sfo <- saemix_model(f_mmkin_parent["SFO", ]) +#' m_saemix_fomc <- saemix_model(f_mmkin_parent["FOMC", ]) +#' m_saemix_dfop <- saemix_model(f_mmkin_parent["DFOP", ]) +#' d_saemix_parent <- saemix_data(f_mmkin_parent["SFO", ]) +#' f_saemix_sfo <- saemix(m_saemix_sfo, d_saemix_parent, saemix_options) +#' f_saemix_fomc <- saemix(m_saemix_fomc, d_saemix_parent, saemix_options) +#' f_saemix_dfop <- saemix(m_saemix_dfop, d_saemix_parent, saemix_options) +#' compare.saemix(list(f_saemix_sfo, f_saemix_fomc, f_saemix_dfop)) +#' f_mmkin_parent_tc <- update(f_mmkin_parent, error_model = "tc") +#' m_saemix_fomc_tc <- saemix_model(f_mmkin_parent_tc["FOMC", ]) +#' f_saemix_fomc_tc <- saemix(m_saemix_fomc_tc, d_saemix_parent, saemix_options) +#' compare.saemix(list(f_saemix_fomc, f_saemix_fomc_tc)) +#' +#' dfop_sfo <- mkinmod(parent = mkinsub("DFOP", "A1"), #' A1 = mkinsub("SFO")) -#' \dontrun{ -#' f_mmkin <- mmkin(list("SFO-SFO" = sfo_sfo), ds, quiet = TRUE) -#' library(saemix) -#' m_saemix <- saemix_model(f_mmkin, cores = 1) +#' f_mmkin <- mmkin(list("DFOP-SFO" = dfop_sfo), ds, quiet = TRUE) +#' m_saemix <- saemix_model(f_mmkin) #' d_saemix <- saemix_data(f_mmkin) -#' saemix_options <- list(seed = 123456, -#' save = FALSE, save.graphs = FALSE, displayProgress = FALSE, -#' nbiter.saemix = c(200, 80)) #' f_saemix <- saemix(m_saemix, d_saemix, saemix_options) -#' plot(f_saemix, plot.type = "convergence") -#' } -#' # Synthetic data with two-component error -#' sampling_times = c(0, 1, 3, 7, 14, 28, 60, 90, 120) -#' dt50_sfo_in <- c(80, 90, 100, 111.111, 125) -#' k_in <- log(2) / dt50_sfo_in #' -#' SFO <- mkinmod(parent = mkinsub("SFO")) -#' -#' pred_sfo <- function(k) { -#' mkinpredict(SFO, c(k_parent = k), -#' c(parent = 100), sampling_times) -#' } -#' -#' ds_sfo_mean <- lapply(k_in, pred_sfo) -#' set.seed(123456L) -#' ds_sfo_syn <- lapply(ds_sfo_mean, function(ds) { -#' add_err(ds, sdfunc = function(value) sqrt(1^2 + value^2 * 0.07^2), -#' n = 1)[[1]] -#' }) -#' \dontrun{ -#' f_mmkin_syn <- mmkin("SFO", ds_sfo_syn, error_model = "tc", quiet = TRUE) -#' # plot(f_mmkin_syn) -#' m_saemix_tc <- saemix_model(f_mmkin_syn, cores = 1) -#' d_saemix_tc <- saemix_data(f_mmkin_syn) -#' f_saemix_tc <- saemix(m_saemix_tc, d_saemix_tc, saemix_options) -#' plot(f_saemix_tc, plot.type = "convergence") #' } #' @return An [saemix::SaemixModel] object. #' @export @@ -68,14 +61,14 @@ saemix_model <- function(object, cores = 1) { if (nrow(object) > 1) stop("Only row objects allowed") mkin_model <- object[[1]]$mkinmod - analytical <- is.function(mkin_model$deg_func) + solution_type <- object[[1]]$solution_type degparms_optim <- mean_degparms(object) - psi0 <- matrix(degparms_optim, nrow = 1) - colnames(psi0) <- names(degparms_optim) - degparms_fixed <- object[[1]]$bparms.fixed + # Transformations are done in the degradation function + transform.par = rep(0, length(degparms_optim)) + odeini_optim_parm_names <- grep('_0$', names(degparms_optim), value = TRUE) odeini_fixed_parm_names <- grep('_0$', names(degparms_fixed), value = TRUE) @@ -85,50 +78,114 @@ saemix_model <- function(object, cores = 1) { odeini_fixed <- degparms_fixed[odeini_fixed_parm_names] names(odeini_fixed) <- gsub('_0$', '', odeini_fixed_parm_names) - model_function <- function(psi, id, xidep) { + model_function <- FALSE + + if (length(mkin_model$spec) == 1 & mkin_model$use_of_ff == "max") { + parent_type <- mkin_model$spec[[1]]$type + if (length(odeini_fixed) == 1) { + if (parent_type == "SFO") { + stop("saemix needs at least two parameters to work on.") + } + if (parent_type == "FOMC") { + model_function <- function(psi, id, xidep) { + odeini_fixed / (xidep[, "time"]/exp(psi[id, 2]) + 1)^exp(psi[id, 1]) + } + } + if (parent_type == "DFOP") { + model_function <- function(psi, id, xidep) { + g <- plogis(psi[id, 3]) + t = xidep[, "time"] + odeini_fixed * (g * exp(- exp(psi[id, 1]) * t) + + (1 - g) * exp(- exp(psi[id, 2]) * t)) + } + } + if (parent_type == "HS") { + model_function <- function(psi, id, xidep) { + tb <- exp(psi[id, 3]) + t = xidep[, "time"] + k1 = exp(psi[id, 1]) + odeini_fixed * ifelse(t <= tb, + exp(- k1 * t), + exp(- k1 * t) * exp(- exp(psi[id, 2]) * (t - tb))) + } + } + } else { + if (length(odeparms_fixed) == 0) { + if (parent_type == "SFO") { + model_function <- function(psi, id, xidep) { + psi[id, 1] * exp( - exp(psi[id, 2]) * xidep[, "time"]) + } + } + if (parent_type == "FOMC") { + model_function <- function(psi, id, xidep) { + psi[id, 1] / (xidep[, "time"]/exp(psi[id, 3]) + 1)^exp(psi[id, 2]) + } + } + if (parent_type == "DFOP") { + model_function <- function(psi, id, xidep) { + g <- plogis(psi[id, 4]) + t = xidep[, "time"] + psi[id, 1] * (g * exp(- exp(psi[id, 2]) * t) + + (1 - g) * exp(- exp(psi[id, 3]) * t)) + } + } + if (parent_type == "HS") { + model_function <- function(psi, id, xidep) { + tb <- exp(psi[id, 4]) + t = xidep[, "time"] + k1 = exp(psi[id, 2]) + psi[id, 1] * ifelse(t <= tb, + exp(- k1 * t), + exp(- k1 * t) * exp(- exp(psi[id, 3]) * (t - tb))) + } + } + } + } + } - uid <- unique(id) + if (!is.function(model_function)) { + model_function <- function(psi, id, xidep) { - res_list <- parallel::mclapply(uid, function(i) { - transparms_optim <- psi[i, ] - names(transparms_optim) <- names(degparms_optim) + uid <- unique(id) - odeini_optim <- transparms_optim[odeini_optim_parm_names] - names(odeini_optim) <- gsub('_0$', '', odeini_optim_parm_names) + res_list <- parallel::mclapply(uid, function(i) { + transparms_optim <- psi[i, ] + names(transparms_optim) <- names(degparms_optim) - odeini <- c(odeini_optim, odeini_fixed)[names(mkin_model$diffs)] + odeini_optim <- transparms_optim[odeini_optim_parm_names] + names(odeini_optim) <- gsub('_0$', '', odeini_optim_parm_names) - ode_transparms_optim_names <- setdiff(names(transparms_optim), odeini_optim_parm_names) - odeparms_optim <- backtransform_odeparms(transparms_optim[ode_transparms_optim_names], mkin_model, - transform_rates = object[[1]]$transform_rates, - transform_fractions = object[[1]]$transform_fractions) - odeparms <- c(odeparms_optim, odeparms_fixed) + odeini <- c(odeini_optim, odeini_fixed)[names(mkin_model$diffs)] - xidep_i <- subset(xidep, id == i) + ode_transparms_optim_names <- setdiff(names(transparms_optim), odeini_optim_parm_names) + odeparms_optim <- backtransform_odeparms(transparms_optim[ode_transparms_optim_names], mkin_model, + transform_rates = object[[1]]$transform_rates, + transform_fractions = object[[1]]$transform_fractions) + odeparms <- c(odeparms_optim, odeparms_fixed) - if (analytical) { - out_values <- mkin_model$deg_func(xidep_i, odeini, odeparms) - } else { + xidep_i <- subset(xidep, id == i) - i_time <- xidep_i$time - i_name <- xidep_i$name + if (solution_type == "analytical") { + out_values <- mkin_model$deg_func(xidep_i, odeini, odeparms) + } else { - out_wide <- mkinpredict(mkin_model, - odeparms = odeparms, odeini = odeini, - solution_type = object[[1]]$solution_type, - outtimes = sort(unique(i_time))) + i_time <- xidep_i$time + i_name <- xidep_i$name - out_index <- cbind(as.character(i_time), as.character(i_name)) - out_values <- out_wide[out_index] - } - return(out_values) - }, mc.cores = cores) - res <- unlist(res_list) - return(res) - } + out_wide <- mkinpredict(mkin_model, + odeparms = odeparms, odeini = odeini, + solution_type = solution_type, + outtimes = sort(unique(i_time))) - raneff_0 <- mean_degparms(object, random = TRUE)$random$ds - var_raneff_0 <- apply(raneff_0, 2, var) + out_index <- cbind(as.character(i_time), as.character(i_name)) + out_values <- out_wide[out_index] + } + return(out_values) + }, mc.cores = cores) + res <- unlist(res_list) + return(res) + } + } error.model <- switch(object[[1]]$err_mod, const = "constant", @@ -136,7 +193,7 @@ saemix_model <- function(object, cores = 1) { obs = "constant") if (object[[1]]$err_mod == "obs") { - warning("The error model 'obs' (variance by variable) was not transferred to the saemix model") + warning("The error model 'obs' (variance by variable) can currently not be transferred to an saemix model") } error.init <- switch(object[[1]]$err_mod, @@ -145,11 +202,15 @@ saemix_model <- function(object, cores = 1) { b = mean(sapply(object, function(x) x$errparms[2]))), obs = c(a = mean(sapply(object, function(x) x$errparms)), b = 1)) - res <- saemixModel(model_function, psi0, + psi0_matrix <- matrix(degparms_optim, nrow = 1) + colnames(psi0_matrix) <- names(degparms_optim) + + res <- saemixModel(model_function, + psi0 = psi0_matrix, "Mixed model generated from mmkin object", - transform.par = rep(0, length(degparms_optim)), - error.model = error.model, error.init = error.init, - omega.init = diag(var_raneff_0) + transform.par = transform.par, + error.model = error.model, + error.init = error.init ) return(res) } diff --git a/R/transform_odeparms.R b/R/transform_odeparms.R index 0a25ee8c..f21d31fc 100644 --- a/R/transform_odeparms.R +++ b/R/transform_odeparms.R @@ -5,19 +5,19 @@ #' constants and other parameters that can only take on positive values, a #' simple log transformation is used. For compositional parameters, such as the #' formations fractions that should always sum up to 1 and can not be negative, -#' the \code{\link{ilr}} transformation is used. +#' the [ilr] transformation is used. #' #' The transformation of sets of formation fractions is fragile, as it supposes #' the same ordering of the components in forward and backward transformation. -#' This is no problem for the internal use in \code{\link{mkinfit}}. +#' This is no problem for the internal use in [mkinfit]. #' #' @param parms Parameters of kinetic models as used in the differential #' equations. #' @param transparms Transformed parameters of kinetic models as used in the #' fitting procedure. -#' @param mkinmod The kinetic model of class \code{\link{mkinmod}}, containing +#' @param mkinmod The kinetic model of class [mkinmod], containing #' the names of the model variables that are needed for grouping the -#' formation fractions before \code{\link{ilr}} transformation, the parameter +#' formation fractions before [ilr] transformation, the parameter #' names and the information if the pathway to sink is included in the model. #' @param transform_rates Boolean specifying if kinetic rate constants should #' be transformed in the model specification used in the fitting for better @@ -28,11 +28,15 @@ #' @param transform_fractions Boolean specifying if formation fractions #' constants should be transformed in the model specification used in the #' fitting for better compliance with the assumption of normal distribution -#' of the estimator. The default (TRUE) is to do transformations. The g -#' parameter of the DFOP and HS models are also transformed, as they can also -#' be seen as compositional data. The transformation used for these -#' transformations is the \code{\link{ilr}} transformation. +#' of the estimator. The default (TRUE) is to do transformations. +#' The g parameter of the DFOP model is also seen as a fraction. +#' If a single fraction is transformed (g parameter of DFOP or only a single +#' target variable e.g. a single metabolite plus a pathway to sink), a +#' logistic transformation is used [stats::qlogis()]. In other cases, i.e. if +#' two or more formation fractions need to be transformed whose sum cannot +#' exceed one, the [ilr] transformation is used. #' @return A vector of transformed or backtransformed parameters +#' @importFrom stats plogis qlogis #' @author Johannes Ranke #' @examples #' @@ -91,8 +95,7 @@ #' #' @export transform_odeparms transform_odeparms <- function(parms, mkinmod, - transform_rates = TRUE, - transform_fractions = TRUE) + transform_rates = TRUE, transform_fractions = TRUE) { # We need the model specification for the names of the model # variables and the information on the sink @@ -119,8 +122,7 @@ transform_odeparms <- function(parms, mkinmod, N <- parms[grep("^N", names(parms))] transparms[names(N)] <- N - # Go through state variables and apply isometric logratio transformation to - # formation fractions if requested + # Go through state variables and transform formation fractions if requested mod_vars = names(spec) for (box in mod_vars) { f <- parms[grep(paste("^f", box, sep = "_"), names(parms))] @@ -128,9 +130,14 @@ transform_odeparms <- function(parms, mkinmod, if (length(f) > 0) { if(transform_fractions) { if (spec[[box]]$sink) { - trans_f <- ilr(c(f, 1 - sum(f))) - trans_f_names <- paste("f", box, "ilr", 1:length(trans_f), sep = "_") - transparms[trans_f_names] <- trans_f + if (length(f) == 1) { + trans_f_name <- paste("f", box, "qlogis", sep = "_") + transparms[trans_f_name] <- qlogis(f) + } else { + trans_f <- ilr(c(f, 1 - sum(f))) + trans_f_names <- paste("f", box, "ilr", 1:length(trans_f), sep = "_") + transparms[trans_f_names] <- trans_f + } } else { if (length(f) > 1) { trans_f <- ilr(f) @@ -161,7 +168,7 @@ transform_odeparms <- function(parms, mkinmod, if (!is.na(parms["g"])) { g <- parms["g"] if (transform_fractions) { - transparms["g_ilr"] <- ilr(c(g, 1 - g)) + transparms["g_qlogis"] <- qlogis(g) } else { transparms["g"] <- g } @@ -207,20 +214,25 @@ backtransform_odeparms <- function(transparms, mkinmod, N <- transparms[grep("^N", names(transparms))] parms[names(N)] <- N - # Go through state variables and apply inverse isometric logratio transformation + # Go through state variables and apply inverse transformations to formation fractions mod_vars = names(spec) for (box in mod_vars) { # Get the names as used in the model f_names = grep(paste("^f", box, sep = "_"), mkinmod$parms, value = TRUE) + # Get the formation fraction parameters trans_f = transparms[grep(paste("^f", box, sep = "_"), names(transparms))] if (length(trans_f) > 0) { if(transform_fractions) { - f <- invilr(trans_f) - if (spec[[box]]$sink) { - parms[f_names] <- f[1:length(f)-1] + if (any(grepl("qlogis", names(trans_f)))) { + parms[f_names] <- plogis(trans_f) } else { - parms[f_names] <- f + f <- invilr(trans_f) + if (spec[[box]]$sink) { + parms[f_names] <- f[1:length(f)-1] + } else { + parms[f_names] <- f + } } } else { parms[names(trans_f)] <- trans_f @@ -242,7 +254,12 @@ backtransform_odeparms <- function(transparms, mkinmod, } } - # DFOP parameter g is treated as a fraction + # DFOP parameter g is now transformed using qlogis + if (!is.na(transparms["g_qlogis"])) { + g_qlogis <- transparms["g_qlogis"] + parms["g"] <- plogis(g_qlogis) + } + # In earlier times we used ilr for g, so we keep this around if (!is.na(transparms["g_ilr"])) { g_ilr <- transparms["g_ilr"] parms["g"] <- invilr(g_ilr)[1] diff --git a/docs/dev/news/index.html b/docs/dev/news/index.html index 4e31f4b7..3980ea88 100644 --- a/docs/dev/news/index.html +++ b/docs/dev/news/index.html @@ -146,6 +146,8 @@ mkin 0.9.50.4 (unreleased) Unreleased diff --git a/docs/dev/pkgdown.yml b/docs/dev/pkgdown.yml index 6d59c7cb..7b186c37 100644 --- a/docs/dev/pkgdown.yml +++ b/docs/dev/pkgdown.yml @@ -10,7 +10,7 @@ articles: web_only/NAFTA_examples: NAFTA_examples.html web_only/benchmarks: benchmarks.html web_only/compiled_models: compiled_models.html -last_built: 2020-11-05T07:25Z +last_built: 2020-11-05T22:53Z urls: reference: https://pkgdown.jrwb.de/mkin/reference article: https://pkgdown.jrwb.de/mkin/articles diff --git a/docs/dev/reference/Rplot001.png b/docs/dev/reference/Rplot001.png index 36b20f09..17a35806 100644 Binary files a/docs/dev/reference/Rplot001.png and b/docs/dev/reference/Rplot001.png differ diff --git a/docs/dev/reference/Rplot002.png b/docs/dev/reference/Rplot002.png index b568eb1c..a33ecd71 100644 Binary files a/docs/dev/reference/Rplot002.png and b/docs/dev/reference/Rplot002.png differ diff --git a/docs/dev/reference/Rplot003.png b/docs/dev/reference/Rplot003.png index 53415e5d..dd7e24f3 100644 Binary files a/docs/dev/reference/Rplot003.png and b/docs/dev/reference/Rplot003.png differ diff --git a/docs/dev/reference/Rplot004.png b/docs/dev/reference/Rplot004.png index ea685493..2057f883 100644 Binary files a/docs/dev/reference/Rplot004.png and b/docs/dev/reference/Rplot004.png differ diff --git a/docs/dev/reference/Rplot005.png b/docs/dev/reference/Rplot005.png index aceec1b5..b9285d56 100644 Binary files a/docs/dev/reference/Rplot005.png and b/docs/dev/reference/Rplot005.png differ diff --git a/docs/dev/reference/Rplot006.png b/docs/dev/reference/Rplot006.png index b3c9f16f..511c6b00 100644 Binary files a/docs/dev/reference/Rplot006.png and b/docs/dev/reference/Rplot006.png differ diff --git a/docs/dev/reference/Rplot007.png b/docs/dev/reference/Rplot007.png index 225931c4..a0d6336e 100644 Binary files a/docs/dev/reference/Rplot007.png and b/docs/dev/reference/Rplot007.png differ diff --git a/docs/dev/reference/Rplot008.png b/docs/dev/reference/Rplot008.png new file mode 100644 index 00000000..5d1701b8 Binary files /dev/null and b/docs/dev/reference/Rplot008.png differ diff --git a/docs/dev/reference/Rplot009.png b/docs/dev/reference/Rplot009.png new file mode 100644 index 00000000..7788b2d0 Binary files /dev/null and b/docs/dev/reference/Rplot009.png differ diff --git a/docs/dev/reference/Rplot010.png b/docs/dev/reference/Rplot010.png new file mode 100644 index 00000000..86ddd790 Binary files /dev/null and b/docs/dev/reference/Rplot010.png differ diff --git a/docs/dev/reference/Rplot011.png b/docs/dev/reference/Rplot011.png new file mode 100644 index 00000000..30861f3e Binary files /dev/null and b/docs/dev/reference/Rplot011.png differ diff --git a/docs/dev/reference/Rplot012.png b/docs/dev/reference/Rplot012.png new file mode 100644 index 00000000..4286bb2a Binary files /dev/null and b/docs/dev/reference/Rplot012.png differ diff --git a/docs/dev/reference/Rplot013.png b/docs/dev/reference/Rplot013.png new file mode 100644 index 00000000..65903441 Binary files /dev/null and b/docs/dev/reference/Rplot013.png differ diff --git a/docs/dev/reference/Rplot014.png b/docs/dev/reference/Rplot014.png new file mode 100644 index 00000000..fe2cf0d9 Binary files /dev/null and b/docs/dev/reference/Rplot014.png differ diff --git a/docs/dev/reference/Rplot015.png b/docs/dev/reference/Rplot015.png new file mode 100644 index 00000000..65903441 Binary files /dev/null and b/docs/dev/reference/Rplot015.png differ diff --git a/docs/dev/reference/mkinfit-1.png b/docs/dev/reference/mkinfit-1.png new file mode 100644 index 00000000..eed9064f Binary files /dev/null and b/docs/dev/reference/mkinfit-1.png differ diff --git a/docs/dev/reference/mkinfit.html b/docs/dev/reference/mkinfit.html index 39bd12cb..b46c2cce 100644 --- a/docs/dev/reference/mkinfit.html +++ b/docs/dev/reference/mkinfit.html @@ -307,12 +307,11 @@ a lower bound for the rates in the optimisation.

transform_fractions

Boolean specifying if formation fractions -constants should be transformed in the model specification used in the -fitting for better compliance with the assumption of normal distribution -of the estimator. The default (TRUE) is to do transformations. If TRUE, -the g parameter of the DFOP and HS models are also transformed, as they -can also be seen as compositional data. The transformation used for these -transformations is the ilr() transformation.

+should be transformed in the model specification used in the fitting for +better compliance with the assumption of normal distribution of the +estimator. The default (TRUE) is to do transformations. If TRUE, +the g parameter of the DFOP model is also transformed. Transformations +are described in transform_odeparms.

quiet @@ -433,15 +432,15 @@ Degradation Data. Environments 6(12) 124 summary(fit)
#> mkin version used for fitting: 0.9.50.4 #> R version used for fitting: 4.0.3 -#> Date of fit: Thu Nov 5 08:25:42 2020 -#> Date of summary: Thu Nov 5 08:25:42 2020 +#> Date of fit: Thu Nov 5 23:14:40 2020 +#> Date of summary: Thu Nov 5 23:14:40 2020 #> #> Equations: #> d_parent/dt = - (alpha/beta) * 1/((time/beta) + 1) * parent #> #> Model predictions using solution type analytical #> -#> Fitted using 222 model solutions performed in 0.049 s +#> Fitted using 222 model solutions performed in 0.05 s #> #> Error model: Constant variance #> @@ -521,8 +520,8 @@ Degradation Data. Environments 6(12) 124
#> Successfully compiled differential equation model from auto-generated C code.
# Fit the model quietly to the FOCUS example dataset D using defaults fit <- mkinfit(SFO_SFO, FOCUS_D, quiet = TRUE) -# Since mkin 0.9.50.3, we get a warning about non-normality of residuals, -# so we try an alternative error model +plot_sep(fit) +
# As lower parent values appear to have lower variance, we try an alternative error model fit.tc <- mkinfit(SFO_SFO, FOCUS_D, quiet = TRUE, error_model = "tc") # This avoids the warning, and the likelihood ratio test confirms it is preferable lrtest(fit.tc, fit) @@ -536,8 +535,7 @@ Degradation Data. Environments 6(12) 124 #> --- #> Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
# We can also allow for different variances of parent and metabolite as error model fit.obs <- mkinfit(SFO_SFO, FOCUS_D, quiet = TRUE, error_model = "obs") -# This also avoids the warning about non-normality, but the two-component error model -# has significantly higher likelihood +# The two-component error model has significantly higher likelihood lrtest(fit.obs, fit.tc)
#> Likelihood ratio test #> @@ -549,9 +547,9 @@ Degradation Data. Environments 6(12) 124 #> --- #> Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
parms(fit.tc)
#> parent_0 k_parent k_m1 f_parent_to_m1 sigma_low -#> 1.007343e+02 1.005562e-01 5.166712e-03 5.083933e-01 3.049891e-03 +#> 1.007343e+02 1.005562e-01 5.166712e-03 5.083933e-01 3.049884e-03 #> rsd_high -#> 7.928117e-02
endpoints(fit.tc) +#> 7.928118e-02
endpoints(fit.tc)
#> $ff #> parent_m1 parent_sink #> 0.5083933 0.4916067 @@ -559,7 +557,7 @@ Degradation Data. Environments 6(12) 124 #> $distimes #> DT50 DT90 #> parent 6.89313 22.89848 -#> m1 134.15635 445.65776 +#> m1 134.15634 445.65772 #>
# We can show a quick (only one replication) benchmark for this case, as we # have several alternative solution methods for the model. We skip @@ -576,9 +574,9 @@ Degradation Data. Environments 6(12) 124 solution_type = "analytical")) }
#> Loading required package: rbenchmark
#> test relative elapsed -#> 3 analytical 1.000 0.746 -#> 1 deSolve_compiled 2.288 1.707 -#> 2 eigen 2.708 2.020
# } +#> 3 analytical 1.000 0.532 +#> 1 deSolve_compiled 1.765 0.939 +#> 2 eigen 2.229 1.186
# } # Use stepwise fitting, using optimised parameters from parent only fit, FOMC-SFO # \dontrun{ @@ -588,21 +586,22 @@ Degradation Data. Environments 6(12) 124
#> Successfully compiled differential equation model from auto-generated C code.
fit.FOMC_SFO <- mkinfit(FOMC_SFO, FOCUS_D, quiet = TRUE) # Again, we get a warning and try a more sophisticated error model fit.FOMC_SFO.tc <- mkinfit(FOMC_SFO, FOCUS_D, quiet = TRUE, error_model = "tc") -# This model has a higher likelihood, but not significantly so +
#> Warning: Optimisation did not converge: +#> iteration limit reached without convergence (10)
# This model has a higher likelihood, but not significantly so lrtest(fit.tc, fit.FOMC_SFO.tc)
#> Likelihood ratio test #> #> Model 1: FOMC_SFO with error model tc and fixed parameter(s) m1_0 #> Model 2: SFO_SFO with error model tc and fixed parameter(s) m1_0 #> #Df LogLik Df Chisq Pr(>Chisq) -#> 1 7 -64.829 -#> 2 6 -64.983 -1 0.3075 0.5792
# Also, the missing standard error for log_beta and the t-tests for alpha +#> 1 7 -64.870 +#> 2 6 -64.983 -1 0.2259 0.6346
# Also, the missing standard error for log_beta and the t-tests for alpha # and beta indicate overparameterisation summary(fit.FOMC_SFO.tc, data = FALSE) -
#> Warning: NaNs produced
#> Warning: NaNs produced
#> Warning: diag(.) had 0 or NA entries; non-finite result is doubtful
#> mkin version used for fitting: 0.9.50.4 +
#> Warning: NaNs produced
#> Warning: NaNs produced
#> Warning: NaNs produced
#> Warning: diag(.) had 0 or NA entries; non-finite result is doubtful
#> mkin version used for fitting: 0.9.50.4 #> R version used for fitting: 4.0.3 -#> Date of fit: Thu Nov 5 08:25:56 2020 -#> Date of summary: Thu Nov 5 08:25:56 2020 +#> Date of fit: Thu Nov 5 23:14:51 2020 +#> Date of summary: Thu Nov 5 23:14:51 2020 #> #> Equations: #> d_parent/dt = - (alpha/beta) * 1/((time/beta) + 1) * parent @@ -611,7 +610,7 @@ Degradation Data. Environments 6(12) 124 #> #> Model predictions using solution type deSolve #> -#> Fitted using 3611 model solutions performed in 2.673 s +#> Fitted using 4273 model solutions performed in 3.081 s #> #> Error model: Two-component variance function #> @@ -629,80 +628,85 @@ Degradation Data. Environments 6(12) 124 #> rsd_high 0.10 error #> #> Starting values for the transformed parameters actually optimised: -#> value lower upper -#> parent_0 100.750000 -Inf Inf -#> log_k_m1 -2.302585 -Inf Inf -#> f_parent_ilr_1 0.000000 -Inf Inf -#> log_alpha 0.000000 -Inf Inf -#> log_beta 2.302585 -Inf Inf -#> sigma_low 0.100000 0 Inf -#> rsd_high 0.100000 0 Inf +#> value lower upper +#> parent_0 100.750000 -Inf Inf +#> log_k_m1 -2.302585 -Inf Inf +#> f_parent_qlogis 0.000000 -Inf Inf +#> log_alpha 0.000000 -Inf Inf +#> log_beta 2.302585 -Inf Inf +#> sigma_low 0.100000 0 Inf +#> rsd_high 0.100000 0 Inf #> #> Fixed parameter values: #> value type #> m1_0 0 state #> +#> +#> Warning(s): +#> Optimisation did not converge: +#> iteration limit reached without convergence (10) +#> #> Results: #> -#> AIC BIC logLik -#> 143.658 155.1211 -64.82902 +#> AIC BIC logLik +#> 143.7396 155.2027 -64.86982 #> #> Optimised, transformed parameters with symmetric confidence intervals: -#> Estimate Std. Error Lower Upper -#> parent_0 101.600000 2.6390000 96.240000 107.000000 -#> log_k_m1 -5.284000 0.0928900 -5.473000 -5.095000 -#> f_parent_ilr_1 0.001008 0.0541900 -0.109500 0.111500 -#> log_alpha 5.522000 0.0077300 5.506000 5.538000 -#> log_beta 7.806000 NaN NaN NaN -#> sigma_low 0.002488 0.0002431 0.001992 0.002984 -#> rsd_high 0.079210 0.0093280 0.060180 0.098230 +#> Estimate Std. Error Lower Upper +#> parent_0 1.016e+02 1.90600 97.7400 105.5000 +#> log_k_m1 -5.285e+00 0.09286 -5.4740 -5.0950 +#> f_parent_qlogis 6.482e-04 0.06164 -0.1251 0.1264 +#> log_alpha 5.467e+00 NaN NaN NaN +#> log_beta 7.750e+00 NaN NaN NaN +#> sigma_low 0.000e+00 NaN NaN NaN +#> rsd_high 7.989e-02 NaN NaN NaN #> #> Parameter correlation: -#> parent_0 log_k_m1 f_parent_ilr_1 log_alpha log_beta sigma_low -#> parent_0 1.000000 -0.094697 -0.76654 0.70525 NaN 0.016099 -#> log_k_m1 -0.094697 1.000000 0.51404 -0.14347 NaN 0.001576 -#> f_parent_ilr_1 -0.766543 0.514038 1.00000 -0.61368 NaN 0.015465 -#> log_alpha 0.705247 -0.143468 -0.61368 1.00000 NaN 5.871780 -#> log_beta NaN NaN NaN NaN 1 NaN -#> sigma_low 0.016099 0.001576 0.01546 5.87178 NaN 1.000000 -#> rsd_high 0.006566 -0.011662 -0.05353 0.04845 NaN -0.652554 -#> rsd_high -#> parent_0 0.006566 -#> log_k_m1 -0.011662 -#> f_parent_ilr_1 -0.053525 -#> log_alpha 0.048451 -#> log_beta NaN -#> sigma_low -0.652554 -#> rsd_high 1.000000 +#> parent_0 log_k_m1 f_parent_qlogis log_alpha log_beta +#> parent_0 1.0000000 -0.0002167 -0.6060 NaN NaN +#> log_k_m1 -0.0002167 1.0000000 0.5474 NaN NaN +#> f_parent_qlogis -0.6060320 0.5474423 1.0000 NaN NaN +#> log_alpha NaN NaN NaN 1 NaN +#> log_beta NaN NaN NaN NaN 1 +#> sigma_low NaN NaN NaN NaN NaN +#> rsd_high NaN NaN NaN NaN NaN +#> sigma_low rsd_high +#> parent_0 NaN NaN +#> log_k_m1 NaN NaN +#> f_parent_qlogis NaN NaN +#> log_alpha NaN NaN +#> log_beta NaN NaN +#> sigma_low 1 NaN +#> rsd_high NaN 1 #> #> Backtransformed parameters: #> Confidence intervals for internally transformed parameters are asymmetric. #> t-test (unrealistically) based on the assumption of normal distribution #> for estimators of untransformed parameters. #> Estimate t value Pr(>t) Lower Upper -#> parent_0 1.016e+02 32.7800 6.312e-26 9.624e+01 1.070e+02 -#> k_m1 5.072e-03 10.1200 1.216e-11 4.197e-03 6.130e-03 -#> f_parent_to_m1 5.004e-01 20.8300 4.318e-20 4.614e-01 5.394e-01 -#> alpha 2.502e+02 0.5624 2.889e-01 2.463e+02 2.542e+02 -#> beta 2.455e+03 0.5549 2.915e-01 NA NA -#> sigma_low 2.488e-03 0.4843 3.158e-01 1.992e-03 2.984e-03 -#> rsd_high 7.921e-02 8.4300 8.001e-10 6.018e-02 9.823e-02 +#> parent_0 1.016e+02 32.5400 7.812e-26 97.740000 1.055e+02 +#> k_m1 5.069e-03 10.0400 1.448e-11 0.004194 6.126e-03 +#> f_parent_to_m1 5.002e-01 20.7300 5.001e-20 0.468800 5.315e-01 +#> alpha 2.367e+02 0.6205 2.697e-01 NA NA +#> beta 2.322e+03 0.6114 2.727e-01 NA NA +#> sigma_low 0.000e+00 NaN NaN NaN NaN +#> rsd_high 7.989e-02 8.6630 4.393e-10 NaN NaN #> #> FOCUS Chi2 error levels in percent: #> err.min n.optim df -#> All data 6.781 5 14 -#> parent 7.141 3 6 -#> m1 4.640 2 8 +#> All data 6.782 5 14 +#> parent 7.142 3 6 +#> m1 4.639 2 8 #> #> Resulting formation fractions: #> ff -#> parent_m1 0.5004 -#> parent_sink 0.4996 +#> parent_m1 0.5002 +#> parent_sink 0.4998 #> #> Estimated disappearance times: -#> DT50 DT90 DT50back -#> parent 6.812 22.7 6.834 -#> m1 136.661 454.0 NA
+#> DT50 DT90 DT50back +#> parent 6.81 22.7 6.833 +#> m1 136.74 454.2 NA
# We can easily use starting parameters from the parent only fit (only for illustration) fit.FOMC = mkinfit("FOMC", FOCUS_2006_D, quiet = TRUE, error_model = "tc") fit.FOMC_SFO <- mkinfit(FOMC_SFO, FOCUS_D, quiet = TRUE, diff --git a/docs/dev/reference/mmkin-1.png b/docs/dev/reference/mmkin-1.png index 135d5446..7b7da90a 100644 Binary files a/docs/dev/reference/mmkin-1.png and b/docs/dev/reference/mmkin-1.png differ diff --git a/docs/dev/reference/mmkin-2.png b/docs/dev/reference/mmkin-2.png index 40109afc..ce2b2af4 100644 Binary files a/docs/dev/reference/mmkin-2.png and b/docs/dev/reference/mmkin-2.png differ diff --git a/docs/dev/reference/mmkin-3.png b/docs/dev/reference/mmkin-3.png index bce34531..bb96f1b2 100644 Binary files a/docs/dev/reference/mmkin-3.png and b/docs/dev/reference/mmkin-3.png differ diff --git a/docs/dev/reference/mmkin-4.png b/docs/dev/reference/mmkin-4.png index 02976ced..351b21aa 100644 Binary files a/docs/dev/reference/mmkin-4.png and b/docs/dev/reference/mmkin-4.png differ diff --git a/docs/dev/reference/mmkin-5.png b/docs/dev/reference/mmkin-5.png index 56750342..c1c05eea 100644 Binary files a/docs/dev/reference/mmkin-5.png and b/docs/dev/reference/mmkin-5.png differ diff --git a/docs/dev/reference/mmkin.html b/docs/dev/reference/mmkin.html index a5d7ba42..b0ca90f0 100644 --- a/docs/dev/reference/mmkin.html +++ b/docs/dev/reference/mmkin.html @@ -75,7 +75,7 @@ datasets specified in its first two arguments." /> mkin - 0.9.50.3 + 0.9.50.4
@@ -123,7 +123,7 @@ datasets specified in its first two arguments." />