1 |
utils::globalVariables(c("predicted", "std")) |
|
2 | ||
3 |
#' Fit nonlinear mixed models with SAEM |
|
4 |
#' |
|
5 |
#' This function uses [saemix::saemix()] as a backend for fitting nonlinear mixed |
|
6 |
#' effects models created from [mmkin] row objects using the Stochastic Approximation |
|
7 |
#' Expectation Maximisation algorithm (SAEM). |
|
8 |
#' |
|
9 |
#' An mmkin row object is essentially a list of mkinfit objects that have been |
|
10 |
#' obtained by fitting the same model to a list of datasets using [mkinfit]. |
|
11 |
#' |
|
12 |
#' Starting values for the fixed effects (population mean parameters, argument |
|
13 |
#' psi0 of [saemix::saemixModel()] are the mean values of the parameters found |
|
14 |
#' using [mmkin]. |
|
15 |
#' |
|
16 |
#' @importFrom utils packageVersion |
|
17 |
#' @importFrom saemix saemix |
|
18 |
#' @param object An [mmkin] row object containing several fits of the same |
|
19 |
#' [mkinmod] model to different datasets |
|
20 |
#' @param verbose Should we print information about created objects of |
|
21 |
#' type [saemix::SaemixModel] and [saemix::SaemixData]? |
|
22 |
#' @param transformations Per default, all parameter transformations are done |
|
23 |
#' in mkin. If this argument is set to 'saemix', parameter transformations |
|
24 |
#' are done in 'saemix' for the supported cases, i.e. (as of version 1.1.2) |
|
25 |
#' SFO, FOMC, DFOP and HS without fixing `parent_0`, and SFO or DFOP with |
|
26 |
#' one SFO metabolite. |
|
27 |
#' @param error_model Possibility to override the error model used in the mmkin object |
|
28 |
#' @param degparms_start Parameter values given as a named numeric vector will |
|
29 |
#' be used to override the starting values obtained from the 'mmkin' object. |
|
30 |
#' @param test_log_parms If TRUE, an attempt is made to use more robust starting |
|
31 |
#' values for population parameters fitted as log parameters in mkin (like |
|
32 |
#' rate constants) by only considering rate constants that pass the t-test |
|
33 |
#' when calculating mean degradation parameters using [mean_degparms]. |
|
34 |
#' @param conf.level Possibility to adjust the required confidence level |
|
35 |
#' for parameter that are tested if requested by 'test_log_parms'. |
|
36 |
#' @param solution_type Possibility to specify the solution type in case the |
|
37 |
#' automatic choice is not desired |
|
38 |
#' @param no_random_effect Character vector of degradation parameters for |
|
39 |
#' which there should be no variability over the groups. Only used |
|
40 |
#' if the covariance model is not explicitly specified. |
|
41 |
#' @param covariance.model Will be passed to [saemix::saemixModel()]. Per |
|
42 |
#' default, uncorrelated random effects are specified for all degradation |
|
43 |
#' parameters. |
|
44 |
#' @param omega.init Will be passed to [saemix::saemixModel()]. If using |
|
45 |
#' mkin transformations and the default covariance model with optionally |
|
46 |
#' excluded random effects, the variances of the degradation parameters |
|
47 |
#' are estimated using [mean_degparms], with testing of untransformed |
|
48 |
#' log parameters for significant difference from zero. If not using |
|
49 |
#' mkin transformations or a custom covariance model, the default |
|
50 |
#' initialisation of [saemix::saemixModel] is used for omega.init. |
|
51 |
#' @param covariates A data frame with covariate data for use in |
|
52 |
#' 'covariate_models', with dataset names as row names. |
|
53 |
#' @param covariate_models A list containing linear model formulas with one explanatory |
|
54 |
#' variable, i.e. of the type 'parameter ~ covariate'. Covariates must be available |
|
55 |
#' in the 'covariates' data frame. |
|
56 |
#' @param error.init Will be passed to [saemix::saemixModel()]. |
|
57 |
#' @param quiet Should we suppress the messages saemix prints at the beginning |
|
58 |
#' and the end of the optimisation process? |
|
59 |
#' @param nbiter.saemix Convenience option to increase the number of |
|
60 |
#' iterations |
|
61 |
#' @param control Passed to [saemix::saemix]. |
|
62 |
#' @param \dots Further parameters passed to [saemix::saemixModel]. |
|
63 |
#' @return An S3 object of class 'saem.mmkin', containing the fitted |
|
64 |
#' [saemix::SaemixObject] as a list component named 'so'. The |
|
65 |
#' object also inherits from 'mixed.mmkin'. |
|
66 |
#' @seealso [summary.saem.mmkin] [plot.mixed.mmkin] |
|
67 |
#' @examples |
|
68 |
#' \dontrun{ |
|
69 |
#' ds <- lapply(experimental_data_for_UBA_2019[6:10], |
|
70 |
#' function(x) subset(x$data[c("name", "time", "value")])) |
|
71 |
#' names(ds) <- paste("Dataset", 6:10) |
|
72 |
#' f_mmkin_parent_p0_fixed <- mmkin("FOMC", ds, |
|
73 |
#' state.ini = c(parent = 100), fixed_initials = "parent", quiet = TRUE) |
|
74 |
#' f_saem_p0_fixed <- saem(f_mmkin_parent_p0_fixed) |
|
75 |
#' |
|
76 |
#' f_mmkin_parent <- mmkin(c("SFO", "FOMC", "DFOP"), ds, quiet = TRUE) |
|
77 |
#' f_saem_sfo <- saem(f_mmkin_parent["SFO", ]) |
|
78 |
#' f_saem_fomc <- saem(f_mmkin_parent["FOMC", ]) |
|
79 |
#' f_saem_dfop <- saem(f_mmkin_parent["DFOP", ]) |
|
80 |
#' anova(f_saem_sfo, f_saem_fomc, f_saem_dfop) |
|
81 |
#' anova(f_saem_sfo, f_saem_dfop, test = TRUE) |
|
82 |
#' illparms(f_saem_dfop) |
|
83 |
#' f_saem_dfop_red <- update(f_saem_dfop, no_random_effect = "g_qlogis") |
|
84 |
#' anova(f_saem_dfop, f_saem_dfop_red, test = TRUE) |
|
85 |
#' |
|
86 |
#' anova(f_saem_sfo, f_saem_fomc, f_saem_dfop) |
|
87 |
#' # The returned saem.mmkin object contains an SaemixObject, therefore we can use |
|
88 |
#' # functions from saemix |
|
89 |
#' library(saemix) |
|
90 |
#' compare.saemix(f_saem_sfo$so, f_saem_fomc$so, f_saem_dfop$so) |
|
91 |
#' plot(f_saem_fomc$so, plot.type = "convergence") |
|
92 |
#' plot(f_saem_fomc$so, plot.type = "individual.fit") |
|
93 |
#' plot(f_saem_fomc$so, plot.type = "npde") |
|
94 |
#' plot(f_saem_fomc$so, plot.type = "vpc") |
|
95 |
#' |
|
96 |
#' f_mmkin_parent_tc <- update(f_mmkin_parent, error_model = "tc") |
|
97 |
#' f_saem_fomc_tc <- saem(f_mmkin_parent_tc["FOMC", ]) |
|
98 |
#' anova(f_saem_fomc, f_saem_fomc_tc, test = TRUE) |
|
99 |
#' |
|
100 |
#' sfo_sfo <- mkinmod(parent = mkinsub("SFO", "A1"), |
|
101 |
#' A1 = mkinsub("SFO")) |
|
102 |
#' fomc_sfo <- mkinmod(parent = mkinsub("FOMC", "A1"), |
|
103 |
#' A1 = mkinsub("SFO")) |
|
104 |
#' dfop_sfo <- mkinmod(parent = mkinsub("DFOP", "A1"), |
|
105 |
#' A1 = mkinsub("SFO")) |
|
106 |
#' # The following fit uses analytical solutions for SFO-SFO and DFOP-SFO, |
|
107 |
#' # and compiled ODEs for FOMC that are much slower |
|
108 |
#' f_mmkin <- mmkin(list( |
|
109 |
#' "SFO-SFO" = sfo_sfo, "FOMC-SFO" = fomc_sfo, "DFOP-SFO" = dfop_sfo), |
|
110 |
#' ds, quiet = TRUE) |
|
111 |
#' # saem fits of SFO-SFO and DFOP-SFO to these data take about five seconds |
|
112 |
#' # each on this system, as we use analytical solutions written for saemix. |
|
113 |
#' # When using the analytical solutions written for mkin this took around |
|
114 |
#' # four minutes |
|
115 |
#' f_saem_sfo_sfo <- saem(f_mmkin["SFO-SFO", ]) |
|
116 |
#' f_saem_dfop_sfo <- saem(f_mmkin["DFOP-SFO", ]) |
|
117 |
#' # We can use print, plot and summary methods to check the results |
|
118 |
#' print(f_saem_dfop_sfo) |
|
119 |
#' plot(f_saem_dfop_sfo) |
|
120 |
#' summary(f_saem_dfop_sfo, data = TRUE) |
|
121 |
#' |
|
122 |
#' # The following takes about 6 minutes |
|
123 |
#' f_saem_dfop_sfo_deSolve <- saem(f_mmkin["DFOP-SFO", ], solution_type = "deSolve", |
|
124 |
#' nbiter.saemix = c(200, 80)) |
|
125 |
#' |
|
126 |
#' #anova( |
|
127 |
#' # f_saem_dfop_sfo, |
|
128 |
#' # f_saem_dfop_sfo_deSolve)) |
|
129 |
#' |
|
130 |
#' # If the model supports it, we can also use eigenvalue based solutions, which |
|
131 |
#' # take a similar amount of time |
|
132 |
#' #f_saem_sfo_sfo_eigen <- saem(f_mmkin["SFO-SFO", ], solution_type = "eigen", |
|
133 |
#' # control = list(nbiter.saemix = c(200, 80), nbdisplay = 10)) |
|
134 |
#' } |
|
135 |
#' @export |
|
136 | 2411x |
saem <- function(object, ...) UseMethod("saem") |
137 | ||
138 |
#' @rdname saem |
|
139 |
#' @export |
|
140 |
saem.mmkin <- function(object, |
|
141 |
transformations = c("mkin", "saemix"), |
|
142 |
error_model = "auto", |
|
143 |
degparms_start = numeric(), |
|
144 |
test_log_parms = TRUE, |
|
145 |
conf.level = 0.6, |
|
146 |
solution_type = "auto", |
|
147 |
covariance.model = "auto", |
|
148 |
omega.init = "auto", |
|
149 |
covariates = NULL, |
|
150 |
covariate_models = NULL, |
|
151 |
no_random_effect = NULL, |
|
152 |
error.init = c(1, 1), |
|
153 |
nbiter.saemix = c(300, 100), |
|
154 |
control = list(displayProgress = FALSE, print = FALSE, |
|
155 |
nbiter.saemix = nbiter.saemix, |
|
156 |
save = FALSE, save.graphs = FALSE), |
|
157 |
verbose = FALSE, quiet = FALSE, ...) |
|
158 |
{ |
|
159 | 2411x |
call <- match.call() |
160 | 2411x |
transformations <- match.arg(transformations) |
161 | 2411x |
m_saemix <- saemix_model(object, verbose = verbose, |
162 | 2411x |
error_model = error_model, |
163 | 2411x |
degparms_start = degparms_start, |
164 | 2411x |
test_log_parms = test_log_parms, conf.level = conf.level, |
165 | 2411x |
solution_type = solution_type, |
166 | 2411x |
transformations = transformations, |
167 | 2411x |
covariance.model = covariance.model, |
168 | 2411x |
omega.init = omega.init, |
169 | 2411x |
covariates = covariates, |
170 | 2411x |
covariate_models = covariate_models, |
171 | 2411x |
error.init = error.init, |
172 | 2411x |
no_random_effect = no_random_effect, |
173 |
...) |
|
174 | 2187x |
d_saemix <- saemix_data(object, covariates = covariates, verbose = verbose) |
175 | ||
176 | 2187x |
fit_failed <- FALSE |
177 | 2187x |
FIM_failed <- NULL |
178 | 2187x |
fit_time <- system.time({ |
179 | 2187x |
utils::capture.output(f_saemix <- try(saemix(m_saemix, d_saemix, control)), split = !quiet) |
180 | ! |
if (inherits(f_saemix, "try-error")) fit_failed <- TRUE |
181 |
}) |
|
182 | ||
183 | 2187x |
return_data <- nlme_data(object) |
184 | ||
185 | 2187x |
if (!fit_failed) { |
186 | ! |
if (any(is.na(f_saemix@results@se.fixed))) FIM_failed <- c(FIM_failed, "fixed effects") |
187 | 2187x |
if (any(is.na(c(f_saemix@results@se.omega, f_saemix@results@se.respar)))) { |
188 | ! |
FIM_failed <- c(FIM_failed, "random effects and error model parameters") |
189 |
} |
|
190 | ||
191 | 2187x |
transparms_optim <- f_saemix@results@fixed.effects |
192 | 2187x |
names(transparms_optim) <- f_saemix@results@name.fixed |
193 | ||
194 | 2187x |
if (transformations == "mkin") { |
195 | 1413x |
bparms_optim <- backtransform_odeparms(transparms_optim, |
196 | 1413x |
object[[1]]$mkinmod, |
197 | 1413x |
object[[1]]$transform_rates, |
198 | 1413x |
object[[1]]$transform_fractions) |
199 |
} else { |
|
200 | 774x |
bparms_optim <- transparms_optim |
201 |
} |
|
202 | ||
203 | 2187x |
saemix_data_ds <- f_saemix@data@data$ds |
204 | 2187x |
mkin_ds_order <- as.character(unique(return_data$ds)) |
205 | 2187x |
saemix_ds_order <- unique(saemix_data_ds) |
206 | ||
207 | 2187x |
psi <- saemix::psi(f_saemix) |
208 | 2187x |
rownames(psi) <- saemix_ds_order |
209 | 2187x |
return_data$predicted <- f_saemix@model@model( |
210 | 2187x |
psi = psi[mkin_ds_order, ], |
211 | 2187x |
id = as.numeric(return_data$ds), |
212 | 2187x |
xidep = return_data[c("time", "name")]) |
213 | ||
214 | 2187x |
return_data <- transform(return_data, |
215 | 2187x |
residual = value - predicted, |
216 | 2187x |
std = sigma_twocomp(predicted, |
217 | 2187x |
f_saemix@results@respar[1], f_saemix@results@respar[2])) |
218 | 2187x |
return_data <- transform(return_data, |
219 | 2187x |
standardized = residual / std) |
220 |
} |
|
221 | ||
222 | 2187x |
result <- list( |
223 | 2187x |
mkinmod = object[[1]]$mkinmod, |
224 | 2187x |
mmkin = object, |
225 | 2187x |
solution_type = object[[1]]$solution_type, |
226 | 2187x |
transformations = transformations, |
227 | 2187x |
transform_rates = object[[1]]$transform_rates, |
228 | 2187x |
transform_fractions = object[[1]]$transform_fractions, |
229 | 2187x |
covariates = covariates, |
230 | 2187x |
covariate_models = covariate_models, |
231 | 2187x |
sm = m_saemix, |
232 | 2187x |
so = f_saemix, |
233 | 2187x |
call = call, |
234 | 2187x |
time = fit_time, |
235 | 2187x |
FIM_failed = FIM_failed, |
236 | 2187x |
mean_dp_start = attr(m_saemix, "mean_dp_start"), |
237 | 2187x |
bparms.fixed = object[[1]]$bparms.fixed, |
238 | 2187x |
data = return_data, |
239 | 2187x |
err_mod = object[[1]]$err_mod, |
240 | 2187x |
date.fit = date(), |
241 | 2187x |
saemixversion = as.character(utils::packageVersion("saemix")), |
242 | 2187x |
mkinversion = as.character(utils::packageVersion("mkin")), |
243 | 2187x |
Rversion = paste(R.version$major, R.version$minor, sep=".") |
244 |
) |
|
245 | ||
246 | 2187x |
if (!fit_failed) { |
247 | 2187x |
result$mkin_ds_order <- mkin_ds_order |
248 | 2187x |
result$saemix_ds_order <- saemix_ds_order |
249 | 2187x |
result$bparms.optim <- bparms_optim |
250 |
} |
|
251 | ||
252 | 2187x |
class(result) <- c("saem.mmkin", "mixed.mmkin") |
253 | 2187x |
return(result) |
254 |
} |
|
255 | ||
256 |
#' @export |
|
257 |
#' @rdname saem |
|
258 |
#' @param x An saem.mmkin object to print |
|
259 |
#' @param digits Number of digits to use for printing |
|
260 |
print.saem.mmkin <- function(x, digits = max(3, getOption("digits") - 3), ...) { |
|
261 | 234x |
cat( "Kinetic nonlinear mixed-effects model fit by SAEM" ) |
262 | 234x |
cat("\nStructural model:\n") |
263 | 234x |
diffs <- x$mmkin[[1]]$mkinmod$diffs |
264 | 234x |
nice_diffs <- gsub("^(d.*) =", "\\1/dt =", diffs) |
265 | 234x |
writeLines(strwrap(nice_diffs, exdent = 11)) |
266 | 234x |
cat("\nData:\n") |
267 | 234x |
cat(nrow(x$data), "observations of", |
268 | 234x |
length(unique(x$data$name)), "variable(s) grouped in", |
269 | 234x |
length(unique(x$data$ds)), "datasets\n") |
270 | ||
271 | 234x |
if (inherits(x$so, "try-error")) { |
272 | ! |
cat("\nFit did not terminate successfully\n") |
273 |
} else { |
|
274 | 234x |
cat("\nLikelihood computed by importance sampling\n") |
275 | 234x |
ll <- try(logLik(x$so, type = "is"), silent = TRUE) |
276 | 234x |
if (inherits(ll, "try-error")) { |
277 | ! |
cat("Not available\n") |
278 |
} else { |
|
279 | 234x |
print(data.frame( |
280 | 234x |
AIC = AIC(x$so, type = "is"), |
281 | 234x |
BIC = BIC(x$so, type = "is"), |
282 | 234x |
logLik = logLik(x$so, type = "is"), |
283 | 234x |
row.names = " "), digits = digits) |
284 |
} |
|
285 | ||
286 | 234x |
cat("\nFitted parameters:\n") |
287 | 234x |
conf.int <- parms(x, ci = TRUE) |
288 | 234x |
print(conf.int, digits = digits) |
289 |
} |
|
290 | ||
291 | 234x |
invisible(x) |
292 |
} |
|
293 | ||
294 |
#' @rdname saem |
|
295 |
#' @return An [saemix::SaemixModel] object. |
|
296 |
#' @export |
|
297 |
saemix_model <- function(object, solution_type = "auto", |
|
298 |
transformations = c("mkin", "saemix"), error_model = "auto", |
|
299 |
degparms_start = numeric(), |
|
300 |
covariance.model = "auto", no_random_effect = NULL, |
|
301 |
omega.init = "auto", |
|
302 |
covariates = NULL, covariate_models = NULL, |
|
303 |
error.init = numeric(), |
|
304 |
test_log_parms = FALSE, conf.level = 0.6, verbose = FALSE, ...) |
|
305 |
{ |
|
306 | 65x |
if (nrow(object) > 1) stop("Only row objects allowed") |
307 | ||
308 | 2346x |
mkin_model <- object[[1]]$mkinmod |
309 | ||
310 | 2346x |
if (length(mkin_model$spec) > 1 & solution_type[1] == "analytical") { |
311 | 104x |
stop("mkin analytical solutions not supported for more thane one observed variable") |
312 |
} |
|
313 | ||
314 | 2242x |
degparms_optim <- mean_degparms(object, test_log_parms = test_log_parms) |
315 | 2242x |
na_degparms <- names(which(is.na(degparms_optim))) |
316 | 2242x |
if (length(na_degparms) > 0) { |
317 | ! |
message("Did not find valid starting values for ", paste(na_degparms, collapse = ", "), "\n", |
318 | ! |
"Now trying with test_log_parms = FALSE") |
319 | ! |
degparms_optim <- mean_degparms(object, test_log_parms = FALSE) |
320 |
} |
|
321 | 2242x |
if (transformations == "saemix") { |
322 | 779x |
degparms_optim <- backtransform_odeparms(degparms_optim, |
323 | 779x |
object[[1]]$mkinmod, |
324 | 779x |
object[[1]]$transform_rates, |
325 | 779x |
object[[1]]$transform_fractions) |
326 |
} |
|
327 | 2242x |
degparms_fixed <- object[[1]]$bparms.fixed |
328 | ||
329 |
# Transformations are done in the degradation function by default |
|
330 |
# (transformations = "mkin") |
|
331 | 2242x |
transform.par = rep(0, length(degparms_optim)) |
332 | ||
333 | 2242x |
odeini_optim_parm_names <- grep('_0$', names(degparms_optim), value = TRUE) |
334 | 2242x |
odeini_fixed_parm_names <- grep('_0$', names(degparms_fixed), value = TRUE) |
335 | ||
336 | 2242x |
odeparms_fixed_names <- setdiff(names(degparms_fixed), odeini_fixed_parm_names) |
337 | 2242x |
odeparms_fixed <- degparms_fixed[odeparms_fixed_names] |
338 | ||
339 | 2242x |
odeini_fixed <- degparms_fixed[odeini_fixed_parm_names] |
340 | 2242x |
names(odeini_fixed) <- gsub('_0$', '', odeini_fixed_parm_names) |
341 | ||
342 | 2242x |
model_function <- FALSE |
343 | ||
344 |
# Model functions with analytical solutions |
|
345 |
# Fixed parameters, use_of_ff = "min" and turning off sinks currently not supported here |
|
346 |
# In general, we need to consider exactly how the parameters in mkinfit were specified, |
|
347 |
# as the parameters are currently mapped by position in these solutions |
|
348 | 2242x |
sinks <- sapply(mkin_model$spec, function(x) x$sink) |
349 | 2242x |
if (length(odeparms_fixed) == 0 & mkin_model$use_of_ff == "max" & all(sinks)) { |
350 |
# Parent only |
|
351 | 2242x |
if (length(mkin_model$spec) == 1) { |
352 | 1748x |
parent_type <- mkin_model$spec[[1]]$type |
353 | 1748x |
if (length(odeini_fixed) == 1 && !grepl("_bound$", names(odeini_fixed))) { |
354 | 50x |
if (transformations == "saemix") { |
355 | ! |
stop("saemix transformations are not supported for parent fits with fixed initial parent value") |
356 |
} |
|
357 | 50x |
if (parent_type == "SFO") { |
358 | 50x |
stop("saemix needs at least two parameters to work on.") |
359 |
} |
|
360 | ! |
if (parent_type == "FOMC") { |
361 | ! |
model_function <- function(psi, id, xidep) { |
362 | ! |
odeini_fixed / (xidep[, "time"]/exp(psi[id, 2]) + 1)^exp(psi[id, 1]) |
363 |
} |
|
364 |
} |
|
365 | ! |
if (parent_type == "DFOP") { |
366 | ! |
model_function <- function(psi, id, xidep) { |
367 | ! |
g <- plogis(psi[id, 3]) |
368 | ! |
t <- xidep[, "time"] |
369 | ! |
odeini_fixed * (g * exp(- exp(psi[id, 1]) * t) + |
370 | ! |
(1 - g) * exp(- exp(psi[id, 2]) * t)) |
371 |
} |
|
372 |
} |
|
373 | ! |
if (parent_type == "HS") { |
374 | ! |
model_function <- function(psi, id, xidep) { |
375 | ! |
tb <- exp(psi[id, 3]) |
376 | ! |
t <- xidep[, "time"] |
377 | ! |
k1 = exp(psi[id, 1]) |
378 | ! |
odeini_fixed * ifelse(t <= tb, |
379 | ! |
exp(- k1 * t), |
380 | ! |
exp(- k1 * tb) * exp(- exp(psi[id, 2]) * (t - tb))) |
381 |
} |
|
382 |
} |
|
383 |
} else { |
|
384 | 1698x |
if (length(odeini_fixed) == 2) { |
385 | ! |
stop("SFORB with fixed initial parent value is not supported") |
386 |
} |
|
387 | 1698x |
if (parent_type == "SFO") { |
388 | 785x |
if (transformations == "mkin") { |
389 | 283x |
model_function <- function(psi, id, xidep) { |
390 | 2628025x |
psi[id, 1] * exp( - exp(psi[id, 2]) * xidep[, "time"]) |
391 |
} |
|
392 |
} else { |
|
393 | 502x |
model_function <- function(psi, id, xidep) { |
394 | 4054103x |
psi[id, 1] * exp( - psi[id, 2] * xidep[, "time"]) |
395 |
} |
|
396 | 502x |
transform.par = c(0, 1) |
397 |
} |
|
398 |
} |
|
399 | 1698x |
if (parent_type == "FOMC") { |
400 | 76x |
if (transformations == "mkin") { |
401 | 41x |
model_function <- function(psi, id, xidep) { |
402 | 510269x |
psi[id, 1] / (xidep[, "time"]/exp(psi[id, 3]) + 1)^exp(psi[id, 2]) |
403 |
} |
|
404 |
} else { |
|
405 | 35x |
model_function <- function(psi, id, xidep) { |
406 | 432565x |
psi[id, 1] / (xidep[, "time"]/psi[id, 3] + 1)^psi[id, 2] |
407 |
} |
|
408 | 35x |
transform.par = c(0, 1, 1) |
409 |
} |
|
410 |
} |
|
411 | 1698x |
if (parent_type == "DFOP") { |
412 | 677x |
if (transformations == "mkin") { |
413 | 637x |
model_function <- function(psi, id, xidep) { |
414 | 8785439x |
g <- plogis(psi[id, 4]) |
415 | 8785439x |
t <- xidep[, "time"] |
416 | 8785439x |
psi[id, 1] * (g * exp(- exp(psi[id, 2]) * t) + |
417 | 8785439x |
(1 - g) * exp(- exp(psi[id, 3]) * t)) |
418 |
} |
|
419 |
} else { |
|
420 | 40x |
model_function <- function(psi, id, xidep) { |
421 | 507885x |
g <- psi[id, 4] |
422 | 507885x |
t <- xidep[, "time"] |
423 | 507885x |
psi[id, 1] * (g * exp(- psi[id, 2] * t) + |
424 | 507885x |
(1 - g) * exp(- psi[id, 3] * t)) |
425 |
} |
|
426 | 40x |
transform.par = c(0, 1, 1, 3) |
427 |
} |
|
428 |
} |
|
429 | 1698x |
if (parent_type == "SFORB") { |
430 | 150x |
if (transformations == "mkin") { |
431 | 130x |
model_function <- function(psi, id, xidep) { |
432 | 1240580x |
k_12 <- exp(psi[id, 3]) |
433 | 1240580x |
k_21 <- exp(psi[id, 4]) |
434 | 1240580x |
k_1output <- exp(psi[id, 2]) |
435 | 1240580x |
t <- xidep[, "time"] |
436 | ||
437 | 1240580x |
sqrt_exp = sqrt(1/4 * (k_12 + k_21 + k_1output)^2 - k_1output * k_21) |
438 | 1240580x |
b1 = 0.5 * (k_12 + k_21 + k_1output) + sqrt_exp |
439 | 1240580x |
b2 = 0.5 * (k_12 + k_21 + k_1output) - sqrt_exp |
440 | ||
441 | 1240580x |
psi[id, 1] * (((k_12 + k_21 - b1)/(b2 - b1)) * exp(-b1 * t) + |
442 | 1240580x |
((k_12 + k_21 - b2)/(b1 - b2)) * exp(-b2 * t)) |
443 |
} |
|
444 |
} else { |
|
445 | 20x |
model_function <- function(psi, id, xidep) { |
446 | 290980x |
k_12 <- psi[id, 3] |
447 | 290980x |
k_21 <- psi[id, 4] |
448 | 290980x |
k_1output <- psi[id, 2] |
449 | 290980x |
t <- xidep[, "time"] |
450 | ||
451 | 290980x |
sqrt_exp = sqrt(1/4 * (k_12 + k_21 + k_1output)^2 - k_1output * k_21) |
452 | 290980x |
b1 = 0.5 * (k_12 + k_21 + k_1output) + sqrt_exp |
453 | 290980x |
b2 = 0.5 * (k_12 + k_21 + k_1output) - sqrt_exp |
454 | ||
455 | 290980x |
psi[id, 1] * (((k_12 + k_21 - b1)/(b2 - b1)) * exp(-b1 * t) + |
456 | 290980x |
((k_12 + k_21 - b2)/(b1 - b2)) * exp(-b2 * t)) |
457 |
} |
|
458 | 20x |
transform.par = c(0, 1, 1, 1) |
459 |
} |
|
460 |
} |
|
461 | 1698x |
if (parent_type == "HS") { |
462 | 10x |
if (transformations == "mkin") { |
463 | 10x |
model_function <- function(psi, id, xidep) { |
464 | 150610x |
tb <- exp(psi[id, 4]) |
465 | 150610x |
t <- xidep[, "time"] |
466 | 150610x |
k1 <- exp(psi[id, 2]) |
467 | 150610x |
psi[id, 1] * ifelse(t <= tb, |
468 | 150610x |
exp(- k1 * t), |
469 | 150610x |
exp(- k1 * tb) * exp(- exp(psi[id, 3]) * (t - tb))) |
470 |
} |
|
471 |
} else { |
|
472 | ! |
model_function <- function(psi, id, xidep) { |
473 | ! |
tb <- psi[id, 4] |
474 | ! |
t <- xidep[, "time"] |
475 | ! |
psi[id, 1] * ifelse(t <= tb, |
476 | ! |
exp(- psi[id, 2] * t), |
477 | ! |
exp(- psi[id, 2] * tb) * exp(- psi[id, 3] * (t - tb))) |
478 |
} |
|
479 | ! |
transform.par = c(0, 1, 1, 1) |
480 |
} |
|
481 |
} |
|
482 |
} |
|
483 |
} |
|
484 | ||
485 |
# Parent with one metabolite |
|
486 |
# Parameter names used in the model functions are as in |
|
487 |
# https://nbviewer.jupyter.org/urls/jrwb.de/nb/Symbolic%20ODE%20solutions%20for%20mkin.ipynb |
|
488 | 2192x |
types <- unname(sapply(mkin_model$spec, function(x) x$type)) |
489 | 2192x |
if (length(mkin_model$spec) == 2 &! "SFORB" %in% types ) { |
490 |
# Initial value for the metabolite (n20) must be fixed |
|
491 | 494x |
if (names(odeini_fixed) == names(mkin_model$spec)[2]) { |
492 | 494x |
n20 <- odeini_fixed |
493 | 494x |
parent_name <- names(mkin_model$spec)[1] |
494 | 494x |
if (identical(types, c("SFO", "SFO"))) { |
495 | 208x |
if (transformations == "mkin") { |
496 | 208x |
model_function <- function(psi, id, xidep) { |
497 | 873912x |
t <- xidep[, "time"] |
498 | 873912x |
n10 <- psi[id, 1] |
499 | 873912x |
k1 <- exp(psi[id, 2]) |
500 | 873912x |
k2 <- exp(psi[id, 3]) |
501 | 873912x |
f12 <- plogis(psi[id, 4]) |
502 | 873912x |
ifelse(xidep[, "name"] == parent_name, |
503 | 873912x |
n10 * exp(- k1 * t), |
504 | 873912x |
(((k2 - k1) * n20 - f12 * k1 * n10) * exp(- k2 * t)) / (k2 - k1) + |
505 | 873912x |
(f12 * k1 * n10 * exp(- k1 * t)) / (k2 - k1) |
506 |
) |
|
507 |
} |
|
508 |
} else { |
|
509 | ! |
model_function <- function(psi, id, xidep) { |
510 | ! |
t <- xidep[, "time"] |
511 | ! |
n10 <- psi[id, 1] |
512 | ! |
k1 <- psi[id, 2] |
513 | ! |
k2 <- psi[id, 3] |
514 | ! |
f12 <- psi[id, 4] |
515 | ! |
ifelse(xidep[, "name"] == parent_name, |
516 | ! |
n10 * exp(- k1 * t), |
517 | ! |
(((k2 - k1) * n20 - f12 * k1 * n10) * exp(- k2 * t)) / (k2 - k1) + |
518 | ! |
(f12 * k1 * n10 * exp(- k1 * t)) / (k2 - k1) |
519 |
) |
|
520 |
} |
|
521 | ! |
transform.par = c(0, 1, 1, 3) |
522 |
} |
|
523 |
} |
|
524 | 494x |
if (identical(types, c("DFOP", "SFO"))) { |
525 | 286x |
if (transformations == "mkin") { |
526 | 104x |
model_function <- function(psi, id, xidep) { |
527 | 1821022x |
t <- xidep[, "time"] |
528 | 1821022x |
n10 <- psi[id, 1] |
529 | 1821022x |
k2 <- exp(psi[id, 2]) |
530 | 1821022x |
f12 <- plogis(psi[id, 3]) |
531 | 1821022x |
l1 <- exp(psi[id, 4]) |
532 | 1821022x |
l2 <- exp(psi[id, 5]) |
533 | 1821022x |
g <- plogis(psi[id, 6]) |
534 | 1821022x |
ifelse(xidep[, "name"] == parent_name, |
535 | 1821022x |
n10 * (g * exp(- l1 * t) + (1 - g) * exp(- l2 * t)), |
536 | 1821022x |
((f12 * g - f12) * l2 * n10 * exp(- l2 * t)) / (l2 - k2) - |
537 | 1821022x |
(f12 * g * l1 * n10 * exp(- l1 * t)) / (l1 - k2) + |
538 | 1821022x |
((((l1 - k2) * l2 - k2 * l1 + k2^2) * n20 + |
539 | 1821022x |
((f12 * l1 + (f12 * g - f12) * k2) * l2 - |
540 | 1821022x |
f12 * g * k2 * l1) * n10) * exp( - k2 * t)) / |
541 | 1821022x |
((l1 - k2) * l2 - k2 * l1 + k2^2) |
542 |
) |
|
543 |
} |
|
544 |
} else { |
|
545 | 182x |
model_function <- function(psi, id, xidep) { |
546 | 2908620x |
t <- xidep[, "time"] |
547 | 2908620x |
n10 <- psi[id, 1] |
548 | 2908620x |
k2 <- psi[id, 2] |
549 | 2908620x |
f12 <- psi[id, 3] |
550 | 2908620x |
l1 <- psi[id, 4] |
551 | 2908620x |
l2 <- psi[id, 5] |
552 | 2908620x |
g <- psi[id, 6] |
553 | 2908620x |
ifelse(xidep[, "name"] == parent_name, |
554 | 2908620x |
n10 * (g * exp(- l1 * t) + (1 - g) * exp(- l2 * t)), |
555 | 2908620x |
((f12 * g - f12) * l2 * n10 * exp(- l2 * t)) / (l2 - k2) - |
556 | 2908620x |
(f12 * g * l1 * n10 * exp(- l1 * t)) / (l1 - k2) + |
557 | 2908620x |
((((l1 - k2) * l2 - k2 * l1 + k2^2) * n20 + |
558 | 2908620x |
((f12 * l1 + (f12 * g - f12) * k2) * l2 - |
559 | 2908620x |
f12 * g * k2 * l1) * n10) * exp( - k2 * t)) / |
560 | 2908620x |
((l1 - k2) * l2 - k2 * l1 + k2^2) |
561 |
) |
|
562 |
} |
|
563 | 182x |
transform.par = c(0, 1, 3, 1, 1, 3) |
564 |
} |
|
565 |
} |
|
566 |
} |
|
567 |
} |
|
568 |
} |
|
569 | ||
570 | 2192x |
if (is.function(model_function) & solution_type == "auto") { |
571 | 2083x |
solution_type = "analytical saemix" |
572 |
} else { |
|
573 | ||
574 | 109x |
if (transformations == "saemix") { |
575 | 5x |
stop("Using saemix transformations is only supported if an analytical solution is implemented for saemix") |
576 |
} |
|
577 | ||
578 | 104x |
if (solution_type == "auto") |
579 | ! |
solution_type <- object[[1]]$solution_type |
580 | ||
581 |
# Define some variables to avoid function calls in model function |
|
582 | 104x |
transparms_optim_names <- names(degparms_optim) |
583 | 104x |
odeini_optim_names <- gsub('_0$', '', odeini_optim_parm_names) |
584 | 104x |
diff_names <- names(mkin_model$diffs) |
585 | 104x |
ode_transparms_optim_names <- setdiff(transparms_optim_names, odeini_optim_parm_names) |
586 | 104x |
transform_rates <- object[[1]]$transform_rates |
587 | 104x |
transform_fractions <- object[[1]]$transform_fractions |
588 | ||
589 |
# Get native symbol info for speed |
|
590 | 104x |
use_symbols = FALSE |
591 | 104x |
if (solution_type == "deSolve" & !is.null(mkin_model$cf)) { |
592 | 104x |
mkin_model$symbols <- try(deSolve::checkDLL( |
593 | 104x |
dllname = mkin_model$dll_info[["name"]], |
594 | 104x |
func = "diffs", initfunc = "initpar", |
595 | 104x |
jacfunc = NULL, nout = 0, outnames = NULL)) |
596 | 104x |
if (!inherits(mkin_model$symbols, "try-error")) { |
597 | 104x |
use_symbols = TRUE |
598 |
} |
|
599 |
} |
|
600 | ||
601 |
# Define the model function |
|
602 | 104x |
model_function <- function(psi, id, xidep) { |
603 | ||
604 | 873912x |
uid <- unique(id) |
605 | ||
606 | 873912x |
res_list <- lapply(uid, function(i) { |
607 | ||
608 | 43671888x |
transparms_optim <- as.numeric(psi[i, ]) # psi[i, ] is a dataframe when called in saemix.predict |
609 | 43671888x |
names(transparms_optim) <- transparms_optim_names |
610 | ||
611 | 43671888x |
odeini_optim <- transparms_optim[odeini_optim_parm_names] |
612 | 43671888x |
names(odeini_optim) <- odeini_optim_names |
613 | ||
614 | 43671888x |
odeini <- c(odeini_optim, odeini_fixed)[diff_names] |
615 | ||
616 | 43671888x |
odeparms_optim <- backtransform_odeparms(transparms_optim[ode_transparms_optim_names], mkin_model, |
617 | 43671888x |
transform_rates = transform_rates, |
618 | 43671888x |
transform_fractions = transform_fractions) |
619 | 43671888x |
odeparms <- c(odeparms_optim, odeparms_fixed) |
620 | ||
621 | 43671888x |
xidep_i <- xidep[which(id == i), ] |
622 | ||
623 | 43671888x |
if (solution_type[1] == "analytical") { |
624 | ! |
out_values <- mkin_model$deg_func(xidep_i, odeini, odeparms) |
625 |
} else { |
|
626 | ||
627 | 43671888x |
i_time <- xidep_i$time |
628 | 43671888x |
i_name <- xidep_i$name |
629 | ||
630 | 43671888x |
out_wide <- mkinpredict(mkin_model, |
631 | 43671888x |
odeparms = odeparms, odeini = odeini, |
632 | 43671888x |
solution_type = solution_type, |
633 | 43671888x |
outtimes = sort(unique(i_time)), |
634 | 43671888x |
na_stop = FALSE |
635 |
) |
|
636 | ||
637 | 43671888x |
out_index <- cbind(as.character(i_time), as.character(i_name)) |
638 | 43671888x |
out_values <- out_wide[out_index] |
639 |
} |
|
640 | 43671888x |
return(out_values) |
641 |
}) |
|
642 | 873912x |
res <- unlist(res_list) |
643 | 873912x |
return(res) |
644 |
} |
|
645 |
} |
|
646 | ||
647 | 2187x |
if (identical(error_model, "auto")) { |
648 | 2187x |
error_model = object[[1]]$err_mod |
649 |
} |
|
650 | 2187x |
error.model <- switch(error_model, |
651 | 2187x |
const = "constant", |
652 | 2187x |
tc = "combined", |
653 | 2187x |
obs = "constant") |
654 | ||
655 | 2187x |
if (error_model == "obs") { |
656 | ! |
warning("The error model 'obs' (variance by variable) can currently not be transferred to an saemix model") |
657 |
} |
|
658 | ||
659 | 2187x |
degparms_psi0 <- degparms_optim |
660 | 2187x |
degparms_psi0[names(degparms_start)] <- degparms_start |
661 | 2187x |
psi0_matrix <- matrix(degparms_psi0, nrow = 1, |
662 | 2187x |
dimnames = list("(Intercept)", names(degparms_psi0))) |
663 | ||
664 | 2187x |
if (covariance.model[1] == "auto") { |
665 | 2062x |
covariance_diagonal <- rep(1, length(degparms_optim)) |
666 | 2062x |
if (!is.null(no_random_effect)) { |
667 | 766x |
degparms_no_random <- which(names(degparms_psi0) %in% no_random_effect) |
668 | 766x |
covariance_diagonal[degparms_no_random] <- 0 |
669 |
} |
|
670 | 2062x |
covariance.model = diag(covariance_diagonal) |
671 |
} |
|
672 | ||
673 | 2187x |
if (omega.init[1] == "auto") { |
674 | 2187x |
if (transformations == "mkin") { |
675 | 1413x |
degparms_eta_ini <- as.numeric( # remove names |
676 | 1413x |
mean_degparms(object, |
677 | 1413x |
random = TRUE, test_log_parms = TRUE)$eta) |
678 | ||
679 | 1413x |
omega.init <- 2 * diag(degparms_eta_ini^2) |
680 |
} else { |
|
681 | 774x |
omega.init <- matrix(nrow = 0, ncol = 0) |
682 |
} |
|
683 |
} |
|
684 | ||
685 | 2187x |
if (is.null(covariate_models)) { |
686 | 2027x |
covariate.model <- matrix(nrow = 0, ncol = 0) # default in saemixModel() |
687 |
} else { |
|
688 | 160x |
degparms_dependent <- sapply(covariate_models, function(x) as.character(x[[2]])) |
689 | 160x |
covariates_in_models = unique(unlist(lapply( |
690 | 160x |
covariate_models, function(x) |
691 | 160x |
colnames(attr(terms(x), "factors")) |
692 |
))) |
|
693 | 160x |
covariates_not_available <- setdiff(covariates_in_models, names(covariates)) |
694 | 160x |
if (length(covariates_not_available) > 0) { |
695 | ! |
stop("Covariate(s) ", paste(covariates_not_available, collapse = ", "), |
696 | ! |
" used in the covariate models not available in the covariate data") |
697 |
} |
|
698 | 160x |
psi0_matrix <- rbind(psi0_matrix, |
699 | 160x |
matrix(0, nrow = length(covariates), ncol = ncol(psi0_matrix), |
700 | 160x |
dimnames = list(names(covariates), colnames(psi0_matrix)))) |
701 | 160x |
covariate.model <- matrix(0, nrow = length(covariates), |
702 | 160x |
ncol = ncol(psi0_matrix), |
703 | 160x |
dimnames = list( |
704 | 160x |
covariates = names(covariates), |
705 | 160x |
degparms = colnames(psi0_matrix))) |
706 | 160x |
if (transformations == "saemix") { |
707 | ! |
stop("Covariate models with saemix transformations currently not supported") |
708 |
} |
|
709 | 160x |
parms_trans <- as.data.frame(t(sapply(object, parms, transformed = TRUE))) |
710 | 160x |
for (covariate_model in covariate_models) { |
711 | 160x |
covariate_name <- as.character(covariate_model[[2]]) |
712 | 160x |
model_data <- cbind(parms_trans, covariates) |
713 | 160x |
ini_model <- lm(covariate_model, data = model_data) |
714 | 160x |
ini_coef <- coef(ini_model) |
715 | 160x |
psi0_matrix[names(ini_coef), covariate_name] <- ini_coef |
716 | 160x |
covariate.model[names(ini_coef)[-1], covariate_name] <- as.numeric(as.logical(ini_coef[-1])) |
717 |
} |
|
718 |
} |
|
719 | ||
720 | 2187x |
res <- saemix::saemixModel(model_function, |
721 | 2187x |
psi0 = psi0_matrix, |
722 | 2187x |
"Mixed model generated from mmkin object", |
723 | 2187x |
transform.par = transform.par, |
724 | 2187x |
error.model = error.model, |
725 | 2187x |
verbose = verbose, |
726 | 2187x |
covariance.model = covariance.model, |
727 | 2187x |
covariate.model = covariate.model, |
728 | 2187x |
omega.init = omega.init, |
729 | 2187x |
error.init = error.init, |
730 |
... |
|
731 |
) |
|
732 | 2187x |
attr(res, "mean_dp_start") <- degparms_optim |
733 | 2187x |
return(res) |
734 |
} |
|
735 | ||
736 |
#' @rdname saem |
|
737 |
#' @importFrom rlang !!! |
|
738 |
#' @return An [saemix::SaemixData] object. |
|
739 |
#' @export |
|
740 |
saemix_data <- function(object, covariates = NULL, verbose = FALSE, ...) { |
|
741 | ! |
if (nrow(object) > 1) stop("Only row objects allowed") |
742 | 2187x |
ds_names <- colnames(object) |
743 | ||
744 | 2187x |
ds_list <- lapply(object, function(x) x$data[c("time", "variable", "observed")]) |
745 | 2187x |
names(ds_list) <- ds_names |
746 | 2187x |
ds_saemix_all <- vctrs::vec_rbind(!!!ds_list, .names_to = "ds") |
747 | 2187x |
ds_saemix <- data.frame(ds = ds_saemix_all$ds, |
748 | 2187x |
name = as.character(ds_saemix_all$variable), |
749 | 2187x |
time = ds_saemix_all$time, |
750 | 2187x |
value = ds_saemix_all$observed, |
751 | 2187x |
stringsAsFactors = FALSE) |
752 | 2187x |
if (!is.null(covariates)) { |
753 | 160x |
name.covariates <- names(covariates) |
754 | 160x |
covariates$ds <- rownames(covariates) |
755 | 160x |
ds_saemix <- merge(ds_saemix, covariates, sort = FALSE) |
756 |
} else { |
|
757 | 2027x |
name.covariates <- character(0) |
758 |
} |
|
759 | ||
760 | 2187x |
res <- saemix::saemixData(ds_saemix, |
761 | 2187x |
name.group = "ds", |
762 | 2187x |
name.predictors = c("time", "name"), |
763 | 2187x |
name.response = "value", |
764 | 2187x |
name.covariates = name.covariates, |
765 | 2187x |
verbose = verbose, |
766 |
...) |
|
767 | 2187x |
return(res) |
768 |
} |
|
769 | ||
770 |
#' logLik method for saem.mmkin objects |
|
771 |
#' |
|
772 |
#' @param object The fitted [saem.mmkin] object |
|
773 |
#' @param \dots Passed to [saemix::logLik.SaemixObject] |
|
774 |
#' @param method Passed to [saemix::logLik.SaemixObject] |
|
775 |
#' @export |
|
776 |
logLik.saem.mmkin <- function(object, ..., method = c("is", "lin", "gq")) { |
|
777 | 4404x |
method <- match.arg(method) |
778 | 4404x |
return(logLik(object$so, method = method)) |
779 |
} |
|
780 | ||
781 |
#' @export |
|
782 |
update.saem.mmkin <- function(object, ..., evaluate = TRUE) { |
|
783 | 507x |
call <- object$call |
784 |
# For some reason we get saem.mmkin in the call when using mhmkin |
|
785 |
# so we need to fix this so we do not have to export saem.mmkin in |
|
786 |
# addition to the S3 method |
|
787 | 507x |
call[[1]] <- saem |
788 | ||
789 |
# We also need to provide the mmkin object in the call, so it |
|
790 |
# will also be found when called by testthat or pkgdown |
|
791 | 507x |
call[[2]] <- object$mmkin |
792 | ||
793 | 507x |
update_arguments <- match.call(expand.dots = FALSE)$... |
794 | ||
795 | 507x |
if (length(update_arguments) > 0) { |
796 | 507x |
update_arguments_in_call <- !is.na(match(names(update_arguments), names(call))) |
797 |
} |
|
798 | ||
799 | 507x |
for (a in names(update_arguments)[update_arguments_in_call]) { |
800 | 35x |
call[[a]] <- update_arguments[[a]] |
801 |
} |
|
802 | ||
803 | 507x |
update_arguments_not_in_call <- !update_arguments_in_call |
804 | 507x |
if(any(update_arguments_not_in_call)) { |
805 | 472x |
call <- c(as.list(call), update_arguments[update_arguments_not_in_call]) |
806 | 472x |
call <- as.call(call) |
807 |
} |
|
808 | 507x |
if(evaluate) eval(call, parent.frame()) |
809 | ! |
else call |
810 |
} |
|
811 | ||
812 |
#' @export |
|
813 |
#' @rdname parms |
|
814 |
#' @param ci Should a matrix with estimates and confidence interval boundaries |
|
815 |
#' be returned? If FALSE (default), a vector of estimates is returned if no |
|
816 |
#' covariates are given, otherwise a matrix of estimates is returned, with |
|
817 |
#' each column corresponding to a row of the data frame holding the covariates |
|
818 |
#' @param covariates A data frame holding covariate values for which to |
|
819 |
#' return parameter values. Only has an effect if 'ci' is FALSE. |
|
820 |
parms.saem.mmkin <- function(object, ci = FALSE, covariates = NULL, ...) { |
|
821 | 2904x |
cov.mod <- object$sm@covariance.model |
822 | 2904x |
n_cov_mod_parms <- sum(cov.mod[upper.tri(cov.mod, diag = TRUE)]) |
823 | 2904x |
n_parms <- length(object$sm@name.modpar) + |
824 | 2904x |
n_cov_mod_parms + |
825 | 2904x |
length(object$sm@name.sigma) |
826 | ||
827 | 2904x |
if (inherits(object$so, "try-error")) { |
828 | ! |
conf.int <- matrix(rep(NA, 3 * n_parms), ncol = 3) |
829 | ! |
colnames(conf.int) <- c("estimate", "lower", "upper") |
830 |
} else { |
|
831 | 2904x |
conf.int <- object$so@results@conf.int[c("estimate", "lower", "upper")] |
832 | 2904x |
rownames(conf.int) <- object$so@results@conf.int[["name"]] |
833 | 2904x |
conf.int.var <- grepl("^Var\\.", rownames(conf.int)) |
834 | 2904x |
conf.int <- conf.int[!conf.int.var, ] |
835 | 2904x |
conf.int.cov <- grepl("^Cov\\.", rownames(conf.int)) |
836 | 2904x |
conf.int <- conf.int[!conf.int.cov, ] |
837 |
} |
|
838 | 2904x |
estimate <- conf.int[, "estimate"] |
839 | ||
840 | 2904x |
names(estimate) <- rownames(conf.int) |
841 | ||
842 | 2904x |
if (ci) { |
843 | 1034x |
return(conf.int) |
844 |
} else { |
|
845 | 1870x |
if (is.null(covariates)) { |
846 | 1760x |
return(estimate) |
847 |
} else { |
|
848 | 110x |
est_for_cov <- matrix(NA, |
849 | 110x |
nrow = length(object$sm@name.modpar), ncol = nrow(covariates), |
850 | 110x |
dimnames = (list(object$sm@name.modpar, rownames(covariates)))) |
851 | 110x |
covmods <- object$covariate_models |
852 | 110x |
names(covmods) <- sapply(covmods, function(x) as.character(x[[2]])) |
853 | 110x |
for (deg_parm_name in rownames(est_for_cov)) { |
854 | 440x |
if (deg_parm_name %in% names(covmods)) { |
855 | 110x |
covariate <- covmods[[deg_parm_name]][[3]] |
856 | 110x |
beta_degparm_name <- paste0("beta_", covariate, |
857 | 110x |
"(", deg_parm_name, ")") |
858 | 110x |
est_for_cov[deg_parm_name, ] <- estimate[deg_parm_name] + |
859 | 110x |
covariates[[covariate]] * estimate[beta_degparm_name] |
860 |
} else { |
|
861 | 330x |
est_for_cov[deg_parm_name, ] <- estimate[deg_parm_name] |
862 |
} |
|
863 |
} |
|
864 | 110x |
return(est_for_cov) |
865 |
} |
|
866 |
} |
|
867 |
} |
1 |
#' Method to get status information for fit array objects |
|
2 |
#' |
|
3 |
#' @param object The object to investigate |
|
4 |
#' @param x The object to be printed |
|
5 |
#' @param \dots For potential future extensions |
|
6 |
#' @return An object with the same dimensions as the fit array |
|
7 |
#' suitable printing method. |
|
8 |
#' @export |
|
9 |
status <- function(object, ...) |
|
10 |
{ |
|
11 | 589x |
UseMethod("status", object) |
12 |
} |
|
13 | ||
14 |
#' @rdname status |
|
15 |
#' @export |
|
16 |
#' @examples |
|
17 |
#' \dontrun{ |
|
18 |
#' fits <- mmkin( |
|
19 |
#' c("SFO", "FOMC"), |
|
20 |
#' list("FOCUS A" = FOCUS_2006_A, |
|
21 |
#' "FOCUS B" = FOCUS_2006_C), |
|
22 |
#' quiet = TRUE) |
|
23 |
#' status(fits) |
|
24 |
#' } |
|
25 |
status.mmkin <- function(object, ...) { |
|
26 | 376x |
all_summary_warnings <- character() |
27 | 376x |
sww <- 0 # Counter for Shapiro-Wilks warnings |
28 | ||
29 | 376x |
result <- lapply(object, |
30 | 376x |
function(fit) { |
31 | ! |
if (inherits(fit, "try-error")) return("E") |
32 | 4391x |
sw <- fit$summary_warnings |
33 | 4391x |
swn <- names(sw) |
34 | 4391x |
if (length(sw) > 0) { |
35 | ! |
if (any(grepl("S", swn))) { |
36 | ! |
sww <<- sww + 1 |
37 | ! |
swn <- gsub("S", paste0("S", sww), swn) |
38 |
} |
|
39 | ! |
warnstring <- paste(swn, collapse = ", ") |
40 | ! |
names(sw) <- swn |
41 | ! |
all_summary_warnings <<- c(all_summary_warnings, sw) |
42 | ! |
return(warnstring) |
43 |
} else { |
|
44 | 4391x |
return("OK") |
45 |
} |
|
46 |
}) |
|
47 | 376x |
result <- unlist(result) |
48 | 376x |
dim(result) <- dim(object) |
49 | 376x |
dimnames(result) <- dimnames(object) |
50 | ||
51 | 376x |
u_swn <- unique(names(all_summary_warnings)) |
52 | 376x |
attr(result, "unique_warnings") <- all_summary_warnings[u_swn] |
53 | 376x |
class(result) <- "status.mmkin" |
54 | 376x |
return(result) |
55 |
} |
|
56 | ||
57 |
#' @rdname status |
|
58 |
#' @export |
|
59 |
print.status.mmkin <- function(x, ...) { |
|
60 | 376x |
u_w <- attr(x, "unique_warnings") |
61 | 376x |
attr(x, "unique_warnings") <- NULL |
62 | 376x |
class(x) <- NULL |
63 | 376x |
print(x, quote = FALSE) |
64 | 376x |
cat("\n") |
65 | 376x |
for (i in seq_along(u_w)) { |
66 | ! |
cat(names(u_w)[i], ": ", u_w[i], "\n", sep = "") |
67 |
} |
|
68 | 376x |
if (any(x == "OK")) cat("OK: No warnings\n") |
69 | ! |
if (any(x == "E")) cat("E: Error\n") |
70 |
} |
|
71 | ||
72 |
#' @rdname status |
|
73 |
#' @export |
|
74 |
status.mhmkin <- function(object, ...) { |
|
75 | 125x |
if (inherits(object[[1]], "saem.mmkin")) { |
76 | 125x |
test_func <- function(fit) { |
77 | 500x |
if (inherits(fit, "try-error")) { |
78 | ! |
return("E") |
79 |
} else { |
|
80 | 500x |
if (inherits(fit$so, "try-error")) { |
81 | ! |
return("E") |
82 |
} else { |
|
83 | 500x |
if (!is.null(fit$FIM_failed)) { |
84 | ! |
return_values <- c("fixed effects" = "Fth", |
85 | ! |
"random effects and error model parameters" = "FO") |
86 | ! |
return(paste(return_values[fit$FIM_failed], collapse = ", ")) |
87 |
} else { |
|
88 | 500x |
return("OK") |
89 |
} |
|
90 |
} |
|
91 |
} |
|
92 |
} |
|
93 |
} else { |
|
94 | ! |
stop("Only mhmkin objects containing saem.mmkin objects currently supported") |
95 |
} |
|
96 | 125x |
result <- lapply(object, test_func) |
97 | 125x |
result <- unlist(result) |
98 | 125x |
dim(result) <- dim(object) |
99 | 125x |
dimnames(result) <- dimnames(object) |
100 | ||
101 | 125x |
class(result) <- "status.mhmkin" |
102 | 125x |
return(result) |
103 |
} |
|
104 | ||
105 |
#' @rdname status |
|
106 |
#' @export |
|
107 |
print.status.mhmkin <- function(x, ...) { |
|
108 | 125x |
class(x) <- NULL |
109 | 125x |
print(x, quote = FALSE) |
110 | 125x |
cat("\n") |
111 | 125x |
if (any(x == "OK")) cat("OK: Fit terminated successfully\n") |
112 | ! |
if (any(x == "Fth")) cat("Fth: Could not invert FIM for fixed effects\n") |
113 | ! |
if (any(x == "FO")) cat("FO: Could not invert FIM for random effects and error model parameters\n") |
114 | ! |
if (any(x == "Fth, FO")) cat("Fth, FO: Could not invert FIM for fixed effects, nor for random effects and error model parameters\n") |
115 | ! |
if (any(x == "E")) cat("E: Error\n") |
116 |
} |
|
117 |
1 |
#' Function to calculate endpoints for further use from kinetic models fitted |
|
2 |
#' with mkinfit |
|
3 |
#' |
|
4 |
#' This function calculates DT50 and DT90 values as well as formation fractions |
|
5 |
#' from kinetic models fitted with mkinfit. If the SFORB model was specified |
|
6 |
#' for one of the parents or metabolites, the Eigenvalues are returned. These |
|
7 |
#' are equivalent to the rate constants of the DFOP model, but with the |
|
8 |
#' advantage that the SFORB model can also be used for metabolites. |
|
9 |
#' |
|
10 |
#' Additional DT50 values are calculated from the FOMC DT90 and k1 and k2 from |
|
11 |
#' HS and DFOP, as well as from Eigenvalues b1 and b2 of any SFORB models |
|
12 |
#' |
|
13 |
#' @param fit An object of class [mkinfit], [nlme.mmkin] or [saem.mmkin], or |
|
14 |
#' another object that has list components mkinmod containing an [mkinmod] |
|
15 |
#' degradation model, and two numeric vectors, bparms.optim and bparms.fixed, |
|
16 |
#' that contain parameter values for that model. |
|
17 |
#' @param covariates Numeric vector with covariate values for all variables in |
|
18 |
#' any covariate models in the object. If given, it overrides 'covariate_quantile'. |
|
19 |
#' @param covariate_quantile This argument only has an effect if the fitted |
|
20 |
#' object has covariate models. If so, the default is to show endpoints |
|
21 |
#' for the median of the covariate values (50th percentile). |
|
22 |
#' @importFrom stats optimize |
|
23 |
#' @return A list with a matrix of dissipation times named distimes, and, if |
|
24 |
#' applicable, a vector of formation fractions named ff and, if the SFORB model |
|
25 |
#' was in use, a vector of eigenvalues of these SFORB models, equivalent to |
|
26 |
#' DFOP rate constants |
|
27 |
#' @note The function is used internally by [summary.mkinfit], |
|
28 |
#' [summary.nlme.mmkin] and [summary.saem.mmkin]. |
|
29 |
#' @author Johannes Ranke |
|
30 |
#' @examples |
|
31 |
#' |
|
32 |
#' fit <- mkinfit("FOMC", FOCUS_2006_C, quiet = TRUE) |
|
33 |
#' endpoints(fit) |
|
34 |
#' \dontrun{ |
|
35 |
#' fit_2 <- mkinfit("DFOP", FOCUS_2006_C, quiet = TRUE) |
|
36 |
#' endpoints(fit_2) |
|
37 |
#' fit_3 <- mkinfit("SFORB", FOCUS_2006_C, quiet = TRUE) |
|
38 |
#' endpoints(fit_3) |
|
39 |
#' } |
|
40 |
#' |
|
41 |
#' @export |
|
42 |
endpoints <- function(fit, covariates = NULL, covariate_quantile = 0.5) { |
|
43 | 56208x |
mkinmod <- fit$mkinmod |
44 | 56208x |
obs_vars <- names(mkinmod$spec) |
45 | ||
46 | 56208x |
if (!is.null(fit$covariate_models)) { |
47 | 110x |
if (is.null(covariates)) { |
48 | 110x |
covariates = as.data.frame( |
49 | 110x |
apply(fit$covariates, 2, quantile, |
50 | 110x |
covariate_quantile, simplify = FALSE)) |
51 |
} else { |
|
52 | ! |
covariate_m <- matrix(covariates, byrow = TRUE) |
53 | ! |
colnames(covariate_m) <- names(covariates) |
54 | ! |
rownames(covariate_m) <- "User" |
55 | ! |
covariates <- as.data.frame(covariate_m) |
56 |
} |
|
57 | 110x |
degparms_trans <- parms(fit, covariates = covariates)[, 1] |
58 | 110x |
if (inherits(fit, "saem.mmkin") & (fit$transformations == "saemix")) { |
59 | ! |
degparms <- degparms_trans |
60 |
} else { |
|
61 | 110x |
degparms <- backtransform_odeparms(degparms_trans, |
62 | 110x |
fit$mkinmod, |
63 | 110x |
transform_rates = fit$transform_rates, |
64 | 110x |
transform_fractions = fit$transform_fractions) |
65 |
} |
|
66 |
} else { |
|
67 | 56098x |
degparms <- c(fit$bparms.optim, fit$bparms.fixed) |
68 |
} |
|
69 | ||
70 |
# Set up object to return |
|
71 | 56208x |
ep <- list() |
72 | 56208x |
ep$covariates <- covariates |
73 | 56208x |
ep$ff <- vector() |
74 | 56208x |
ep$SFORB <- vector() |
75 | 56208x |
ep$distimes <- data.frame( |
76 | 56208x |
DT50 = rep(NA, length(obs_vars)), |
77 | 56208x |
DT90 = rep(NA, length(obs_vars)), |
78 | 56208x |
row.names = obs_vars) |
79 | ||
80 | 56208x |
for (obs_var in obs_vars) { |
81 | 73858x |
type = names(mkinmod$map[[obs_var]])[1] |
82 | ||
83 |
# Get formation fractions if directly fitted, and calculate remaining fraction to sink |
|
84 | 73858x |
f_names = grep(paste("^f", obs_var, sep = "_"), names(degparms), value=TRUE) |
85 | 73858x |
if (length(f_names) > 0) { |
86 | 15068x |
f_values = degparms[f_names] |
87 | 15068x |
f_to_sink = 1 - sum(f_values) |
88 | 15068x |
names(f_to_sink) = ifelse(type == "SFORB", |
89 | 15068x |
paste(obs_var, "free", "sink", sep = "_"), |
90 | 15068x |
paste(obs_var, "sink", sep = "_")) |
91 | 15068x |
for (f_name in f_names) { |
92 | 17338x |
ep$ff[[sub("f_", "", sub("_to_", "_", f_name))]] = f_values[[f_name]] |
93 |
} |
|
94 | 15068x |
ep$ff = append(ep$ff, f_to_sink) |
95 |
} |
|
96 | ||
97 |
# Get the rest |
|
98 | 73858x |
if (type == "SFO") { |
99 | 40900x |
k_names = grep(paste("^k", obs_var, sep="_"), names(degparms), value=TRUE) |
100 | 40900x |
k_tot = sum(degparms[k_names]) |
101 | 40900x |
DT50 = log(2)/k_tot |
102 | 40900x |
DT90 = log(10)/k_tot |
103 | 40900x |
if (mkinmod$use_of_ff == "min" && length(obs_vars) > 1) { |
104 | 622x |
for (k_name in k_names) |
105 |
{ |
|
106 | 932x |
ep$ff[[sub("k_", "", k_name)]] = degparms[[k_name]] / k_tot |
107 |
} |
|
108 |
} |
|
109 |
} |
|
110 | 73858x |
if (type == "FOMC") { |
111 | 1790x |
alpha = degparms["alpha"] |
112 | 1790x |
beta = degparms["beta"] |
113 | 1790x |
DT50 = beta * (2^(1/alpha) - 1) |
114 | 1790x |
DT90 = beta * (10^(1/alpha) - 1) |
115 | 1790x |
DT50_back = DT90 / (log(10)/log(2)) # Backcalculated DT50 as recommended in FOCUS 2011 |
116 | 1790x |
ep$distimes[obs_var, c("DT50back")] = DT50_back |
117 |
} |
|
118 | 73858x |
if (type == "IORE") { |
119 | 352x |
k_names = grep(paste("^k__iore", obs_var, sep="_"), names(degparms), value=TRUE) |
120 | 352x |
k_tot = sum(degparms[k_names]) |
121 |
# From the NAFTA kinetics guidance, p. 5 |
|
122 | 352x |
n = degparms[paste("N", obs_var, sep = "_")] |
123 | 352x |
k = k_tot |
124 |
# Use the initial concentration of the parent compound |
|
125 | 352x |
source_name = mkinmod$map[[1]][[1]] |
126 | 352x |
c0 = degparms[paste(source_name, "0", sep = "_")] |
127 | 352x |
alpha = 1 / (n - 1) |
128 | 352x |
beta = (c0^(1 - n))/(k * (n - 1)) |
129 | 352x |
DT50 = beta * (2^(1/alpha) - 1) |
130 | 352x |
DT90 = beta * (10^(1/alpha) - 1) |
131 | 352x |
DT50_back = DT90 / (log(10)/log(2)) # Backcalculated DT50 as recommended in FOCUS 2011 |
132 | 352x |
ep$distimes[obs_var, c("DT50back")] = DT50_back |
133 | 352x |
if (mkinmod$use_of_ff == "min") { |
134 | ! |
for (k_name in k_names) |
135 |
{ |
|
136 | ! |
ep$ff[[sub("k_", "", k_name)]] = degparms[[k_name]] / k_tot |
137 |
} |
|
138 |
} |
|
139 |
} |
|
140 | 73858x |
if (type == "DFOP") { |
141 | 27729x |
k1 = degparms["k1"] |
142 | 27729x |
k2 = degparms["k2"] |
143 | 27729x |
g = degparms["g"] |
144 | 27729x |
f <- function(log_t, x) { |
145 | 684705x |
t <- exp(log_t) |
146 | 684705x |
fraction <- g * exp( - k1 * t) + (1 - g) * exp( - k2 * t) |
147 | 684705x |
(fraction - (1 - x/100))^2 |
148 |
} |
|
149 | 27729x |
DT50_k1 = log(2)/k1 |
150 | 27729x |
DT50_k2 = log(2)/k2 |
151 | 27729x |
DT90_k1 = log(10)/k1 |
152 | 27729x |
DT90_k2 = log(10)/k2 |
153 | ||
154 | 27729x |
DT50 <- try(exp(optimize(f, c(log(DT50_k1), log(DT50_k2)), x=50)$minimum), |
155 | 27729x |
silent = TRUE) |
156 | 27729x |
DT90 <- try(exp(optimize(f, c(log(DT90_k1), log(DT90_k2)), x=90)$minimum), |
157 | 27729x |
silent = TRUE) |
158 | ! |
if (inherits(DT50, "try-error")) DT50 = NA |
159 | ! |
if (inherits(DT90, "try-error")) DT90 = NA |
160 | 27729x |
DT50_back = DT90 / (log(10)/log(2)) # Backcalculated DT50 as recommended in FOCUS 2011 |
161 | ||
162 | 27729x |
ep$distimes[obs_var, c("DT50back")] = DT50_back |
163 | 27729x |
ep$distimes[obs_var, c("DT50_k1")] = DT50_k1 |
164 | 27729x |
ep$distimes[obs_var, c("DT50_k2")] = DT50_k2 |
165 |
} |
|
166 | 73858x |
if (type == "HS") { |
167 | 318x |
k1 = degparms["k1"] |
168 | 318x |
k2 = degparms["k2"] |
169 | 318x |
tb = degparms["tb"] |
170 | 318x |
DTx <- function(x) { |
171 | 636x |
DTx.a <- (log(100/(100 - x)))/k1 |
172 | 636x |
DTx.b <- tb + (log(100/(100 - x)) - k1 * tb)/k2 |
173 | 339x |
if (DTx.a < tb) DTx <- DTx.a |
174 | 297x |
else DTx <- DTx.b |
175 | 636x |
return(DTx) |
176 |
} |
|
177 | 318x |
DT50 <- DTx(50) |
178 | 318x |
DT90 <- DTx(90) |
179 | 318x |
DT50_back = DT90 / (log(10)/log(2)) # Backcalculated DT50 as recommended in FOCUS 2011 |
180 | 318x |
DT50_k1 = log(2)/k1 |
181 | 318x |
DT50_k2 = log(2)/k2 |
182 | 318x |
ep$distimes[obs_var, c("DT50back")] = DT50_back |
183 | 318x |
ep$distimes[obs_var, c("DT50_k1")] = DT50_k1 |
184 | 318x |
ep$distimes[obs_var, c("DT50_k2")] = DT50_k2 |
185 |
} |
|
186 | 73858x |
if (type == "SFORB") { |
187 |
# FOCUS kinetics (2006), p. 60 f |
|
188 | 2616x |
k_out_names = grep(paste("^k", obs_var, "free", sep="_"), names(degparms), value=TRUE) |
189 | 2616x |
k_out_names = setdiff(k_out_names, paste("k", obs_var, "free", "bound", sep="_")) |
190 | 2616x |
k_1output = sum(degparms[k_out_names]) |
191 | 2616x |
k_12 = degparms[paste("k", obs_var, "free", "bound", sep="_")] |
192 | 2616x |
k_21 = degparms[paste("k", obs_var, "bound", "free", sep="_")] |
193 | ||
194 | 2616x |
sqrt_exp = sqrt(1/4 * (k_12 + k_21 + k_1output)^2 - k_1output * k_21) |
195 | 2616x |
b1 = 0.5 * (k_12 + k_21 + k_1output) + sqrt_exp |
196 | 2616x |
b2 = 0.5 * (k_12 + k_21 + k_1output) - sqrt_exp |
197 | 2616x |
g = (k_12 + k_21 - b1)/(b2 - b1) |
198 | ||
199 | 2616x |
DT50_b1 = log(2)/b1 |
200 | 2616x |
DT50_b2 = log(2)/b2 |
201 | 2616x |
DT90_b1 = log(10)/b1 |
202 | 2616x |
DT90_b2 = log(10)/b2 |
203 | ||
204 | 2616x |
SFORB_fraction = function(t) { |
205 | 60096x |
g * exp(-b1 * t) + (1 - g) * exp(-b2 * t) |
206 |
} |
|
207 | ||
208 | 2616x |
f_50 <- function(log_t) (SFORB_fraction(exp(log_t)) - 0.5)^2 |
209 | 2616x |
log_DT50 <- try(optimize(f_50, c(log(DT50_b1), log(DT50_b2)))$minimum, |
210 | 2616x |
silent = TRUE) |
211 | 2616x |
f_90 <- function(log_t) (SFORB_fraction(exp(log_t)) - 0.1)^2 |
212 | 2616x |
log_DT90 <- try(optimize(f_90, c(log(DT90_b1), log(DT90_b2)))$minimum, |
213 | 2616x |
silent = TRUE) |
214 | ||
215 | 2616x |
DT50 = if (inherits(log_DT50, "try-error")) NA |
216 | 2616x |
else exp(log_DT50) |
217 | 2616x |
DT90 = if (inherits(log_DT90, "try-error")) NA |
218 | 2616x |
else exp(log_DT90) |
219 | ||
220 | 2616x |
DT50_back = DT90 / (log(10)/log(2)) # Backcalculated DT50 as recommended in FOCUS 2011 |
221 | ||
222 | 2616x |
for (k_out_name in k_out_names) |
223 |
{ |
|
224 | 2618x |
ep$ff[[sub("k_", "", k_out_name)]] = degparms[[k_out_name]] / k_1output |
225 |
} |
|
226 | ||
227 |
# Return the eigenvalues for comparison with DFOP rate constants |
|
228 | 2616x |
ep$SFORB[[paste(obs_var, "b1", sep="_")]] = b1 |
229 | 2616x |
ep$SFORB[[paste(obs_var, "b2", sep="_")]] = b2 |
230 |
# Return g for comparison with DFOP |
|
231 | 2616x |
ep$SFORB[[paste(obs_var, "g", sep="_")]] = g |
232 | ||
233 | 2616x |
ep$distimes[obs_var, c("DT50back")] = DT50_back |
234 | 2616x |
ep$distimes[obs_var, c(paste("DT50", obs_var, "b1", sep = "_"))] = DT50_b1 |
235 | 2616x |
ep$distimes[obs_var, c(paste("DT50", obs_var, "b2", sep = "_"))] = DT50_b2 |
236 |
} |
|
237 | 73858x |
if (type == "logistic") { |
238 |
# FOCUS kinetics (2014) p. 67 |
|
239 | 153x |
kmax = degparms["kmax"] |
240 | 153x |
k0 = degparms["k0"] |
241 | 153x |
r = degparms["r"] |
242 | 153x |
DT50 = (1/r) * log(1 - ((kmax/k0) * (1 - 2^(r/kmax)))) |
243 | 153x |
DT90 = (1/r) * log(1 - ((kmax/k0) * (1 - 10^(r/kmax)))) |
244 | ||
245 | 153x |
DT50_k0 = log(2)/k0 |
246 | 153x |
DT50_kmax = log(2)/kmax |
247 | 153x |
ep$distimes[obs_var, c("DT50_k0")] = DT50_k0 |
248 | 153x |
ep$distimes[obs_var, c("DT50_kmax")] = DT50_kmax |
249 |
} |
|
250 | 73858x |
ep$distimes[obs_var, c("DT50", "DT90")] = c(DT50, DT90) |
251 |
} |
|
252 | 38846x |
if (length(ep$ff) == 0) ep$ff <- NULL |
253 | 53592x |
if (length(ep$SFORB) == 0) ep$SFORB <- NULL |
254 | 56208x |
return(ep) |
255 |
} |
1 |
#' Function to set up a kinetic model with one or more state variables |
|
2 |
#' |
|
3 |
#' This function is usually called using a call to [mkinsub()] for each observed |
|
4 |
#' variable, specifying the corresponding submodel as well as outgoing pathways |
|
5 |
#' (see examples). |
|
6 |
#' |
|
7 |
#' For the definition of model types and their parameters, the equations given |
|
8 |
#' in the FOCUS and NAFTA guidance documents are used. |
|
9 |
#' |
|
10 |
#' For kinetic models with more than one observed variable, a symbolic solution |
|
11 |
#' of the system of differential equations is included in the resulting |
|
12 |
#' mkinmod object in some cases, speeding up the solution. |
|
13 |
#' |
|
14 |
#' If a C compiler is found by [pkgbuild::has_compiler()] and there |
|
15 |
#' is more than one observed variable in the specification, C code is generated |
|
16 |
#' for evaluating the differential equations, compiled using |
|
17 |
#' [inline::cfunction()] and added to the resulting mkinmod object. |
|
18 |
#' |
|
19 |
#' @param ... For each observed variable, a list as obtained by [mkinsub()] |
|
20 |
#' has to be specified as an argument (see examples). Currently, single |
|
21 |
#' first order kinetics "SFO", indeterminate order rate equation kinetics |
|
22 |
#' "IORE", or single first order with reversible binding "SFORB" are |
|
23 |
#' implemented for all variables, while "FOMC", "DFOP", "HS" and "logistic" |
|
24 |
#' can additionally be chosen for the first variable which is assumed to be |
|
25 |
#' the source compartment. |
|
26 |
#' Additionally, [mkinsub()] has an argument \code{to}, specifying names of |
|
27 |
#' variables to which a transfer is to be assumed in the model. |
|
28 |
#' If the argument \code{use_of_ff} is set to "min" |
|
29 |
#' and the model for the compartment is "SFO" or "SFORB", an |
|
30 |
#' additional [mkinsub()] argument can be \code{sink = FALSE}, effectively |
|
31 |
#' fixing the flux to sink to zero. |
|
32 |
#' In print.mkinmod, this argument is currently not used. |
|
33 |
#' @param use_of_ff Specification of the use of formation fractions in the |
|
34 |
#' model equations and, if applicable, the coefficient matrix. If "max", |
|
35 |
#' formation fractions are always used (default). If "min", a minimum use of |
|
36 |
#' formation fractions is made, i.e. each first-order pathway to a metabolite |
|
37 |
#' has its own rate constant. |
|
38 |
#' @param speclist The specification of the observed variables and their |
|
39 |
#' submodel types and pathways can be given as a single list using this |
|
40 |
#' argument. Default is NULL. |
|
41 |
#' @param quiet Should messages be suppressed? |
|
42 |
#' @param verbose If \code{TRUE}, passed to [inline::cfunction()] if |
|
43 |
#' applicable to give detailed information about the C function being built. |
|
44 |
#' @param name A name for the model. Should be a valid R object name. |
|
45 |
#' @param dll_dir Directory where an DLL object, if generated internally by |
|
46 |
#' [inline::cfunction()], should be saved. The DLL will only be stored in a |
|
47 |
#' permanent location for use in future sessions, if 'dll_dir' and 'name' |
|
48 |
#' are specified. This is helpful if fit objects are cached e.g. by knitr, |
|
49 |
#' as the cache remains functional across sessions if the DLL is stored in |
|
50 |
#' a user defined location. |
|
51 |
#' @param unload If a DLL from the target location in 'dll_dir' is already |
|
52 |
#' loaded, should that be unloaded first? |
|
53 |
#' @param overwrite If a file exists at the target DLL location in 'dll_dir', |
|
54 |
#' should this be overwritten? |
|
55 |
#' @importFrom methods signature |
|
56 |
#' @return A list of class \code{mkinmod} for use with [mkinfit()], |
|
57 |
#' containing, among others, |
|
58 |
#' \item{diffs}{ |
|
59 |
#' A vector of string representations of differential equations, one for |
|
60 |
#' each modelling variable. |
|
61 |
#' } |
|
62 |
#' \item{map}{ |
|
63 |
#' A list containing named character vectors for each observed variable, |
|
64 |
#' specifying the modelling variables by which it is represented. |
|
65 |
#' } |
|
66 |
#' \item{use_of_ff}{ |
|
67 |
#' The content of \code{use_of_ff} is passed on in this list component. |
|
68 |
#' } |
|
69 |
#' \item{deg_func}{ |
|
70 |
#' If generated, a function containing the solution of the degradation |
|
71 |
#' model. |
|
72 |
#' } |
|
73 |
#' \item{coefmat}{ |
|
74 |
#' The coefficient matrix, if the system of differential equations can be |
|
75 |
#' represented by one. |
|
76 |
#' } |
|
77 |
#' \item{cf}{ |
|
78 |
#' If generated, a compiled function calculating the derivatives as |
|
79 |
#' returned by cfunction. |
|
80 |
#' } |
|
81 |
#' @note The IORE submodel is not well tested for metabolites. When using this |
|
82 |
#' model for metabolites, you may want to read the note in the help |
|
83 |
#' page to [mkinfit]. |
|
84 |
#' @author Johannes Ranke |
|
85 |
#' @references FOCUS (2006) \dQuote{Guidance Document on Estimating Persistence |
|
86 |
#' and Degradation Kinetics from Environmental Fate Studies on Pesticides in |
|
87 |
#' EU Registration} Report of the FOCUS Work Group on Degradation Kinetics, |
|
88 |
#' EC Document Reference Sanco/10058/2005 version 2.0, 434 pp, |
|
89 |
#' \url{http://esdac.jrc.ec.europa.eu/projects/degradation-kinetics} |
|
90 |
#' |
|
91 |
#' NAFTA Technical Working Group on Pesticides (not dated) Guidance for |
|
92 |
#' Evaluating and Calculating Degradation Kinetics in Environmental Media |
|
93 |
#' @examples |
|
94 |
#' |
|
95 |
#' # Specify the SFO model (this is not needed any more, as we can now mkinfit("SFO", ...) |
|
96 |
#' SFO <- mkinmod(parent = mkinsub("SFO")) |
|
97 |
#' |
|
98 |
#' # One parent compound, one metabolite, both single first order |
|
99 |
#' SFO_SFO <- mkinmod( |
|
100 |
#' parent = mkinsub("SFO", "m1"), |
|
101 |
#' m1 = mkinsub("SFO")) |
|
102 |
#' print(SFO_SFO) |
|
103 |
#' |
|
104 |
#' \dontrun{ |
|
105 |
#' fit_sfo_sfo <- mkinfit(SFO_SFO, FOCUS_2006_D, quiet = TRUE, solution_type = "deSolve") |
|
106 |
#' |
|
107 |
#' # Now supplying compound names used for plotting, and write to user defined location |
|
108 |
#' # We need to choose a path outside the session tempdir because this gets removed |
|
109 |
#' DLL_dir <- "~/.local/share/mkin" |
|
110 |
#' if (!dir.exists(DLL_dir)) dir.create(DLL_dir) |
|
111 |
#' SFO_SFO.2 <- mkinmod( |
|
112 |
#' parent = mkinsub("SFO", "m1", full_name = "Test compound"), |
|
113 |
#' m1 = mkinsub("SFO", full_name = "Metabolite M1"), |
|
114 |
#' name = "SFO_SFO", dll_dir = DLL_dir, unload = TRUE, overwrite = TRUE) |
|
115 |
#' # Now we can save the model and restore it in a new session |
|
116 |
#' saveRDS(SFO_SFO.2, file = "~/SFO_SFO.rds") |
|
117 |
#' # Terminate the R session here if you would like to check, and then do |
|
118 |
#' library(mkin) |
|
119 |
#' SFO_SFO.3 <- readRDS("~/SFO_SFO.rds") |
|
120 |
#' fit_sfo_sfo <- mkinfit(SFO_SFO.3, FOCUS_2006_D, quiet = TRUE, solution_type = "deSolve") |
|
121 |
#' |
|
122 |
#' # Show details of creating the C function |
|
123 |
#' SFO_SFO <- mkinmod( |
|
124 |
#' parent = mkinsub("SFO", "m1"), |
|
125 |
#' m1 = mkinsub("SFO"), verbose = TRUE) |
|
126 |
#' |
|
127 |
#' # The symbolic solution which is available in this case is not |
|
128 |
#' # made for human reading but for speed of computation |
|
129 |
#' SFO_SFO$deg_func |
|
130 |
#' |
|
131 |
#' # If we have several parallel metabolites |
|
132 |
#' # (compare tests/testthat/test_synthetic_data_for_UBA_2014.R) |
|
133 |
#' m_synth_DFOP_par <- mkinmod( |
|
134 |
#' parent = mkinsub("DFOP", c("M1", "M2")), |
|
135 |
#' M1 = mkinsub("SFO"), |
|
136 |
#' M2 = mkinsub("SFO"), |
|
137 |
#' quiet = TRUE) |
|
138 |
#' |
|
139 |
#' fit_DFOP_par_c <- mkinfit(m_synth_DFOP_par, |
|
140 |
#' synthetic_data_for_UBA_2014[[12]]$data, |
|
141 |
#' quiet = TRUE) |
|
142 |
#' } |
|
143 |
#' |
|
144 |
#' @export mkinmod |
|
145 |
mkinmod <- function(..., use_of_ff = "max", name = NULL, |
|
146 |
speclist = NULL, quiet = FALSE, verbose = FALSE, dll_dir = NULL, |
|
147 |
unload = FALSE, overwrite = FALSE) |
|
148 |
{ |
|
149 | 4940x |
if (is.null(speclist)) spec <- list(...) |
150 | 3905x |
else spec <- speclist |
151 | 8845x |
obs_vars <- names(spec) |
152 | ||
153 | 8845x |
save_msg <- "You need to specify both 'name' and 'dll_dir' to save a model DLL" |
154 | 8845x |
if (!is.null(dll_dir)) { |
155 | ! |
if (!dir.exists(dll_dir)) stop(dll_dir, " does not exist") |
156 | ! |
if (is.null(name)) stop(save_msg) |
157 |
} |
|
158 | ||
159 |
# Check if any of the names of the observed variables contains any other |
|
160 | 8845x |
for (obs_var in obs_vars) { |
161 | 104x |
if (length(grep(obs_var, obs_vars)) > 1) stop("Sorry, variable names can not contain each other") |
162 | 104x |
if (grepl("_to_", obs_var)) stop("Sorry, names of observed variables can not contain _to_") |
163 | 104x |
if (obs_var == "sink") stop("Naming a compound 'sink' is not supported") |
164 |
} |
|
165 | ||
166 | 8533x |
if (!use_of_ff %in% c("min", "max")) |
167 | 104x |
stop("The use of formation fractions 'use_of_ff' can only be 'min' or 'max'") |
168 | ||
169 | 8429x |
parms <- vector() |
170 |
# }}} |
|
171 | ||
172 |
# Do not return a coefficient matrix mat when FOMC, IORE, DFOP, HS or logistic is used for the parent {{{ |
|
173 | 8429x |
if(spec[[1]]$type %in% c("FOMC", "IORE", "DFOP", "HS", "logistic")) { |
174 | 2280x |
mat = FALSE |
175 | 6149x |
} else mat = TRUE |
176 |
#}}} |
|
177 | ||
178 |
# Establish a list of differential equations as well as a map from observed {{{ |
|
179 |
# compartments to differential equations |
|
180 | 8429x |
diffs <- vector() |
181 | 8429x |
map <- list() |
182 | 8429x |
for (varname in obs_vars) |
183 |
{ |
|
184 |
# Check the type component of the compartment specification {{{ |
|
185 | ! |
if(is.null(spec[[varname]]$type)) stop( |
186 | ! |
"Every part of the model specification must be a list containing a type component") |
187 | 104x |
if(!spec[[varname]]$type %in% c("SFO", "FOMC", "IORE", "DFOP", "HS", "SFORB", "logistic")) stop( |
188 | 104x |
"Available types are SFO, FOMC, IORE, DFOP, HS, SFORB and logistic only") |
189 | 13150x |
if(spec[[varname]]$type %in% c("FOMC", "DFOP", "HS", "logistic") & match(varname, obs_vars) != 1) { |
190 | 104x |
stop(paste("Types FOMC, DFOP, HS and logistic are only implemented for the first compartment,", |
191 | 104x |
"which is assumed to be the source compartment")) |
192 |
} |
|
193 |
#}}} |
|
194 |
# New (sub)compartments (boxes) needed for the model type {{{ |
|
195 | 13046x |
new_boxes <- switch(spec[[varname]]$type, |
196 | 13046x |
SFO = varname, |
197 | 13046x |
FOMC = varname, |
198 | 13046x |
IORE = varname, |
199 | 13046x |
DFOP = varname, |
200 | 13046x |
HS = varname, |
201 | 13046x |
logistic = varname, |
202 | 13046x |
SFORB = paste(varname, c("free", "bound"), sep = "_") |
203 |
) |
|
204 | 13046x |
map[[varname]] <- new_boxes |
205 | 13046x |
names(map[[varname]]) <- rep(spec[[varname]]$type, length(new_boxes)) #}}} |
206 |
# Start a new differential equation for each new box {{{ |
|
207 | 13046x |
new_diffs <- paste("d_", new_boxes, " =", sep = "") |
208 | 13046x |
names(new_diffs) <- new_boxes |
209 | 13046x |
diffs <- c(diffs, new_diffs) #}}} |
210 |
} #}}} |
|
211 | ||
212 |
# Create content of differential equations and build parameter list {{{ |
|
213 | 8221x |
for (varname in obs_vars) |
214 |
{ |
|
215 |
# Get the name of the box(es) we are working on for the decline term(s) |
|
216 | 12838x |
box_1 = map[[varname]][[1]] # This is the only box unless type is SFORB |
217 |
# Turn on sink if this is not explicitly excluded by the user by |
|
218 |
# specifying sink=FALSE |
|
219 | 4x |
if(is.null(spec[[varname]]$sink)) spec[[varname]]$sink <- TRUE |
220 | 12838x |
if(spec[[varname]]$type %in% c("SFO", "IORE", "SFORB")) { # {{{ Add decline term |
221 | 10838x |
if (use_of_ff == "min") { # Minimum use of formation fractions |
222 | 1304x |
if(spec[[varname]]$type == "IORE" && length(spec[[varname]]$to) > 0) { |
223 | 104x |
stop("Transformation reactions from compounds modelled with IORE\n", |
224 | 104x |
"are only supported with formation fractions (use_of_ff = 'max')") |
225 |
} |
|
226 | 1200x |
if(spec[[varname]]$sink) { |
227 |
# If sink is requested, add first-order/IORE sink term |
|
228 | 952x |
k_compound_sink <- paste("k", box_1, "sink", sep = "_") |
229 | 952x |
if(spec[[varname]]$type == "IORE") { |
230 | ! |
k_compound_sink <- paste("k__iore", box_1, "sink", sep = "_") |
231 |
} |
|
232 | 952x |
parms <- c(parms, k_compound_sink) |
233 | 952x |
decline_term <- paste(k_compound_sink, "*", box_1) |
234 | 952x |
if(spec[[varname]]$type == "IORE") { |
235 | ! |
N <- paste("N", box_1, sep = "_") |
236 | ! |
parms <- c(parms, N) |
237 | ! |
decline_term <- paste0(decline_term, "^", N) |
238 |
} |
|
239 |
} else { # otherwise no decline term needed here |
|
240 | 248x |
decline_term = "0" |
241 |
} |
|
242 |
} else { # Maximum use of formation fractions |
|
243 | 9534x |
k_compound <- paste("k", box_1, sep = "_") |
244 | 9534x |
if(spec[[varname]]$type == "IORE") { |
245 | 176x |
k_compound <- paste("k__iore", box_1, sep = "_") |
246 |
} |
|
247 | 9534x |
parms <- c(parms, k_compound) |
248 | 9534x |
decline_term <- paste(k_compound, "*", box_1) |
249 | 9534x |
if(spec[[varname]]$type == "IORE") { |
250 | 176x |
N <- paste("N", box_1, sep = "_") |
251 | 176x |
parms <- c(parms, N) |
252 | 176x |
decline_term <- paste0(decline_term, "^", N) |
253 |
} |
|
254 |
} |
|
255 |
} #}}} |
|
256 | 12734x |
if(spec[[varname]]$type == "FOMC") { # {{{ Add FOMC decline term |
257 |
# From p. 53 of the FOCUS kinetics report, without the power function so it works in C |
|
258 | 381x |
decline_term <- paste("(alpha/beta) * 1/((time/beta) + 1) *", box_1) |
259 | 381x |
parms <- c(parms, "alpha", "beta") |
260 |
} #}}} |
|
261 | 12734x |
if(spec[[varname]]$type == "DFOP") { # {{{ Add DFOP decline term |
262 |
# From p. 57 of the FOCUS kinetics report |
|
263 | 1283x |
decline_term <- paste("((k1 * g * exp(-k1 * time) + k2 * (1 - g) * exp(-k2 * time)) / (g * exp(-k1 * time) + (1 - g) * exp(-k2 * time))) *", box_1) |
264 | 1283x |
parms <- c(parms, "k1", "k2", "g") |
265 |
} #}}} |
|
266 | 12734x |
HS_decline <- "ifelse(time <= tb, k1, k2)" # Used below for automatic translation to C |
267 | 12734x |
if(spec[[varname]]$type == "HS") { # {{{ Add HS decline term |
268 |
# From p. 55 of the FOCUS kinetics report |
|
269 | 30x |
decline_term <- paste(HS_decline, "*", box_1) |
270 | 30x |
parms <- c(parms, "k1", "k2", "tb") |
271 |
} #}}} |
|
272 | 12734x |
if(spec[[varname]]$type == "logistic") { # {{{ Add logistic decline term |
273 |
# From p. 67 of the FOCUS kinetics report (2014) |
|
274 | 306x |
decline_term <- paste("(k0 * kmax)/(k0 + (kmax - k0) * exp(-r * time)) *", box_1) |
275 | 306x |
parms <- c(parms, "kmax", "k0", "r") |
276 |
} #}}} |
|
277 |
# Add origin decline term to box 1 (usually the only box, unless type is SFORB)#{{{ |
|
278 | 12734x |
diffs[[box_1]] <- paste(diffs[[box_1]], "-", decline_term)#}}} |
279 | 12734x |
if(spec[[varname]]$type == "SFORB") { # {{{ Add SFORB reversible binding terms |
280 | 25x |
box_2 = map[[varname]][[2]] |
281 | 25x |
k_free_bound <- paste("k", varname, "free", "bound", sep = "_") |
282 | 25x |
k_bound_free <- paste("k", varname, "bound", "free", sep = "_") |
283 | 25x |
parms <- c(parms, k_free_bound, k_bound_free) |
284 | 25x |
reversible_binding_term_1 <- paste("-", k_free_bound, "*", box_1, "+", |
285 | 25x |
k_bound_free, "*", box_2) |
286 | 25x |
reversible_binding_term_2 <- paste("+", k_free_bound, "*", box_1, "-", |
287 | 25x |
k_bound_free, "*", box_2) |
288 | 25x |
diffs[[box_1]] <- paste(diffs[[box_1]], reversible_binding_term_1) |
289 | 25x |
diffs[[box_2]] <- paste(diffs[[box_2]], reversible_binding_term_2) |
290 |
} #}}} |
|
291 | ||
292 |
# Transfer between compartments#{{{ |
|
293 | 12734x |
to <- spec[[varname]]$to |
294 | 12734x |
if(!is.null(to)) { |
295 |
# Name of box from which transfer takes place |
|
296 | 4174x |
origin_box <- box_1 |
297 | ||
298 |
# Number of targets |
|
299 | 4174x |
n_targets = length(to) |
300 | ||
301 |
# Add transfer terms to listed compartments |
|
302 | 4174x |
for (target in to) { |
303 | ! |
if (!target %in% obs_vars) stop("You did not specify a submodel for target variable ", target) |
304 | 4813x |
target_box <- switch(spec[[target]]$type, |
305 | 4813x |
SFO = target, |
306 | 4813x |
IORE = target, |
307 | 4813x |
SFORB = paste(target, "free", sep = "_")) |
308 | 4813x |
if (use_of_ff == "min" && spec[[varname]]$type %in% c("SFO", "SFORB")) |
309 |
{ |
|
310 | 601x |
k_from_to <- paste("k", origin_box, target_box, sep = "_") |
311 | 601x |
parms <- c(parms, k_from_to) |
312 | 601x |
diffs[[origin_box]] <- paste(diffs[[origin_box]], "-", |
313 | 601x |
k_from_to, "*", origin_box) |
314 | 601x |
diffs[[target_box]] <- paste(diffs[[target_box]], "+", |
315 | 601x |
k_from_to, "*", origin_box) |
316 |
} else { |
|
317 |
# Do not introduce a formation fraction if this is the only target |
|
318 | 4212x |
if (spec[[varname]]$sink == FALSE && n_targets == 1) { |
319 | 689x |
diffs[[target_box]] <- paste(diffs[[target_box]], "+", |
320 | 689x |
decline_term) |
321 |
} else { |
|
322 | 3523x |
fraction_to_target = paste("f", origin_box, "to", target, sep = "_") |
323 | 3523x |
parms <- c(parms, fraction_to_target) |
324 | 3523x |
diffs[[target_box]] <- paste(diffs[[target_box]], "+", |
325 | 3523x |
fraction_to_target, "*", decline_term) |
326 |
} |
|
327 |
} |
|
328 |
} |
|
329 |
} #}}} |
|
330 |
} #}}} |
|
331 | ||
332 | 8117x |
model <- list(diffs = diffs, parms = parms, map = map, spec = spec, use_of_ff = use_of_ff, name = name) |
333 | ||
334 |
# Create coefficient matrix if possible #{{{ |
|
335 | 8117x |
if (mat) { |
336 | 5941x |
boxes <- names(diffs) |
337 | 5941x |
n <- length(boxes) |
338 | 5941x |
m <- matrix(nrow=n, ncol=n, dimnames=list(boxes, boxes)) |
339 | ||
340 | 5941x |
if (use_of_ff == "min") { # {{{ Minimum use of formation fractions |
341 | 600x |
for (from in boxes) { |
342 | 1201x |
for (to in boxes) { |
343 | 2405x |
if (from == to) { # diagonal elements |
344 | 1201x |
k.candidate = paste("k", from, c(boxes, "sink"), sep = "_") |
345 | 1201x |
k.candidate = sub("free.*bound", "free_bound", k.candidate) |
346 | 1201x |
k.candidate = sub("bound.*free", "bound_free", k.candidate) |
347 | 1201x |
k.effective = intersect(model$parms, k.candidate) |
348 | 1201x |
m[from,to] = ifelse(length(k.effective) > 0, |
349 | 1201x |
paste("-", k.effective, collapse = " "), "0") |
350 | ||
351 |
} else { # off-diagonal elements |
|
352 | 1204x |
k.candidate = paste("k", from, to, sep = "_") |
353 | 1204x |
if (sub("_free$", "", from) == sub("_bound$", "", to)) { |
354 | 1x |
k.candidate = paste("k", sub("_free$", "_free_bound", from), sep = "_") |
355 |
} |
|
356 | 1204x |
if (sub("_bound$", "", from) == sub("_free$", "", to)) { |
357 | 1x |
k.candidate = paste("k", sub("_bound$", "_bound_free", from), sep = "_") |
358 |
} |
|
359 | 1204x |
k.effective = intersect(model$parms, k.candidate) |
360 | 1204x |
m[to, from] = ifelse(length(k.effective) > 0, |
361 | 1204x |
k.effective, "0") |
362 |
} |
|
363 |
} |
|
364 |
} # }}} |
|
365 |
} else { # {{{ Use formation fractions where possible |
|
366 | 5341x |
for (from in boxes) { |
367 | 8074x |
for (to in boxes) { |
368 | 15220x |
if (from == to) { # diagonal elements |
369 | 8074x |
k.candidate = paste("k", from, sep = "_") |
370 | 8074x |
m[from,to] = ifelse(k.candidate %in% model$parms, |
371 | 8074x |
paste("-", k.candidate), "0") |
372 | 8074x |
if(grepl("_free", from)) { # add transfer to bound compartment for SFORB |
373 | 24x |
m[from,to] = paste(m[from,to], "-", paste("k", from, "bound", sep = "_")) |
374 |
} |
|
375 | 8074x |
if(grepl("_bound", from)) { # add backtransfer to free compartment for SFORB |
376 | 24x |
m[from,to] = paste("- k", from, "free", sep = "_") |
377 |
} |
|
378 | 8074x |
m[from,to] = m[from,to] |
379 |
} else { # off-diagonal elements |
|
380 | 7146x |
f.candidate = paste("f", from, "to", to, sep = "_") |
381 | 7146x |
k.candidate = paste("k", from, to, sep = "_") |
382 | 7146x |
k.candidate = sub("free.*bound", "free_bound", k.candidate) |
383 | 7146x |
k.candidate = sub("bound.*free", "bound_free", k.candidate) |
384 | 7146x |
m[to, from] = ifelse(f.candidate %in% model$parms, |
385 | 7146x |
paste(f.candidate, " * k_", from, sep = ""), |
386 | 7146x |
ifelse(k.candidate %in% model$parms, k.candidate, "0")) |
387 |
# Special case: singular pathway and no sink |
|
388 | 7146x |
if (spec[[from]]$sink == FALSE && length(spec[[from]]$to) == 1 && to %in% spec[[from]]$to) { |
389 | 689x |
m[to, from] = paste("k", from, sep = "_") |
390 |
} |
|
391 |
} |
|
392 |
} |
|
393 |
} |
|
394 |
} # }}} |
|
395 | 5941x |
model$coefmat <- m |
396 |
}#}}} |
|
397 | ||
398 |
# Try to create a function compiled from C code if there is more than one observed variable {{{ |
|
399 |
# and a compiler is available |
|
400 | 8117x |
if (length(obs_vars) > 1 & pkgbuild::has_compiler()) { |
401 | ||
402 |
# Translate the R code for the derivatives to C code |
|
403 | 3728x |
diffs.C <- paste(diffs, collapse = ";\n") |
404 | 3728x |
diffs.C <- paste0(diffs.C, ";") |
405 | ||
406 |
# HS |
|
407 | 3728x |
diffs.C <- gsub(HS_decline, "(time <= tb ? k1 : k2)", diffs.C, fixed = TRUE) |
408 | ||
409 | 3728x |
for (i in seq_along(diffs)) { |
410 | 8347x |
state_var <- names(diffs)[i] |
411 | ||
412 |
# IORE |
|
413 | 8347x |
if (state_var %in% obs_vars) { |
414 | 8343x |
if (spec[[state_var]]$type == "IORE") { |
415 | ! |
diffs.C <- gsub(paste0(state_var, "^N_", state_var), |
416 | ! |
paste0("pow(y[", i - 1, "], N_", state_var, ")"), |
417 | ! |
diffs.C, fixed = TRUE) |
418 |
} |
|
419 |
} |
|
420 | ||
421 |
# Replace d_... terms by f[i-1] |
|
422 |
# First line |
|
423 | 8347x |
pattern <- paste0("^d_", state_var) |
424 | 8347x |
replacement <- paste0("\nf[", i - 1, "]") |
425 | 8347x |
diffs.C <- gsub(pattern, replacement, diffs.C) |
426 |
# Other lines |
|
427 | 8347x |
pattern <- paste0("\\nd_", state_var) |
428 | 8347x |
replacement <- paste0("\nf[", i - 1, "]") |
429 | 8347x |
diffs.C <- gsub(pattern, replacement, diffs.C) |
430 | ||
431 |
# Replace names of observed variables by y[i], |
|
432 |
# making the implicit assumption that the observed variables only occur after "* " |
|
433 | 8347x |
pattern <- paste0("\\* ", state_var) |
434 | 8347x |
replacement <- paste0("* y[", i - 1, "]") |
435 | 8347x |
diffs.C <- gsub(pattern, replacement, diffs.C) |
436 |
} |
|
437 | ||
438 | 3728x |
derivs_sig <- signature(n = "integer", t = "numeric", y = "numeric", |
439 | 3728x |
f = "numeric", rpar = "numeric", ipar = "integer") |
440 | ||
441 |
# Declare the time variable in the body of the function if it is used |
|
442 | 3728x |
derivs_code <- if (spec[[1]]$type %in% c("FOMC", "DFOP", "HS")) { |
443 | 1060x |
paste0("double time = *t;\n", diffs.C) |
444 |
} else { |
|
445 | 2668x |
diffs.C |
446 |
} |
|
447 | ||
448 |
# Define the function initializing the parameters |
|
449 | 3728x |
npar <- length(parms) |
450 | 3728x |
initpar_code <- paste0( |
451 | 3728x |
"static double parms [", npar, "];\n", |
452 | 3728x |
paste0("#define ", parms, " parms[", 0:(npar - 1), "]\n", collapse = ""), |
453 | 3728x |
"\n", |
454 | 3728x |
"void initpar(void (* odeparms)(int *, double *)) {\n", |
455 | 3728x |
" int N = ", npar, ";\n", |
456 | 3728x |
" odeparms(&N, parms);\n", |
457 | 3728x |
"}\n\n") |
458 | ||
459 |
# Try to build a shared library |
|
460 | 3728x |
model$cf <- try(inline::cfunction(derivs_sig, derivs_code, |
461 | 3728x |
otherdefs = initpar_code, |
462 | 3728x |
verbose = verbose, name = "diffs", |
463 | 3728x |
convention = ".C", language = "C"), |
464 | 3728x |
silent = TRUE) |
465 | ||
466 | 3728x |
if (!inherits(model$cf, "try-error")) { |
467 | 495x |
if (!quiet) message("Temporary DLL for differentials generated and loaded") |
468 | 3728x |
if (!is.null(dll_dir)) { |
469 |
# We suppress warnings, as we get a warning about a path "(embedding)" |
|
470 |
# under Windows, at least when using RStudio |
|
471 | 247x |
suppressWarnings(inline::moveDLL(model$cf, name, dll_dir, |
472 | 247x |
unload = unload, overwrite = overwrite, verbose = !quiet)) |
473 |
} |
|
474 | 3728x |
model$dll_info <- inline::getDynLib(model$cf) |
475 |
} |
|
476 |
} |
|
477 |
# }}} |
|
478 | ||
479 |
# Attach a degradation function if an analytical solution is available |
|
480 | 8117x |
model$deg_func <- create_deg_func(spec, use_of_ff) |
481 | ||
482 | 8117x |
class(model) <- "mkinmod" |
483 | 8117x |
return(model) |
484 |
} |
|
485 | ||
486 |
#' Print mkinmod objects |
|
487 |
#' |
|
488 |
#' Print mkinmod objects in a way that the user finds his way to get to its |
|
489 |
#' components. |
|
490 |
#' |
|
491 |
#' @rdname mkinmod |
|
492 |
#' @param x An \code{\link{mkinmod}} object. |
|
493 |
#' @export |
|
494 |
print.mkinmod <- function(x, ...) { |
|
495 | 104x |
cat("<mkinmod> model generated with\n") |
496 | 104x |
cat("Use of formation fractions $use_of_ff:", x$use_of_ff, "\n") |
497 | 104x |
cat("Specification $spec:\n") |
498 | 104x |
for (obs in names(x$spec)) { |
499 | 208x |
cat("$", obs, "\n", sep = "") |
500 | 208x |
spl <- x$spec[[obs]] |
501 | 208x |
cat("$type:", spl$type) |
502 | 104x |
if (!is.null(spl$to) && length(spl$to)) cat("; $to: ", paste(spl$to, collapse = ", "), sep = "") |
503 | 208x |
cat("; $sink: ", spl$sink, sep = "") |
504 | ! |
if (!is.null(spl$full_name)) if (!is.na(spl$full_name)) cat("; $full_name:", spl$full_name) |
505 | 208x |
cat("\n") |
506 |
} |
|
507 | 104x |
if (is.matrix(x$coefmat)) cat("Coefficient matrix $coefmat available\n") |
508 | ! |
if (!is.null(x$cf)) cat("Compiled model $cf available\n") |
509 | 104x |
cat("Differential equations:\n") |
510 | 104x |
nice_diffs <- gsub("^(d.*) =", "\\1/dt =", x[["diffs"]]) |
511 | 104x |
writeLines(strwrap(nice_diffs, exdent = 11)) |
512 |
} |
|
513 |
# vim: set foldmethod=marker ts=2 sw=2 expandtab: |
1 |
#' Fit nonlinear mixed-effects models built from one or more kinetic |
|
2 |
#' degradation models and one or more error models |
|
3 |
#' |
|
4 |
#' The name of the methods expresses that (**m**ultiple) **h**ierarchichal |
|
5 |
#' (also known as multilevel) **m**ulticompartment **kin**etic models are |
|
6 |
#' fitted. Our kinetic models are nonlinear, so we can use various nonlinear |
|
7 |
#' mixed-effects model fitting functions. |
|
8 |
#' |
|
9 |
#' @param objects A list of [mmkin] objects containing fits of the same |
|
10 |
#' degradation models to the same data, but using different error models. |
|
11 |
#' Alternatively, a single [mmkin] object containing fits of several |
|
12 |
#' degradation models to the same data |
|
13 |
#' @param backend The backend to be used for fitting. Currently, only saemix is |
|
14 |
#' supported |
|
15 |
#' @param no_random_effect Default is NULL and will be passed to [saem]. If a |
|
16 |
#' character vector is supplied, it will be passed to all calls to [saem], |
|
17 |
#' which will exclude random effects for all matching parameters. Alternatively, |
|
18 |
#' a list of character vectors or an object of class [illparms.mhmkin] can be |
|
19 |
#' specified. They have to have the same dimensions that the return object of |
|
20 |
#' the current call will have, i.e. the number of rows must match the number |
|
21 |
#' of degradation models in the mmkin object(s), and the number of columns must |
|
22 |
#' match the number of error models used in the mmkin object(s). |
|
23 |
#' @param algorithm The algorithm to be used for fitting (currently not used) |
|
24 |
#' @param \dots Further arguments that will be passed to the nonlinear mixed-effects |
|
25 |
#' model fitting function. |
|
26 |
#' @param cores The number of cores to be used for multicore processing. This |
|
27 |
#' is only used when the \code{cluster} argument is \code{NULL}. On Windows |
|
28 |
#' machines, cores > 1 is not supported, you need to use the \code{cluster} |
|
29 |
#' argument to use multiple logical processors. Per default, all cores detected |
|
30 |
#' by [parallel::detectCores()] are used, except on Windows where the default |
|
31 |
#' is 1. |
|
32 |
#' @param cluster A cluster as returned by [makeCluster] to be used for |
|
33 |
#' parallel execution. |
|
34 |
#' @importFrom parallel mclapply parLapply detectCores |
|
35 |
#' @return A two-dimensional [array] of fit objects and/or try-errors that can |
|
36 |
#' be indexed using the degradation model names for the first index (row index) |
|
37 |
#' and the error model names for the second index (column index), with class |
|
38 |
#' attribute 'mhmkin'. |
|
39 |
#' @author Johannes Ranke |
|
40 |
#' @seealso \code{\link{[.mhmkin}} for subsetting [mhmkin] objects |
|
41 |
#' @export |
|
42 |
mhmkin <- function(objects, ...) { |
|
43 | 375x |
UseMethod("mhmkin") |
44 |
} |
|
45 | ||
46 |
#' @export |
|
47 |
#' @rdname mhmkin |
|
48 |
mhmkin.mmkin <- function(objects, ...) { |
|
49 | ! |
mhmkin(list(objects), ...) |
50 |
} |
|
51 | ||
52 |
#' @export |
|
53 |
#' @rdname mhmkin |
|
54 |
#' @examples |
|
55 |
#' \dontrun{ |
|
56 |
#' # We start with separate evaluations of all the first six datasets with two |
|
57 |
#' # degradation models and two error models |
|
58 |
#' f_sep_const <- mmkin(c("SFO", "FOMC"), ds_fomc[1:6], cores = 2, quiet = TRUE) |
|
59 |
#' f_sep_tc <- update(f_sep_const, error_model = "tc") |
|
60 |
#' # The mhmkin function sets up hierarchical degradation models aka |
|
61 |
#' # nonlinear mixed-effects models for all four combinations, specifying |
|
62 |
#' # uncorrelated random effects for all degradation parameters |
|
63 |
#' f_saem_1 <- mhmkin(list(f_sep_const, f_sep_tc), cores = 2) |
|
64 |
#' status(f_saem_1) |
|
65 |
#' # The 'illparms' function shows that in all hierarchical fits, at least |
|
66 |
#' # one random effect is ill-defined (the confidence interval for the |
|
67 |
#' # random effect expressed as standard deviation includes zero) |
|
68 |
#' illparms(f_saem_1) |
|
69 |
#' # Therefore we repeat the fits, excluding the ill-defined random effects |
|
70 |
#' f_saem_2 <- update(f_saem_1, no_random_effect = illparms(f_saem_1)) |
|
71 |
#' status(f_saem_2) |
|
72 |
#' illparms(f_saem_2) |
|
73 |
#' # Model comparisons show that FOMC with two-component error is preferable, |
|
74 |
#' # and confirms our reduction of the default parameter model |
|
75 |
#' anova(f_saem_1) |
|
76 |
#' anova(f_saem_2) |
|
77 |
#' # The convergence plot for the selected model looks fine |
|
78 |
#' saemix::plot(f_saem_2[["FOMC", "tc"]]$so, plot.type = "convergence") |
|
79 |
#' # The plot of predictions versus data shows that we have a pretty data-rich |
|
80 |
#' # situation with homogeneous distribution of residuals, because we used the |
|
81 |
#' # same degradation model, error model and parameter distribution model that |
|
82 |
#' # was used in the data generation. |
|
83 |
#' plot(f_saem_2[["FOMC", "tc"]]) |
|
84 |
#' # We can specify the same parameter model reductions manually |
|
85 |
#' no_ranef <- list("parent_0", "log_beta", "parent_0", c("parent_0", "log_beta")) |
|
86 |
#' dim(no_ranef) <- c(2, 2) |
|
87 |
#' f_saem_2m <- update(f_saem_1, no_random_effect = no_ranef) |
|
88 |
#' anova(f_saem_2m) |
|
89 |
#' } |
|
90 |
mhmkin.list <- function(objects, backend = "saemix", algorithm = "saem", |
|
91 |
no_random_effect = NULL, |
|
92 |
..., |
|
93 |
cores = if (Sys.info()["sysname"] == "Windows") 1 else parallel::detectCores(), cluster = NULL) |
|
94 |
{ |
|
95 | 375x |
call <- match.call() |
96 | 375x |
dot_args <- list(...) |
97 | 375x |
backend_function <- switch(backend, |
98 | 375x |
saemix = "saem" |
99 |
) |
|
100 | ||
101 | 375x |
deg_models <- lapply(objects[[1]][, 1], function(x) x$mkinmod) |
102 | 375x |
names(deg_models) <- dimnames(objects[[1]])$model |
103 | 375x |
n.deg <- length(deg_models) |
104 | ||
105 | 375x |
ds <- lapply(objects[[1]][1, ], function(x) x$data) |
106 | ||
107 | 375x |
for (other in objects[-1]) { |
108 |
# Check if the degradation models in all objects are the same |
|
109 | 375x |
for (deg_model_name in names(deg_models)) { |
110 | 750x |
if (!all.equal(other[[deg_model_name, 1]]$mkinmod$spec, |
111 | 750x |
deg_models[[deg_model_name]]$spec)) |
112 |
{ |
|
113 | ! |
stop("The mmkin objects have to be based on the same degradation models") |
114 |
} |
|
115 |
} |
|
116 |
# Check if they have been fitted to the same dataset |
|
117 | 375x |
other_object_ds <- lapply(other[1, ], function(x) x$data) |
118 | 375x |
for (i in 1:length(ds)) { |
119 | 2250x |
if (!all.equal(ds[[i]][c("time", "variable", "observed")], |
120 | 2250x |
other_object_ds[[i]][c("time", "variable", "observed")])) |
121 |
{ |
|
122 | ! |
stop("The mmkin objects have to be fitted to the same datasets") |
123 |
} |
|
124 |
} |
|
125 |
} |
|
126 | ||
127 | 375x |
n.o <- length(objects) |
128 | ||
129 | 375x |
error_models = sapply(objects, function(x) x[[1]]$err_mod) |
130 | 375x |
n.e <- length(error_models) |
131 | ||
132 | 375x |
n.fits <- n.deg * n.e |
133 | 375x |
fit_indices <- matrix(1:n.fits, ncol = n.e) |
134 | 375x |
dimnames(fit_indices) <- list(degradation = names(deg_models), |
135 | 375x |
error = error_models) |
136 | ||
137 | 375x |
if (is.null(no_random_effect) || is.null(dim(no_random_effect))) { |
138 | 129x |
no_ranef <- rep(list(no_random_effect), n.fits) |
139 | 129x |
dim(no_ranef) <- dim(fit_indices) |
140 |
} else { |
|
141 | 246x |
if (!identical(dim(no_random_effect), dim(fit_indices))) { |
142 | ! |
stop("Dimensions of argument 'no_random_effect' are not suitable") |
143 |
} |
|
144 | 246x |
if (is(no_random_effect, "illparms.mhmkin")) { |
145 | 125x |
no_ranef_dim <- dim(no_random_effect) |
146 | 125x |
no_ranef <- lapply(no_random_effect, function(x) { |
147 | 500x |
no_ranef_split <- strsplit(x, ", ") |
148 | 500x |
ret <- sapply(no_ranef_split, function(y) { |
149 | 500x |
gsub("sd\\((.*)\\)", "\\1", y) |
150 |
}) |
|
151 | 500x |
return(ret) |
152 |
}) |
|
153 | 125x |
dim(no_ranef) <- no_ranef_dim |
154 |
} else { |
|
155 | 121x |
no_ranef <- no_random_effect |
156 |
} |
|
157 |
} |
|
158 | ||
159 | 375x |
fit_function <- function(fit_index) { |
160 | 12x |
w <- which(fit_indices == fit_index, arr.ind = TRUE) |
161 | 12x |
deg_index <- w[1] |
162 | 12x |
error_index <- w[2] |
163 | 12x |
mmkin_row <- objects[[error_index]][deg_index, ] |
164 | 12x |
res <- try(do.call(backend_function, |
165 | 12x |
args = c( |
166 | 12x |
list(mmkin_row), |
167 | 12x |
dot_args, |
168 | 12x |
list(no_random_effect = no_ranef[[deg_index, error_index]])))) |
169 | 12x |
return(res) |
170 |
} |
|
171 | ||
172 | ||
173 | 375x |
fit_time <- system.time({ |
174 | 375x |
if (is.null(cluster)) { |
175 | 375x |
results <- parallel::mclapply(as.list(1:n.fits), fit_function, |
176 | 375x |
mc.cores = cores, mc.preschedule = FALSE) |
177 |
} else { |
|
178 | ! |
results <- parallel::parLapply(cluster, as.list(1:n.fits), fit_function) |
179 |
} |
|
180 |
}) |
|
181 | ||
182 | 363x |
attributes(results) <- attributes(fit_indices) |
183 | 363x |
attr(results, "call") <- call |
184 | 363x |
attr(results, "time") <- fit_time |
185 | 363x |
class(results) <- switch(backend, |
186 | 363x |
saemix = c("mhmkin.saem.mmkin", "mhmkin") |
187 |
) |
|
188 | 363x |
return(results) |
189 |
} |
|
190 | ||
191 |
#' Subsetting method for mhmkin objects |
|
192 |
#' |
|
193 |
#' @param x An [mhmkin] object. |
|
194 |
#' @param i Row index selecting the fits for specific models |
|
195 |
#' @param j Column index selecting the fits to specific datasets |
|
196 |
#' @param drop If FALSE, the method always returns an mhmkin object, otherwise |
|
197 |
#' either a list of fit objects or a single fit object. |
|
198 |
#' @return An object inheriting from \code{\link{mhmkin}}. |
|
199 |
#' @rdname mhmkin |
|
200 |
#' @export |
|
201 |
`[.mhmkin` <- function(x, i, j, ..., drop = FALSE) { |
|
202 | ! |
original_class <- class(x) |
203 | ! |
class(x) <- NULL |
204 | ! |
x_sub <- x[i, j, drop = drop] |
205 | ||
206 | ! |
if (!drop) { |
207 | ! |
class(x_sub) <- original_class |
208 |
} |
|
209 | ! |
return(x_sub) |
210 |
} |
|
211 | ||
212 |
#' Print method for mhmkin objects |
|
213 |
#' |
|
214 |
#' @rdname mhmkin |
|
215 |
#' @export |
|
216 |
print.mhmkin <- function(x, ...) { |
|
217 | 125x |
cat("<mhmkin> object\n") |
218 | 125x |
cat("Status of individual fits:\n\n") |
219 | 125x |
print(status(x)) |
220 |
} |
|
221 | ||
222 |
#' Check if fit within an mhmkin object failed |
|
223 |
#' @param x The object to be checked |
|
224 |
check_failed <- function(x) { |
|
225 | 1936x |
if (inherits(x, "try-error")) { |
226 | ! |
return(TRUE) |
227 |
} else { |
|
228 | 1936x |
if (inherits(x$so, "try-error")) { |
229 | ! |
return(TRUE) |
230 |
} else { |
|
231 | 1936x |
return(FALSE) |
232 |
} |
|
233 |
} |
|
234 |
} |
|
235 | ||
236 |
#' @export |
|
237 |
AIC.mhmkin <- function(object, ..., k = 2) { |
|
238 | 125x |
res <- sapply(object, function(x) { |
239 | ! |
if (check_failed(x)) return(NA) |
240 | 500x |
else return(AIC(x$so, k = k)) |
241 |
}) |
|
242 | 125x |
dim(res) <- dim(object) |
243 | 125x |
dimnames(res) <- dimnames(object) |
244 | 125x |
return(res) |
245 |
} |
|
246 | ||
247 |
#' @export |
|
248 |
BIC.mhmkin <- function(object, ...) { |
|
249 | 125x |
res <- sapply(object, function(x) { |
250 | ! |
if (check_failed(x)) return(NA) |
251 | 500x |
else return(BIC(x$so)) |
252 |
}) |
|
253 | 125x |
dim(res) <- dim(object) |
254 | 125x |
dimnames(res) <- dimnames(object) |
255 | 125x |
return(res) |
256 |
} |
|
257 | ||
258 |
#' @export |
|
259 |
update.mhmkin <- function(object, ..., evaluate = TRUE) { |
|
260 | 246x |
call <- attr(object, "call") |
261 |
# For some reason we get mhkin.list in call[[1]] when using mhmkin from the |
|
262 |
# loaded package so we need to fix this so we do not have to export |
|
263 |
# mhmkin.list in addition to the S3 method mhmkin |
|
264 | 246x |
call[[1]] <- mhmkin |
265 | ||
266 | 246x |
update_arguments <- match.call(expand.dots = FALSE)$... |
267 | ||
268 | 246x |
if (length(update_arguments) > 0) { |
269 | 246x |
update_arguments_in_call <- !is.na(match(names(update_arguments), names(call))) |
270 |
} |
|
271 | ||
272 | 246x |
for (a in names(update_arguments)[update_arguments_in_call]) { |
273 | ! |
call[[a]] <- update_arguments[[a]] |
274 |
} |
|
275 | ||
276 | 246x |
update_arguments_not_in_call <- !update_arguments_in_call |
277 | 246x |
if(any(update_arguments_not_in_call)) { |
278 | 246x |
call <- c(as.list(call), update_arguments[update_arguments_not_in_call]) |
279 | 246x |
call <- as.call(call) |
280 |
} |
|
281 | 246x |
if(evaluate) eval(call, parent.frame()) |
282 | ! |
else call |
283 |
} |
|
284 | ||
285 |
#' @export |
|
286 |
anova.mhmkin <- function(object, ..., |
|
287 |
method = c("is", "lin", "gq"), test = FALSE, model.names = "auto") { |
|
288 | 234x |
if (identical(model.names, "auto")) { |
289 | 234x |
model.names <- outer(rownames(object), colnames(object), paste) |
290 |
} |
|
291 | 234x |
failed_index <- which(sapply(object, check_failed), arr.ind = TRUE) |
292 | 234x |
if (length(failed_index > 0)) { |
293 | ! |
rlang::inject(anova(!!!(object[-failed_index]), method = method, test = test, |
294 | ! |
model.names = model.names[-failed_index])) |
295 |
} else { |
|
296 | 234x |
rlang::inject(anova(!!!(object), method = method, test = test, |
297 | 234x |
model.names = model.names)) |
298 |
} |
|
299 |
} |
|
300 |
1 |
#' Evaluate parent kinetics using the NAFTA guidance |
|
2 |
#' |
|
3 |
#' The function fits the SFO, IORE and DFOP models using \code{\link{mmkin}} |
|
4 |
#' and returns an object of class \code{nafta} that has methods for printing |
|
5 |
#' and plotting. |
|
6 |
#' |
|
7 |
#' @param ds A dataframe that must contain one variable called "time" with the |
|
8 |
#' time values specified by the \code{time} argument, one column called |
|
9 |
#' "name" with the grouping of the observed values, and finally one column of |
|
10 |
#' observed values called "value". |
|
11 |
#' @param title Optional title of the dataset |
|
12 |
#' @param quiet Should the evaluation text be shown? |
|
13 |
#' @param \dots Further arguments passed to \code{\link{mmkin}} (not for the |
|
14 |
#' printing method). |
|
15 |
#' @importFrom stats qf |
|
16 |
#' @return An list of class \code{nafta}. The list element named "mmkin" is the |
|
17 |
#' \code{\link{mmkin}} object containing the fits of the three models. The |
|
18 |
#' list element named "title" contains the title of the dataset used. The |
|
19 |
#' list element "data" contains the dataset used in the fits. |
|
20 |
#' @author Johannes Ranke |
|
21 |
#' @source NAFTA (2011) Guidance for evaluating and calculating degradation |
|
22 |
#' kinetics in environmental media. NAFTA Technical Working Group on |
|
23 |
#' Pesticides |
|
24 |
#' \url{https://www.epa.gov/pesticide-science-and-assessing-pesticide-risks/guidance-evaluating-and-calculating-degradation} |
|
25 |
#' accessed 2019-02-22 |
|
26 |
#' |
|
27 |
#' US EPA (2015) Standard Operating Procedure for Using the NAFTA Guidance to |
|
28 |
#' Calculate Representative Half-life Values and Characterizing Pesticide |
|
29 |
#' Degradation |
|
30 |
#' \url{https://www.epa.gov/pesticide-science-and-assessing-pesticide-risks/standard-operating-procedure-using-nafta-guidance} |
|
31 |
#' @examples |
|
32 |
#' |
|
33 |
#' nafta_evaluation <- nafta(NAFTA_SOP_Appendix_D, cores = 1) |
|
34 |
#' print(nafta_evaluation) |
|
35 |
#' plot(nafta_evaluation) |
|
36 |
#' |
|
37 |
#' @export |
|
38 |
nafta <- function(ds, title = NA, quiet = FALSE, ...) { |
|
39 | 264x |
if (length(levels(ds$name)) > 1) { |
40 | 88x |
stop("The NAFTA procedure is only defined for decline data for a single compound") |
41 |
} |
|
42 | 176x |
n <- nrow(subset(ds, !is.na(value))) |
43 | 176x |
models <- c("SFO", "IORE", "DFOP") |
44 | ||
45 | 176x |
result <- list(title = title, data = ds) |
46 | 176x |
result$mmkin <- mmkin(models, list(ds), quiet = TRUE, ...) |
47 | ||
48 | 176x |
distimes <- lapply(result$mmkin, function(x) as.numeric(endpoints(x)$distimes["parent", ])) |
49 | ||
50 | 176x |
result$distimes <- matrix(NA, nrow = 3, ncol = 3, |
51 | 176x |
dimnames = list(models, c("DT50", "DT90", "DT50_rep"))) |
52 | 176x |
result$distimes["SFO", ] <- distimes[[1]][c(1, 2, 1)] |
53 | 176x |
result$distimes["IORE", ] <- distimes[[2]][c(1, 2, 3)] |
54 | 176x |
result$distimes["DFOP", ] <- distimes[[3]][c(1, 2, 5)] |
55 | ||
56 |
# Get parameters with statistics |
|
57 | 176x |
result$parameters <- lapply(result$mmkin, function(x) { |
58 | 528x |
summary(x)$bpar[, c(1, 4:6)] |
59 |
}) |
|
60 | 176x |
names(result$parameters) <- models |
61 | ||
62 |
# Compare the sum of squared residuals (SSR) to the upper bound of the |
|
63 |
# confidence region of the SSR for the IORE model |
|
64 | 176x |
result$S <- sapply(result$mmkin, function(x) sum(x$data$residual^2)) |
65 | 176x |
names(result$S) <- c("SFO", "IORE", "DFOP") |
66 |
# Equation (3) on p. 3 |
|
67 | 176x |
p <- 3 |
68 | 176x |
result$S["IORE"] |
69 | 176x |
result$S_c <- result$S[["IORE"]] * (1 + p/(n - p) * qf(0.5, p, n - p)) |
70 | ||
71 | 176x |
result$t_rep <- .evaluate_nafta_results(result$S, result$S_c, |
72 | 176x |
result$distimes, quiet = quiet) |
73 | ||
74 | 176x |
class(result) <- "nafta" |
75 | 176x |
return(result) |
76 |
} |
|
77 | ||
78 |
#' Plot the results of the three models used in the NAFTA scheme. |
|
79 |
#' |
|
80 |
#' The plots are ordered with increasing complexity of the model in this |
|
81 |
#' function (SFO, then IORE, then DFOP). |
|
82 |
#' |
|
83 |
#' Calls \code{\link{plot.mmkin}}. |
|
84 |
#' |
|
85 |
#' @param x An object of class \code{\link{nafta}}. |
|
86 |
#' @param legend Should a legend be added? |
|
87 |
#' @param main Possibility to override the main title of the plot. |
|
88 |
#' @param \dots Further arguments passed to \code{\link{plot.mmkin}}. |
|
89 |
#' @return The function is called for its side effect. |
|
90 |
#' @author Johannes Ranke |
|
91 |
#' @export |
|
92 |
plot.nafta <- function(x, legend = FALSE, main = "auto", ...) { |
|
93 | 176x |
if (main == "auto") { |
94 | ! |
if (is.na(x$title)) main = "" |
95 | 176x |
else main = x$title |
96 |
} |
|
97 | 176x |
plot(x$mmkin, ..., legend = legend, main = main) |
98 |
} |
|
99 | ||
100 |
#' Print nafta objects |
|
101 |
#' |
|
102 |
#' Print nafta objects. The results for the three models are printed in the |
|
103 |
#' order of increasing model complexity, i.e. SFO, then IORE, and finally DFOP. |
|
104 |
#' |
|
105 |
#' @param x An \code{\link{nafta}} object. |
|
106 |
#' @param digits Number of digits to be used for printing parameters and |
|
107 |
#' dissipation times. |
|
108 |
#' @rdname nafta |
|
109 |
#' @export |
|
110 |
print.nafta <- function(x, quiet = TRUE, digits = 3, ...) { |
|
111 | 176x |
cat("Sums of squares:\n") |
112 | 176x |
print(x$S) |
113 | 176x |
cat("\nCritical sum of squares for checking the SFO model:\n") |
114 | 176x |
print(x$S_c) |
115 | 176x |
cat("\nParameters:\n") |
116 | 176x |
print(x$parameters, digits = digits) |
117 | 176x |
t_rep <- .evaluate_nafta_results(x$S, x$S_c, x$distimes, quiet = quiet) |
118 | 176x |
cat("\nDTx values:\n") |
119 | 176x |
print(signif(x$distimes, digits = digits)) |
120 | 176x |
cat("\nRepresentative half-life:\n") |
121 | 176x |
print(round(t_rep, 2)) |
122 |
} |
|
123 | ||
124 |
.evaluate_nafta_results <- function(S, S_c, distimes, quiet = FALSE) { |
|
125 | 352x |
t_SFO <- distimes["IORE", "DT50"] |
126 | 352x |
t_IORE <- distimes["IORE", "DT50_rep"] |
127 | 352x |
t_DFOP2 <- distimes["DFOP", "DT50_rep"] |
128 | ||
129 | 352x |
if (S["SFO"] < S_c) { |
130 | ! |
if (!quiet) { |
131 | ! |
message("S_SFO is lower than the critical value S_c, use the SFO model") |
132 |
} |
|
133 | ! |
t_rep <- t_SFO |
134 |
} else { |
|
135 | 352x |
if (!quiet) { |
136 | 88x |
message("The SFO model is rejected as S_SFO is equal or higher than the critical value S_c") |
137 |
} |
|
138 | 352x |
if (t_IORE < t_DFOP2) { |
139 | 176x |
if (!quiet) { |
140 | 88x |
message("The half-life obtained from the IORE model may be used") |
141 |
} |
|
142 | 176x |
t_rep <- t_IORE |
143 |
} else { |
|
144 | 176x |
if (!quiet) { |
145 | ! |
message("The representative half-life of the IORE model is longer than the one corresponding") |
146 | ! |
message("to the terminal degradation rate found with the DFOP model.") |
147 | ! |
message("The representative half-life obtained from the DFOP model may be used") |
148 |
} |
|
149 | 176x |
t_rep <- t_DFOP2 |
150 |
} |
|
151 |
} |
|
152 | 352x |
return(t_rep) |
153 |
} |
1 |
#' Summary method for class "nlme.mmkin" |
|
2 |
#' |
|
3 |
#' Lists model equations, initial parameter values, optimised parameters |
|
4 |
#' for fixed effects (population), random effects (deviations from the |
|
5 |
#' population mean) and residual error model, as well as the resulting |
|
6 |
#' endpoints such as formation fractions and DT50 values. Optionally |
|
7 |
#' (default is FALSE), the data are listed in full. |
|
8 |
#' |
|
9 |
#' @param object an object of class [nlme.mmkin] |
|
10 |
#' @param x an object of class [summary.nlme.mmkin] |
|
11 |
#' @param data logical, indicating whether the full data should be included in |
|
12 |
#' the summary. |
|
13 |
#' @param verbose Should the summary be verbose? |
|
14 |
#' @param distimes logical, indicating whether DT50 and DT90 values should be |
|
15 |
#' included. |
|
16 |
#' @param alpha error level for confidence interval estimation from the t |
|
17 |
#' distribution |
|
18 |
#' @param digits Number of digits to use for printing |
|
19 |
#' @param \dots optional arguments passed to methods like \code{print}. |
|
20 |
#' @return The summary function returns a list based on the [nlme] object |
|
21 |
#' obtained in the fit, with at least the following additional components |
|
22 |
#' \item{nlmeversion, mkinversion, Rversion}{The nlme, mkin and R versions used} |
|
23 |
#' \item{date.fit, date.summary}{The dates where the fit and the summary were |
|
24 |
#' produced} |
|
25 |
#' \item{diffs}{The differential equations used in the degradation model} |
|
26 |
#' \item{use_of_ff}{Was maximum or minimum use made of formation fractions} |
|
27 |
#' \item{data}{The data} |
|
28 |
#' \item{confint_trans}{Transformed parameters as used in the optimisation, with confidence intervals} |
|
29 |
#' \item{confint_back}{Backtransformed parameters, with confidence intervals if available} |
|
30 |
#' \item{ff}{The estimated formation fractions derived from the fitted |
|
31 |
#' model.} |
|
32 |
#' \item{distimes}{The DT50 and DT90 values for each observed variable.} |
|
33 |
#' \item{SFORB}{If applicable, eigenvalues of SFORB components of the model.} |
|
34 |
#' The print method is called for its side effect, i.e. printing the summary. |
|
35 |
#' @importFrom stats predict |
|
36 |
#' @author Johannes Ranke for the mkin specific parts |
|
37 |
#' José Pinheiro and Douglas Bates for the components inherited from nlme |
|
38 |
#' @examples |
|
39 |
#' |
|
40 |
#' # Generate five datasets following SFO kinetics |
|
41 |
#' sampling_times = c(0, 1, 3, 7, 14, 28, 60, 90, 120) |
|
42 |
#' dt50_sfo_in_pop <- 50 |
|
43 |
#' k_in_pop <- log(2) / dt50_sfo_in_pop |
|
44 |
#' set.seed(1234) |
|
45 |
#' k_in <- rlnorm(5, log(k_in_pop), 0.5) |
|
46 |
#' SFO <- mkinmod(parent = mkinsub("SFO")) |
|
47 |
#' |
|
48 |
#' pred_sfo <- function(k) { |
|
49 |
#' mkinpredict(SFO, |
|
50 |
#' c(k_parent = k), |
|
51 |
#' c(parent = 100), |
|
52 |
#' sampling_times) |
|
53 |
#' } |
|
54 |
#' |
|
55 |
#' ds_sfo_mean <- lapply(k_in, pred_sfo) |
|
56 |
#' names(ds_sfo_mean) <- paste("ds", 1:5) |
|
57 |
#' |
|
58 |
#' set.seed(12345) |
|
59 |
#' ds_sfo_syn <- lapply(ds_sfo_mean, function(ds) { |
|
60 |
#' add_err(ds, |
|
61 |
#' sdfunc = function(value) sqrt(1^2 + value^2 * 0.07^2), |
|
62 |
#' n = 1)[[1]] |
|
63 |
#' }) |
|
64 |
#' |
|
65 |
#' \dontrun{ |
|
66 |
#' # Evaluate using mmkin and nlme |
|
67 |
#' library(nlme) |
|
68 |
#' f_mmkin <- mmkin("SFO", ds_sfo_syn, quiet = TRUE, error_model = "tc", cores = 1) |
|
69 |
#' f_nlme <- nlme(f_mmkin) |
|
70 |
#' summary(f_nlme, data = TRUE) |
|
71 |
#' } |
|
72 |
#' |
|
73 |
#' @export |
|
74 |
summary.nlme.mmkin <- function(object, data = FALSE, verbose = FALSE, distimes = TRUE, alpha = 0.05, ...) { |
|
75 | ||
76 | 319x |
mod_vars <- names(object$mkinmod$diffs) |
77 | ||
78 | 319x |
confint_trans <- intervals(object, which = "fixed", level = 1 - alpha)$fixed |
79 | 319x |
attr(confint_trans, "label") <- NULL |
80 | 319x |
pnames <- rownames(confint_trans) |
81 | ||
82 | 319x |
bp <- backtransform_odeparms(confint_trans[, "est."], object$mkinmod, |
83 | 319x |
object$transform_rates, object$transform_fractions) |
84 | 319x |
bpnames <- names(bp) |
85 | ||
86 |
# variance-covariance estimates for fixed effects (from summary.lme) |
|
87 | 319x |
fixed <- fixef(object) |
88 | 319x |
stdFixed <- sqrt(diag(as.matrix(object$varFix))) |
89 | 319x |
object$corFixed <- array( |
90 | 319x |
t(object$varFix/stdFixed)/stdFixed, |
91 | 319x |
dim(object$varFix), |
92 | 319x |
list(names(fixed), names(fixed))) |
93 | ||
94 |
# Transform boundaries of CI for one parameter at a time, |
|
95 |
# with the exception of sets of formation fractions (single fractions are OK). |
|
96 | 319x |
f_names_skip <- character(0) |
97 | 319x |
for (box in mod_vars) { # Figure out sets of fractions to skip |
98 | 436x |
f_names <- grep(paste("^f", box, sep = "_"), pnames, value = TRUE) |
99 | 436x |
n_paths <- length(f_names) |
100 | ! |
if (n_paths > 1) f_names_skip <- c(f_names_skip, f_names) |
101 |
} |
|
102 | ||
103 | 319x |
confint_back <- matrix(NA, nrow = length(bp), ncol = 3, |
104 | 319x |
dimnames = list(bpnames, colnames(confint_trans))) |
105 | 319x |
confint_back[, "est."] <- bp |
106 | ||
107 | 319x |
for (pname in pnames) { |
108 | 1410x |
if (!pname %in% f_names_skip) { |
109 | 1410x |
par.lower <- confint_trans[pname, "lower"] |
110 | 1410x |
par.upper <- confint_trans[pname, "upper"] |
111 | 1410x |
names(par.lower) <- names(par.upper) <- pname |
112 | 1410x |
bpl <- backtransform_odeparms(par.lower, object$mkinmod, |
113 | 1410x |
object$transform_rates, |
114 | 1410x |
object$transform_fractions) |
115 | 1410x |
bpu <- backtransform_odeparms(par.upper, object$mkinmod, |
116 | 1410x |
object$transform_rates, |
117 | 1410x |
object$transform_fractions) |
118 | 1410x |
confint_back[names(bpl), "lower"] <- bpl |
119 | 1410x |
confint_back[names(bpu), "upper"] <- bpu |
120 |
} |
|
121 |
} |
|
122 | ||
123 | 319x |
object$confint_trans <- confint_trans |
124 | 319x |
object$confint_back <- confint_back |
125 | ||
126 | 319x |
object$date.summary = date() |
127 | 319x |
object$use_of_ff = object$mkinmod$use_of_ff |
128 | 319x |
object$error_model_algorithm = object$mmkin[[1]]$error_model_algorithm |
129 | 319x |
err_mod = object$mmkin[[1]]$err_mod |
130 | ||
131 | 319x |
object$diffs <- object$mkinmod$diffs |
132 | 319x |
object$print_data <- data |
133 | 319x |
object$data[["observed"]] <- object$data[["value"]] |
134 | 319x |
object$data[["value"]] <- NULL |
135 | 319x |
object$data[["predicted"]] <- predict(object) |
136 | 319x |
object$data[["residual"]] <- residuals(object, type = "response") |
137 | 319x |
if (is.null(object$modelStruct$varStruct)) { |
138 | ! |
object$data[["std"]] <- object$sigma |
139 |
} else { |
|
140 | 319x |
object$data[["std"]] <- 1/attr(object$modelStruct$varStruct, "weights") |
141 |
} |
|
142 | 319x |
object$data[["standardized"]] <- residuals(object, type = "pearson") |
143 | 319x |
object$verbose <- verbose |
144 | ||
145 | 319x |
object$fixed <- object$mmkin[[1]]$fixed |
146 | 319x |
object$AIC = AIC(object) |
147 | 319x |
object$BIC = BIC(object) |
148 | 319x |
object$logLik = logLik(object) |
149 | ||
150 | 319x |
ep <- endpoints(object) |
151 | 319x |
if (length(ep$ff) != 0) |
152 | 117x |
object$ff <- ep$ff |
153 | 319x |
if (distimes) object$distimes <- ep$distimes |
154 | ! |
if (length(ep$SFORB) != 0) object$SFORB <- ep$SFORB |
155 | 319x |
class(object) <- c("summary.nlme.mmkin", "nlme.mmkin", "nlme", "lme") |
156 | 319x |
return(object) |
157 |
} |
|
158 | ||
159 |
#' @rdname summary.nlme.mmkin |
|
160 |
#' @export |
|
161 |
print.summary.nlme.mmkin <- function(x, digits = max(3, getOption("digits") - 3), verbose = x$verbose, ...) { |
|
162 | 117x |
cat("nlme version used for fitting: ", x$nlmeversion, "\n") |
163 | 117x |
cat("mkin version used for pre-fitting: ", x$mkinversion, "\n") |
164 | 117x |
cat("R version used for fitting: ", x$Rversion, "\n") |
165 | ||
166 | 117x |
cat("Date of fit: ", x$date.fit, "\n") |
167 | 117x |
cat("Date of summary:", x$date.summary, "\n") |
168 | ||
169 | 117x |
cat("\nEquations:\n") |
170 | 117x |
nice_diffs <- gsub("^(d.*) =", "\\1/dt =", x[["diffs"]]) |
171 | 117x |
writeLines(strwrap(nice_diffs, exdent = 11)) |
172 | ||
173 | 117x |
cat("\nData:\n") |
174 | 117x |
cat(nrow(x$data), "observations of", |
175 | 117x |
length(unique(x$data$name)), "variable(s) grouped in", |
176 | 117x |
length(unique(x$data$ds)), "datasets\n") |
177 | ||
178 | 117x |
cat("\nModel predictions using solution type", x$solution_type, "\n") |
179 | ||
180 | 117x |
cat("\nFitted in", x$time[["elapsed"]], "s using", x$numIter, "iterations\n") |
181 | ||
182 | 117x |
cat("\nVariance model: ") |
183 | 117x |
cat(switch(x$err_mod, |
184 | 117x |
const = "Constant variance", |
185 | 117x |
obs = "Variance unique to each observed variable", |
186 | 117x |
tc = "Two-component variance function"), "\n") |
187 | ||
188 | 117x |
cat("\nMean of starting values for individual parameters:\n") |
189 | 117x |
print(x$mean_dp_start, digits = digits) |
190 | ||
191 | 117x |
cat("\nFixed degradation parameter values:\n") |
192 | 117x |
if(length(x$fixed$value) == 0) cat("None\n") |
193 | ! |
else print(x$fixed, digits = digits) |
194 | ||
195 | 117x |
cat("\nResults:\n\n") |
196 | 117x |
print(data.frame(AIC = x$AIC, BIC = x$BIC, logLik = x$logLik, |
197 | 117x |
row.names = " "), digits = digits, ...) |
198 | ||
199 | 117x |
cat("\nOptimised, transformed parameters with symmetric confidence intervals:\n") |
200 | 117x |
print(x$confint_trans, digits = digits, ...) |
201 | ||
202 | 117x |
if (nrow(x$confint_trans) > 1) { |
203 | 117x |
corr <- x$corFixed |
204 | 117x |
class(corr) <- "correlation" |
205 | 117x |
print(corr, title = "\nCorrelation:", rdig = digits, ...) |
206 |
} |
|
207 | ||
208 | 117x |
cat("\n") # Random effects |
209 | 117x |
print(summary(x$modelStruct), sigma = x$sigma, |
210 | 117x |
reEstimates = x$coef$random, digits = digits, verbose = verbose, ...) |
211 | ||
212 | 117x |
cat("\nBacktransformed parameters with asymmetric confidence intervals:\n") |
213 | 117x |
print(x$confint_back, digits = digits, ...) |
214 | ||
215 | ||
216 | 117x |
printSFORB <- !is.null(x$SFORB) |
217 | 117x |
if(printSFORB){ |
218 | ! |
cat("\nEstimated Eigenvalues of SFORB model(s):\n") |
219 | ! |
print(x$SFORB, digits = digits,...) |
220 |
} |
|
221 | ||
222 | 117x |
printff <- !is.null(x$ff) |
223 | 117x |
if(printff){ |
224 | ! |
cat("\nResulting formation fractions:\n") |
225 | ! |
print(data.frame(ff = x$ff), digits = digits, ...) |
226 |
} |
|
227 | ||
228 | 117x |
printdistimes <- !is.null(x$distimes) |
229 | 117x |
if(printdistimes){ |
230 | 117x |
cat("\nEstimated disappearance times:\n") |
231 | 117x |
print(x$distimes, digits = digits, ...) |
232 |
} |
|
233 | ||
234 | 117x |
if (x$print_data){ |
235 | ! |
cat("\nData:\n") |
236 | ! |
print(format(x$data, digits = digits, ...), row.names = FALSE) |
237 |
} |
|
238 | ||
239 | 117x |
invisible(x) |
240 |
} |
1 |
utils::globalVariables(c("variable", "residual")) |
|
2 | ||
3 |
#' Function to plot squared residuals and the error model for an mkin object |
|
4 |
#' |
|
5 |
#' This function plots the squared residuals for the specified subset of the |
|
6 |
#' observed variables from an mkinfit object. In addition, one or more dashed |
|
7 |
#' line(s) show the fitted error model. A combined plot of the fitted model |
|
8 |
#' and this error model plot can be obtained with \code{\link{plot.mkinfit}} |
|
9 |
#' using the argument \code{show_errplot = TRUE}. |
|
10 |
#' |
|
11 |
#' @param object A fit represented in an \code{\link{mkinfit}} object. |
|
12 |
#' @param obs_vars A character vector of names of the observed variables for |
|
13 |
#' which residuals should be plotted. Defaults to all observed variables in |
|
14 |
#' the model |
|
15 |
#' @param xlim plot range in x direction. |
|
16 |
#' @param xlab Label for the x axis. |
|
17 |
#' @param ylab Label for the y axis. |
|
18 |
#' @param maxy Maximum value of the residuals. This is used for the scaling of |
|
19 |
#' the y axis and defaults to "auto". |
|
20 |
#' @param legend Should a legend be plotted? |
|
21 |
#' @param lpos Where should the legend be placed? Default is "topright". Will |
|
22 |
#' be passed on to \code{\link{legend}}. |
|
23 |
#' @param col_obs Colors for the observed variables. |
|
24 |
#' @param pch_obs Symbols to be used for the observed variables. |
|
25 |
#' @param frame Should a frame be drawn around the plots? |
|
26 |
#' @param \dots further arguments passed to \code{\link{plot}}. |
|
27 |
#' @return Nothing is returned by this function, as it is called for its side |
|
28 |
#' effect, namely to produce a plot. |
|
29 |
#' @author Johannes Ranke |
|
30 |
#' @seealso \code{\link{mkinplot}}, for a way to plot the data and the fitted |
|
31 |
#' lines of the mkinfit object. |
|
32 |
#' @keywords hplot |
|
33 |
#' @examples |
|
34 |
#' |
|
35 |
#' \dontrun{ |
|
36 |
#' model <- mkinmod(parent = mkinsub("SFO", "m1"), m1 = mkinsub("SFO")) |
|
37 |
#' fit <- mkinfit(model, FOCUS_2006_D, error_model = "tc", quiet = TRUE) |
|
38 |
#' mkinerrplot(fit) |
|
39 |
#' } |
|
40 |
#' |
|
41 |
#' @export |
|
42 |
mkinerrplot <- function (object, |
|
43 |
obs_vars = names(object$mkinmod$map), |
|
44 |
xlim = c(0, 1.1 * max(object$data$predicted)), |
|
45 |
xlab = "Predicted", ylab = "Squared residual", |
|
46 |
maxy = "auto", legend= TRUE, lpos = "topright", |
|
47 |
col_obs = "auto", pch_obs = "auto", |
|
48 |
frame = TRUE, |
|
49 |
...) |
|
50 |
{ |
|
51 | 275x |
obs_vars_all <- as.character(unique(object$data$variable)) |
52 | ||
53 | 275x |
if (length(obs_vars) > 0){ |
54 | 275x |
obs_vars <- intersect(obs_vars_all, obs_vars) |
55 | ! |
} else obs_vars <- obs_vars_all |
56 | ||
57 | 275x |
residuals <- subset(object$data, variable %in% obs_vars, residual) |
58 | ||
59 | 275x |
if (maxy == "auto") maxy = max(residuals^2, na.rm = TRUE) |
60 | ||
61 |
# Set colors and symbols |
|
62 | 275x |
if (col_obs[1] == "auto") { |
63 | 70x |
col_obs <- 1:length(obs_vars) |
64 |
} |
|
65 | ||
66 | 275x |
if (pch_obs[1] == "auto") { |
67 | 70x |
pch_obs <- 1:length(obs_vars) |
68 |
} |
|
69 | 275x |
names(col_obs) <- names(pch_obs) <- obs_vars |
70 | ||
71 | 275x |
plot(0, type = "n", |
72 | 275x |
xlab = xlab, ylab = ylab, |
73 | 275x |
xlim = xlim, |
74 | 275x |
ylim = c(0, 1.2 * maxy), frame = frame, ...) |
75 | ||
76 | 275x |
for(obs_var in obs_vars){ |
77 | 410x |
residuals_plot <- subset(object$data, variable == obs_var, c("predicted", "residual")) |
78 | 410x |
points(residuals_plot[["predicted"]], |
79 | 410x |
residuals_plot[["residual"]]^2, |
80 | 410x |
pch = pch_obs[obs_var], col = col_obs[obs_var]) |
81 |
} |
|
82 | ||
83 | 275x |
if (object$err_mod == "const") { |
84 | 140x |
abline(h = object$errparms^2, lty = 2, col = 1) |
85 |
} |
|
86 | 275x |
if (object$err_mod == "obs") { |
87 | 65x |
for (obs_var in obs_vars) { |
88 | 130x |
sigma_name = paste0("sigma_", obs_var) |
89 | 130x |
abline(h = object$errparms[sigma_name]^2, lty = 2, |
90 | 130x |
col = col_obs[obs_var]) |
91 |
} |
|
92 |
} |
|
93 | 275x |
if (object$err_mod == "tc") { |
94 | 70x |
sigma_plot <- function(predicted) { |
95 | 70x |
sigma_twocomp(predicted, |
96 | 70x |
sigma_low = object$errparms[1], |
97 | 70x |
rsd_high = object$errparms[2])^2 |
98 |
} |
|
99 | 70x |
plot(sigma_plot, from = 0, to = max(object$data$predicted), |
100 | 70x |
add = TRUE, lty = 2, col = 1) |
101 |
} |
|
102 | ||
103 | 275x |
if (legend == TRUE) { |
104 | 70x |
legend(lpos, inset = c(0.05, 0.05), legend = obs_vars, |
105 | 70x |
col = col_obs[obs_vars], pch = pch_obs[obs_vars]) |
106 |
} |
|
107 |
} |
1 |
#' Plot parameter variability of multistart objects |
|
2 |
#' |
|
3 |
#' Produces a boxplot with all parameters from the multiple runs, scaled |
|
4 |
#' either by the parameters of the run with the highest likelihood, |
|
5 |
#' or by their medians as proposed in the paper by Duchesne et al. (2021). |
|
6 |
#' |
|
7 |
#' Starting values of degradation model parameters and error model parameters |
|
8 |
#' are shown as green circles. The results obtained in the original run |
|
9 |
#' are shown as red circles. |
|
10 |
#' |
|
11 |
#' @param object The [multistart] object |
|
12 |
#' @param llmin The minimum likelihood of objects to be shown |
|
13 |
#' @param llquant Fractional value for selecting only the fits with higher |
|
14 |
#' likelihoods. Overrides 'llmin'. |
|
15 |
#' @param scale By default, scale parameters using the best |
|
16 |
#' available fit. |
|
17 |
#' If 'median', parameters are scaled using the median parameters from all fits. |
|
18 |
#' @param main Title of the plot |
|
19 |
#' @param lpos Positioning of the legend. |
|
20 |
#' @param \dots Passed to [boxplot] |
|
21 |
#' @references Duchesne R, Guillemin A, Gandrillon O, Crauste F. Practical |
|
22 |
#' identifiability in the frame of nonlinear mixed effects models: the example |
|
23 |
#' of the in vitro erythropoiesis. BMC Bioinformatics. 2021 Oct 4;22(1):478. |
|
24 |
#' doi: 10.1186/s12859-021-04373-4. |
|
25 |
#' @seealso [multistart] |
|
26 |
#' @importFrom stats median quantile |
|
27 |
#' @export |
|
28 |
parplot <- function(object, ...) { |
|
29 | 176x |
UseMethod("parplot") |
30 |
} |
|
31 | ||
32 |
#' @rdname parplot |
|
33 |
#' @export |
|
34 |
parplot.multistart.saem.mmkin <- function(object, llmin = -Inf, llquant = NA, |
|
35 |
scale = c("best", "median"), |
|
36 |
lpos = "bottomleft", main = "", ...) |
|
37 |
{ |
|
38 | 176x |
oldpar <- par(no.readonly = TRUE) |
39 | 176x |
on.exit(par(oldpar, no.readonly = TRUE)) |
40 | ||
41 | 176x |
orig <- attr(object, "orig") |
42 | 176x |
orig_parms <- parms(orig) |
43 | 176x |
start_degparms <- orig$mean_dp_start |
44 | 176x |
all_parms <- parms(object, exclude_failed = FALSE) |
45 | ||
46 | 176x |
if (inherits(object, "multistart.saem.mmkin")) { |
47 | 176x |
llfunc <- function(object) { |
48 | ! |
if (inherits(object$so, "try-error")) return(NA) |
49 | 1408x |
else return(logLik(object$so)) |
50 |
} |
|
51 |
} else { |
|
52 | ! |
stop("parplot is only implemented for multistart.saem.mmkin objects") |
53 |
} |
|
54 | 176x |
ll <- sapply(object, llfunc) |
55 | 176x |
if (!is.na(llquant[1])) { |
56 | ! |
if (llmin != -Inf) warning("Overriding 'llmin' because 'llquant' was specified") |
57 | 88x |
llmin <- quantile(ll, 1 - llquant) |
58 |
} |
|
59 | 176x |
selected <- which(ll > llmin) |
60 | 176x |
selected_parms <- all_parms[selected, ] |
61 | ||
62 | 176x |
par(las = 1) |
63 | 176x |
if (orig$transformations == "mkin") { |
64 | 88x |
degparm_names_transformed <- names(start_degparms) |
65 | 88x |
degparm_index <- which(names(orig_parms) %in% degparm_names_transformed) |
66 | 88x |
orig_parms[degparm_names_transformed] <- backtransform_odeparms( |
67 | 88x |
orig_parms[degparm_names_transformed], |
68 | 88x |
orig$mmkin[[1]]$mkinmod, |
69 | 88x |
transform_rates = orig$mmkin[[1]]$transform_rates, |
70 | 88x |
transform_fractions = orig$mmkin[[1]]$transform_fractions) |
71 | 88x |
start_degparms <- backtransform_odeparms(start_degparms, |
72 | 88x |
orig$mmkin[[1]]$mkinmod, |
73 | 88x |
transform_rates = orig$mmkin[[1]]$transform_rates, |
74 | 88x |
transform_fractions = orig$mmkin[[1]]$transform_fractions) |
75 | 88x |
degparm_names <- names(start_degparms) |
76 | ||
77 | 88x |
names(orig_parms) <- c(degparm_names, names(orig_parms[-degparm_index])) |
78 | ||
79 | 88x |
selected_parms[, degparm_names_transformed] <- |
80 | 88x |
t(apply(selected_parms[, degparm_names_transformed], 1, backtransform_odeparms, |
81 | 88x |
orig$mmkin[[1]]$mkinmod, |
82 | 88x |
transform_rates = orig$mmkin[[1]]$transform_rates, |
83 | 88x |
transform_fractions = orig$mmkin[[1]]$transform_fractions)) |
84 | 88x |
colnames(selected_parms)[1:length(degparm_names)] <- degparm_names |
85 |
} |
|
86 | ||
87 | 176x |
start_errparms <- orig$so@model@error.init |
88 | 176x |
names(start_errparms) <- orig$so@model@name.sigma |
89 | ||
90 | 176x |
start_omegaparms <- orig$so@model@omega.init |
91 | ||
92 | 176x |
start_parms <- c(start_degparms, start_errparms) |
93 | ||
94 | 176x |
scale <- match.arg(scale) |
95 | 176x |
parm_scale <- switch(scale, |
96 | 176x |
best = selected_parms[which.best(object[selected]), ], |
97 | 176x |
median = apply(selected_parms, 2, median) |
98 |
) |
|
99 | ||
100 |
# Boxplots of all scaled parameters |
|
101 | 176x |
selected_scaled_parms <- t(apply(selected_parms, 1, function(x) x / parm_scale)) |
102 | 176x |
boxplot(selected_scaled_parms, log = "y", main = main, , |
103 | 176x |
ylab = "Normalised parameters", ...) |
104 | ||
105 |
# Show starting parameters |
|
106 | 176x |
start_scaled_parms <- rep(NA_real_, length(orig_parms)) |
107 | 176x |
names(start_scaled_parms) <- names(orig_parms) |
108 | 176x |
start_scaled_parms[names(start_parms)] <- |
109 | 176x |
start_parms / parm_scale[names(start_parms)] |
110 | 176x |
points(start_scaled_parms, col = 3, cex = 3) |
111 | ||
112 |
# Show parameters of original run |
|
113 | 176x |
orig_scaled_parms <- orig_parms / parm_scale |
114 | 176x |
points(orig_scaled_parms, col = 2, cex = 2) |
115 | ||
116 | 176x |
abline(h = 1, lty = 2) |
117 | ||
118 | 176x |
legend(lpos, inset = c(0.05, 0.05), bty = "n", |
119 | 176x |
pch = 1, col = 3:1, lty = c(NA, NA, 1), |
120 | 176x |
legend = c( |
121 | 176x |
"Original start", |
122 | 176x |
"Original results", |
123 | 176x |
"Multistart runs")) |
124 |
} |
1 |
utils::globalVariables("ds") |
|
2 | ||
3 |
#' Plot predictions from a fitted nonlinear mixed model obtained via an mmkin row object |
|
4 |
#' |
|
5 |
#' @param x An object of class [mixed.mmkin], [saem.mmkin] or [nlme.mmkin] |
|
6 |
#' @param i A numeric index to select datasets for which to plot the individual predictions, |
|
7 |
#' in case plots get too large |
|
8 |
#' @inheritParams plot.mkinfit |
|
9 |
#' @param standardized Should the residuals be standardized? Only takes effect if |
|
10 |
#' `resplot = "time"`. |
|
11 |
#' @param pop_curves Per default, one population curve is drawn in case |
|
12 |
#' population parameters are fitted by the model, e.g. for saem objects. |
|
13 |
#' In case there is a covariate model, the behaviour depends on the value |
|
14 |
#' of 'covariates' |
|
15 |
#' @param covariates Data frame with covariate values for all variables in |
|
16 |
#' any covariate models in the object. If given, it overrides 'covariate_quantiles'. |
|
17 |
#' Each line in the data frame will result in a line drawn for the population. |
|
18 |
#' Rownames are used in the legend to label the lines. |
|
19 |
#' @param covariate_quantiles This argument only has an effect if the fitted |
|
20 |
#' object has covariate models. If so, the default is to show three population |
|
21 |
#' curves, for the 5th percentile, the 50th percentile and the 95th percentile |
|
22 |
#' of the covariate values used for fitting the model. |
|
23 |
#' @note Covariate models are currently only supported for saem.mmkin objects. |
|
24 |
#' @param pred_over Named list of alternative predictions as obtained |
|
25 |
#' from [mkinpredict] with a compatible [mkinmod]. |
|
26 |
#' @param test_log_parms Passed to [mean_degparms] in the case of an |
|
27 |
#' [mixed.mmkin] object |
|
28 |
#' @param conf.level Passed to [mean_degparms] in the case of an |
|
29 |
#' [mixed.mmkin] object |
|
30 |
#' @param default_log_parms Passed to [mean_degparms] in the case of an |
|
31 |
#' [mixed.mmkin] object |
|
32 |
#' @param rel.height.legend The relative height of the legend shown on top |
|
33 |
#' @param rel.height.bottom The relative height of the bottom plot row |
|
34 |
#' @param ymax Vector of maximum y axis values |
|
35 |
#' @param ncol.legend Number of columns to use in the legend |
|
36 |
#' @param nrow.legend Number of rows to use in the legend |
|
37 |
#' @param resplot Should the residuals plotted against time or against |
|
38 |
#' predicted values? |
|
39 |
#' @param col_ds Colors used for plotting the observed data and the |
|
40 |
#' corresponding model prediction lines for the different datasets. |
|
41 |
#' @param pch_ds Symbols to be used for plotting the data. |
|
42 |
#' @param lty_ds Line types to be used for the model predictions. |
|
43 |
#' @importFrom stats coefficients |
|
44 |
#' @return The function is called for its side effect. |
|
45 |
#' @author Johannes Ranke |
|
46 |
#' @examples |
|
47 |
#' ds <- lapply(experimental_data_for_UBA_2019[6:10], |
|
48 |
#' function(x) x$data[c("name", "time", "value")]) |
|
49 |
#' names(ds) <- paste0("ds ", 6:10) |
|
50 |
#' dfop_sfo <- mkinmod(parent = mkinsub("DFOP", "A1"), |
|
51 |
#' A1 = mkinsub("SFO"), quiet = TRUE) |
|
52 |
#' \dontrun{ |
|
53 |
#' f <- mmkin(list("DFOP-SFO" = dfop_sfo), ds, quiet = TRUE) |
|
54 |
#' plot(f[, 3:4], standardized = TRUE) |
|
55 |
#' |
|
56 |
#' # For this fit we need to increase pnlsMaxiter, and we increase the |
|
57 |
#' # tolerance in order to speed up the fit for this example evaluation |
|
58 |
#' # It still takes 20 seconds to run |
|
59 |
#' f_nlme <- nlme(f, control = list(pnlsMaxIter = 120, tolerance = 1e-3)) |
|
60 |
#' plot(f_nlme) |
|
61 |
#' |
|
62 |
#' f_saem <- saem(f, transformations = "saemix") |
|
63 |
#' plot(f_saem) |
|
64 |
#' |
|
65 |
#' f_obs <- mmkin(list("DFOP-SFO" = dfop_sfo), ds, quiet = TRUE, error_model = "obs") |
|
66 |
#' f_nlmix <- nlmix(f_obs) |
|
67 |
#' plot(f_nlmix) |
|
68 |
#' |
|
69 |
#' # We can overlay the two variants if we generate predictions |
|
70 |
#' pred_nlme <- mkinpredict(dfop_sfo, |
|
71 |
#' f_nlme$bparms.optim[-1], |
|
72 |
#' c(parent = f_nlme$bparms.optim[[1]], A1 = 0), |
|
73 |
#' seq(0, 180, by = 0.2)) |
|
74 |
#' plot(f_saem, pred_over = list(nlme = pred_nlme)) |
|
75 |
#' } |
|
76 |
#' @export |
|
77 |
plot.mixed.mmkin <- function(x, |
|
78 |
i = 1:ncol(x$mmkin), |
|
79 |
obs_vars = names(x$mkinmod$map), |
|
80 |
standardized = TRUE, |
|
81 |
covariates = NULL, |
|
82 |
covariate_quantiles = c(0.5, 0.05, 0.95), |
|
83 |
xlab = "Time", |
|
84 |
xlim = range(x$data$time), |
|
85 |
resplot = c("predicted", "time"), |
|
86 |
pop_curves = "auto", |
|
87 |
pred_over = NULL, |
|
88 |
test_log_parms = FALSE, |
|
89 |
conf.level = 0.6, |
|
90 |
default_log_parms = NA, |
|
91 |
ymax = "auto", maxabs = "auto", |
|
92 |
ncol.legend = ifelse(length(i) <= 3, length(i) + 1, ifelse(length(i) <= 8, 3, 4)), |
|
93 |
nrow.legend = ceiling((length(i) + 1) / ncol.legend), |
|
94 |
rel.height.legend = 0.02 + 0.07 * nrow.legend, |
|
95 |
rel.height.bottom = 1.1, |
|
96 |
pch_ds = 1:length(i), |
|
97 |
col_ds = pch_ds + 1, |
|
98 |
lty_ds = col_ds, |
|
99 |
frame = TRUE, ... |
|
100 |
) |
|
101 |
{ |
|
102 |
# Prepare parameters and data |
|
103 | 283x |
fit_1 <- x$mmkin[[1]] |
104 | 283x |
ds_names <- colnames(x$mmkin) |
105 | ||
106 | 283x |
backtransform = TRUE |
107 | ||
108 | 283x |
if (identical(class(x), "mixed.mmkin")) { |
109 | 65x |
if (identical(pop_curves, "auto")) { |
110 | ! |
pop_curves <- FALSE |
111 |
} else { |
|
112 | 65x |
pop_curves <- TRUE |
113 |
} |
|
114 | 65x |
if (pop_curves) { |
115 | 65x |
degparms_pop <- mean_degparms(x$mmkin, test_log_parms = test_log_parms, |
116 | 65x |
conf.level = conf.level, default_log_parms = default_log_parms) |
117 |
} |
|
118 | ||
119 | 65x |
degparms_tmp <- parms(x$mmkin, transformed = TRUE) |
120 | 65x |
degparms_i <- as.data.frame(t(degparms_tmp[setdiff(rownames(degparms_tmp), names(fit_1$errparms)), ])) |
121 | 65x |
residual_type = ifelse(standardized, "standardized", "residual") |
122 | 65x |
residuals <- x$data[[residual_type]] |
123 |
} |
|
124 | ||
125 | 283x |
if (inherits(x, "nlme.mmkin")) { |
126 | 65x |
if (identical(pop_curves, "auto")) { |
127 | 65x |
pop_curves <- TRUE |
128 |
} else { |
|
129 | ! |
pop_curves <- FALSE |
130 |
} |
|
131 | 65x |
degparms_i <- coefficients(x) |
132 | 65x |
degparms_pop <- nlme::fixef(x) |
133 | 65x |
residuals <- residuals(x, |
134 | 65x |
type = ifelse(standardized, "pearson", "response")) |
135 |
} |
|
136 | ||
137 | 283x |
if (inherits(x, "saem.mmkin")) { |
138 | 65x |
if (x$transformations == "saemix") backtransform = FALSE |
139 | 153x |
psi <- saemix::psi(x$so) |
140 | 153x |
rownames(psi) <- x$saemix_ds_order |
141 | 153x |
degparms_i <- psi[ds_names, ] |
142 | 153x |
degparms_i_names <- colnames(degparms_i) |
143 | 153x |
residual_type = ifelse(standardized, "standardized", "residual") |
144 | 153x |
residuals <- x$data[[residual_type]] |
145 | ||
146 | 153x |
if (identical(pop_curves, "auto")) { |
147 | 153x |
if (length(x$covariate_models) == 0) { |
148 | 153x |
degparms_pop <- x$so@results@fixed.effects |
149 | 153x |
names(degparms_pop) <- degparms_i_names |
150 | 153x |
pop_curves <- TRUE |
151 |
} else { |
|
152 | ! |
if (is.null(covariates)) { |
153 | ! |
covariates = as.data.frame( |
154 | ! |
apply(x$covariates, 2, quantile, |
155 | ! |
covariate_quantiles, simplify = FALSE)) |
156 | ! |
rownames(covariates) <- paste( |
157 | ! |
ifelse(length(x$covariate_models) == 1, |
158 | ! |
"Covariate", "Covariates"), |
159 | ! |
rownames(covariates)) |
160 |
} |
|
161 | ! |
degparms_pop <- parms(x, covariates = covariates) |
162 | ! |
pop_curves <- TRUE |
163 |
} |
|
164 |
} else { |
|
165 | ! |
pop_curves <- FALSE |
166 |
} |
|
167 |
} |
|
168 | ||
169 | 283x |
if (pop_curves) { |
170 |
# Make sure degparms_pop is a matrix, columns corresponding to population curve(s) |
|
171 | 283x |
if (is.null(dim(degparms_pop))) { |
172 | 283x |
degparms_pop <- matrix(degparms_pop, ncol = 1, |
173 | 283x |
dimnames = list(names(degparms_pop), "Population")) |
174 |
} |
|
175 |
} |
|
176 | ||
177 | 283x |
degparms_fixed <- fit_1$fixed$value |
178 | 283x |
names(degparms_fixed) <- rownames(fit_1$fixed) |
179 | 283x |
degparms_all <- cbind(as.matrix(degparms_i), |
180 | 283x |
matrix(rep(degparms_fixed, nrow(degparms_i)), |
181 | 283x |
ncol = length(degparms_fixed), |
182 | 283x |
nrow = nrow(degparms_i), byrow = TRUE)) |
183 | 283x |
degparms_all_names <- c(names(degparms_i), names(degparms_fixed)) |
184 | 283x |
colnames(degparms_all) <- degparms_all_names |
185 | ||
186 | 283x |
odeini_names <- grep("_0$", degparms_all_names, value = TRUE) |
187 | 283x |
odeparms_names <- setdiff(degparms_all_names, odeini_names) |
188 | ||
189 | 283x |
observed <- cbind(x$data[c("ds", "name", "time", "value")], |
190 | 283x |
residual = residuals) |
191 | ||
192 | 283x |
solution_type = fit_1$solution_type |
193 | ||
194 | 283x |
outtimes <- sort(unique(c(x$data$time, |
195 | 283x |
seq(xlim[1], xlim[2], length.out = 50)))) |
196 | ||
197 | 283x |
pred_list <- lapply(i, function(ds_i) { |
198 | 2945x |
odeparms_trans <- degparms_all[ds_i, odeparms_names] |
199 | 2945x |
names(odeparms_trans) <- odeparms_names # needed if only one odeparm |
200 | 2945x |
if (backtransform) { |
201 | 2620x |
odeparms <- backtransform_odeparms(odeparms_trans, |
202 | 2620x |
x$mkinmod, |
203 | 2620x |
transform_rates = fit_1$transform_rates, |
204 | 2620x |
transform_fractions = fit_1$transform_fractions) |
205 |
} else { |
|
206 | 325x |
odeparms <- odeparms_trans |
207 |
} |
|
208 | ||
209 | 2945x |
odeini <- degparms_all[ds_i, odeini_names] |
210 | 2945x |
names(odeini) <- gsub("_0", "", odeini_names) |
211 | ||
212 | 2945x |
out <- mkinpredict(x$mkinmod, odeparms, odeini, |
213 | 2945x |
outtimes, solution_type = solution_type, |
214 | 2945x |
atol = fit_1$atol, rtol = fit_1$rtol) |
215 |
}) |
|
216 | 283x |
names(pred_list) <- ds_names[i] |
217 | 283x |
pred_ds <- vctrs::vec_rbind(!!!pred_list, .names_to = "ds") |
218 | ||
219 | 283x |
if (pop_curves) { |
220 | 283x |
pred_list_pop <- lapply(1:ncol(degparms_pop), function(cov_i) { |
221 | 283x |
degparms_all_pop_i <- c(degparms_pop[, cov_i], degparms_fixed) |
222 | 283x |
odeparms_pop_trans_i <- degparms_all_pop_i[odeparms_names] |
223 | 283x |
names(odeparms_pop_trans_i) <- odeparms_names # needed if only one odeparm |
224 | 283x |
if (backtransform) { |
225 | 218x |
odeparms_pop_i <- backtransform_odeparms(odeparms_pop_trans_i, |
226 | 218x |
x$mkinmod, |
227 | 218x |
transform_rates = fit_1$transform_rates, |
228 | 218x |
transform_fractions = fit_1$transform_fractions) |
229 |
} else { |
|
230 | 65x |
odeparms_pop_i <- odeparms_pop_trans_i |
231 |
} |
|
232 | ||
233 | 283x |
odeini <- degparms_all_pop_i[odeini_names] |
234 | 283x |
names(odeini) <- gsub("_0", "", odeini_names) |
235 | ||
236 | 283x |
out <- mkinpredict(x$mkinmod, odeparms_pop_i, odeini, |
237 | 283x |
outtimes, solution_type = solution_type, |
238 | 283x |
atol = fit_1$atol, rtol = fit_1$rtol) |
239 |
}) |
|
240 | 283x |
names(pred_list_pop) <- colnames(degparms_pop) |
241 | ||
242 |
} else { |
|
243 | ! |
pred_list_pop <- NULL |
244 |
} |
|
245 | ||
246 |
# Start of graphical section |
|
247 | 283x |
oldpar <- par(no.readonly = TRUE) |
248 | 283x |
on.exit(par(oldpar, no.readonly = TRUE)) |
249 | ||
250 | 283x |
n_plot_rows = length(obs_vars) |
251 | 283x |
n_plots = n_plot_rows * 2 |
252 | ||
253 |
# Set relative plot heights, so the first plot row is the norm |
|
254 | 283x |
rel.heights <- if (n_plot_rows > 1) { |
255 | 218x |
c(rel.height.legend, c(rep(1, n_plot_rows - 1), rel.height.bottom)) |
256 |
} else { |
|
257 | 65x |
c(rel.height.legend, 1) |
258 |
} |
|
259 | ||
260 | 283x |
layout_matrix = matrix(c(1, 1, 2:(n_plots + 1)), |
261 | 283x |
n_plot_rows + 1, 2, byrow = TRUE) |
262 | 283x |
layout(layout_matrix, heights = rel.heights) |
263 | ||
264 | 283x |
par(mar = c(0.1, 2.1, 0.1, 2.1)) |
265 | ||
266 |
# Empty plot with legend |
|
267 | ! |
if (!is.null(pred_over)) lty_over <- seq(2, length.out = length(pred_over)) |
268 | 283x |
else lty_over <- NULL |
269 | 283x |
if (pop_curves) { |
270 | 283x |
if (is.null(covariates)) { |
271 | 283x |
lty_pop <- 1 |
272 | 283x |
names(lty_pop) <- "Population" |
273 |
} else { |
|
274 | ! |
lty_pop <- 1:nrow(covariates) |
275 | ! |
names(lty_pop) <- rownames(covariates) |
276 |
} |
|
277 |
} else { |
|
278 | ! |
lty_pop <- NULL |
279 |
} |
|
280 | 283x |
n_pop_over <- length(lty_pop) + length(lty_over) |
281 | ||
282 | 283x |
plot(0, type = "n", axes = FALSE, ann = FALSE) |
283 | 283x |
legend("center", bty = "n", ncol = ncol.legend, |
284 | 283x |
legend = c(names(lty_pop), names(pred_over), ds_names[i]), |
285 | 283x |
lty = c(lty_pop, lty_over, lty_ds), |
286 | 283x |
lwd = c(rep(2, n_pop_over), rep(1, length(i))), |
287 | 283x |
col = c(rep(1, n_pop_over), col_ds), |
288 | 283x |
pch = c(rep(NA, n_pop_over), pch_ds)) |
289 | ||
290 | 283x |
resplot <- match.arg(resplot) |
291 | ||
292 |
# Loop plot rows |
|
293 | 283x |
for (plot_row in 1:n_plot_rows) { |
294 | ||
295 | 501x |
obs_var <- obs_vars[plot_row] |
296 | 501x |
observed_row <- subset(observed, name == obs_var) |
297 | ||
298 |
# Set ylim to sensible default, or use ymax |
|
299 | 501x |
if (identical(ymax, "auto")) { |
300 | 501x |
ylim_row = c(0, |
301 | 501x |
max(c(observed_row$value, pred_ds[[obs_var]]), na.rm = TRUE)) |
302 |
} else { |
|
303 | ! |
ylim_row = c(0, ymax[plot_row]) |
304 |
} |
|
305 | ||
306 |
# Margins for bottom row of plots when we have more than one row |
|
307 |
# This is the only row that needs to show the x axis legend |
|
308 | 501x |
if (plot_row == n_plot_rows) { |
309 | 283x |
par(mar = c(5.1, 4.1, 1.1, 2.1)) |
310 |
} else { |
|
311 | 218x |
par(mar = c(3.0, 4.1, 1.1, 2.1)) |
312 |
} |
|
313 | ||
314 | 501x |
plot(0, type = "n", |
315 | 501x |
xlim = xlim, ylim = ylim_row, |
316 | 501x |
xlab = xlab, ylab = paste("Residues", obs_var), frame = frame) |
317 | ||
318 | 501x |
if (!is.null(pred_over)) { |
319 | ! |
for (i_over in seq_along(pred_over)) { |
320 | ! |
pred_frame <- as.data.frame(pred_over[[i_over]]) |
321 | ! |
lines(pred_frame$time, pred_frame[[obs_var]], |
322 | ! |
lwd = 2, lty = lty_over[i_over]) |
323 |
} |
|
324 |
} |
|
325 | ||
326 | 501x |
for (ds_i in seq_along(i)) { |
327 | 4915x |
points(subset(observed_row, ds == ds_names[ds_i], c("time", "value")), |
328 | 4915x |
col = col_ds[ds_i], pch = pch_ds[ds_i]) |
329 | 4915x |
lines(subset(pred_ds, ds == ds_names[ds_i], c("time", obs_var)), |
330 | 4915x |
col = col_ds[ds_i], lty = lty_ds[ds_i]) |
331 |
} |
|
332 | ||
333 | 501x |
if (pop_curves) { |
334 | 501x |
for (cov_i in seq_along(pred_list_pop)) { |
335 | 501x |
cov_name <- names(pred_list_pop)[cov_i] |
336 | 501x |
lines( |
337 | 501x |
pred_list_pop[[cov_i]][, "time"], |
338 | 501x |
pred_list_pop[[cov_i]][, obs_var], |
339 | 501x |
type = "l", lwd = 2, lty = lty_pop[cov_i]) |
340 |
} |
|
341 |
} |
|
342 | ||
343 | 501x |
if (identical(maxabs, "auto")) { |
344 | 283x |
maxabs = max(abs(observed_row$residual), na.rm = TRUE) |
345 |
} |
|
346 | ||
347 | 501x |
if (identical(resplot, "time")) { |
348 | ! |
plot(0, type = "n", xlim = xlim, xlab = "Time", |
349 | ! |
ylim = c(-1.2 * maxabs, 1.2 * maxabs), |
350 | ! |
ylab = if (standardized) "Standardized residual" else "Residual", |
351 | ! |
frame = frame) |
352 | ||
353 | ! |
abline(h = 0, lty = 2) |
354 | ||
355 | ! |
for (ds_i in seq_along(i)) { |
356 | ! |
points(subset(observed_row, ds == ds_names[ds_i], c("time", "residual")), |
357 | ! |
col = col_ds[ds_i], pch = pch_ds[ds_i]) |
358 |
} |
|
359 |
} |
|
360 | ||
361 | 501x |
if (identical(resplot, "predicted")) { |
362 | 501x |
plot(0, type = "n", |
363 | 501x |
xlim = c(0, max(pred_ds[[obs_var]])), |
364 | 501x |
xlab = "Predicted", |
365 | 501x |
ylim = c(-1.2 * maxabs, 1.2 * maxabs), |
366 | 501x |
ylab = if (standardized) "Standardized residual" else "Residual", |
367 | 501x |
frame = frame) |
368 | ||
369 | 501x |
abline(h = 0, lty = 2) |
370 | ||
371 | 501x |
for (ds_i in seq_along(i)) { |
372 | 4915x |
observed_row_ds <- merge( |
373 | 4915x |
subset(observed_row, ds == ds_names[ds_i], c("time", "residual")), |
374 | 4915x |
subset(pred_ds, ds == ds_names[ds_i], c("time", obs_var))) |
375 | 4915x |
points(observed_row_ds[c(3, 2)], |
376 | 4915x |
col = col_ds[ds_i], pch = pch_ds[ds_i]) |
377 |
} |
|
378 |
} |
|
379 |
} |
|
380 |
} |
1 |
#' Summary method for class "mkinfit" |
|
2 |
#' |
|
3 |
#' Lists model equations, initial parameter values, optimised parameters with |
|
4 |
#' some uncertainty statistics, the chi2 error levels calculated according to |
|
5 |
#' FOCUS guidance (2006) as defined therein, formation fractions, DT50 values |
|
6 |
#' and optionally the data, consisting of observed, predicted and residual |
|
7 |
#' values. |
|
8 |
#' |
|
9 |
#' @param object an object of class [mkinfit]. |
|
10 |
#' @param x an object of class \code{summary.mkinfit}. |
|
11 |
#' @param data logical, indicating whether the data should be included in the |
|
12 |
#' summary. |
|
13 |
#' @param distimes logical, indicating whether DT50 and DT90 values should be |
|
14 |
#' included. |
|
15 |
#' @param alpha error level for confidence interval estimation from t |
|
16 |
#' distribution |
|
17 |
#' @param digits Number of digits to use for printing |
|
18 |
#' @param \dots optional arguments passed to methods like \code{print}. |
|
19 |
#' @importFrom stats qt pt cov2cor |
|
20 |
#' @return The summary function returns a list with components, among others |
|
21 |
#' \item{version, Rversion}{The mkin and R versions used} |
|
22 |
#' \item{date.fit, date.summary}{The dates where the fit and the summary were |
|
23 |
#' produced} |
|
24 |
#' \item{diffs}{The differential equations used in the model} |
|
25 |
#' \item{use_of_ff}{Was maximum or minimum use made of formation fractions} |
|
26 |
#' \item{bpar}{Optimised and backtransformed |
|
27 |
#' parameters} |
|
28 |
#' \item{data}{The data (see Description above).} |
|
29 |
#' \item{start}{The starting values and bounds, if applicable, for optimised |
|
30 |
#' parameters.} |
|
31 |
#' \item{fixed}{The values of fixed parameters.} |
|
32 |
#' \item{errmin }{The chi2 error levels for |
|
33 |
#' each observed variable.} |
|
34 |
#' \item{bparms.ode}{All backtransformed ODE |
|
35 |
#' parameters, for use as starting parameters for related models.} |
|
36 |
#' \item{errparms}{Error model parameters.} |
|
37 |
#' \item{ff}{The estimated formation fractions derived from the fitted |
|
38 |
#' model.} |
|
39 |
#' \item{distimes}{The DT50 and DT90 values for each observed variable.} |
|
40 |
#' \item{SFORB}{If applicable, eigenvalues and fractional eigenvector component |
|
41 |
#' g of SFORB systems in the model.} |
|
42 |
#' The print method is called for its side effect, i.e. printing the summary. |
|
43 |
#' @author Johannes Ranke |
|
44 |
#' @references FOCUS (2006) \dQuote{Guidance Document on Estimating Persistence |
|
45 |
#' and Degradation Kinetics from Environmental Fate Studies on Pesticides in |
|
46 |
#' EU Registration} Report of the FOCUS Work Group on Degradation Kinetics, |
|
47 |
#' EC Document Reference Sanco/10058/2005 version 2.0, 434 pp, |
|
48 |
#' \url{http://esdac.jrc.ec.europa.eu/projects/degradation-kinetics} |
|
49 |
#' @examples |
|
50 |
#' |
|
51 |
#' summary(mkinfit("SFO", FOCUS_2006_A, quiet = TRUE)) |
|
52 |
#' |
|
53 |
#' @export |
|
54 |
summary.mkinfit <- function(object, data = TRUE, distimes = TRUE, alpha = 0.05, ...) { |
|
55 | 52158x |
param <- object$par |
56 | 52158x |
pnames <- names(param) |
57 | 52158x |
bpnames <- names(object$bparms.optim) |
58 | 52158x |
epnames <- names(object$errparms) |
59 | 52158x |
p <- length(param) |
60 | 52158x |
mod_vars <- names(object$mkinmod$diffs) |
61 | 52158x |
covar <- try(solve(object$hessian), silent = TRUE) |
62 | 52158x |
covar_notrans <- try(solve(object$hessian_notrans), silent = TRUE) |
63 | 52158x |
rdf <- object$df.residual |
64 | ||
65 | 52158x |
if (!is.numeric(covar) | is.na(covar[1])) { |
66 | ! |
covar <- NULL |
67 | ! |
se <- lci <- uci <- rep(NA, p) |
68 |
} else { |
|
69 | 52158x |
rownames(covar) <- colnames(covar) <- pnames |
70 | 52158x |
se <- sqrt(diag(covar)) |
71 | 52158x |
lci <- param + qt(alpha/2, rdf) * se |
72 | 52158x |
uci <- param + qt(1-alpha/2, rdf) * se |
73 |
} |
|
74 | ||
75 | 52158x |
beparms.optim <- c(object$bparms.optim, object$par[epnames]) |
76 | 52158x |
if (!is.numeric(covar_notrans) | is.na(covar_notrans[1])) { |
77 | 88x |
covar_notrans <- NULL |
78 | 88x |
se_notrans <- tval <- pval <- rep(NA, p) |
79 |
} else { |
|
80 | 52070x |
rownames(covar_notrans) <- colnames(covar_notrans) <- c(bpnames, epnames) |
81 | 52070x |
se_notrans <- sqrt(diag(covar_notrans)) |
82 | 52070x |
tval <- beparms.optim / se_notrans |
83 | 52070x |
pval <- pt(abs(tval), rdf, lower.tail = FALSE) |
84 |
} |
|
85 | ||
86 | 52158x |
names(se) <- pnames |
87 | ||
88 | 52158x |
param <- cbind(param, se, lci, uci) |
89 | 52158x |
dimnames(param) <- list(pnames, c("Estimate", "Std. Error", "Lower", "Upper")) |
90 | ||
91 | 52158x |
bparam <- cbind(Estimate = beparms.optim, se_notrans, |
92 | 52158x |
"t value" = tval, "Pr(>t)" = pval, Lower = NA, Upper = NA) |
93 | ||
94 |
# Transform boundaries of CI for one parameter at a time, |
|
95 |
# with the exception of sets of formation fractions (single fractions are OK). |
|
96 | 52158x |
f_names_skip <- character(0) |
97 | 52158x |
for (box in mod_vars) { # Figure out sets of fractions to skip |
98 | 70671x |
f_names <- grep(paste("^f", box, sep = "_"), pnames, value = TRUE) |
99 | 70671x |
n_paths <- length(f_names) |
100 | 1135x |
if (n_paths > 1) f_names_skip <- c(f_names_skip, f_names) |
101 |
} |
|
102 | ||
103 | 52158x |
for (pname in pnames) { |
104 | 293621x |
if (!pname %in% f_names_skip) { |
105 | 290217x |
par.lower <- param[pname, "Lower"] |
106 | 290217x |
par.upper <- param[pname, "Upper"] |
107 | 290217x |
names(par.lower) <- names(par.upper) <- pname |
108 | 290217x |
bpl <- backtransform_odeparms(par.lower, object$mkinmod, |
109 | 290217x |
object$transform_rates, |
110 | 290217x |
object$transform_fractions) |
111 | 290217x |
bpu <- backtransform_odeparms(par.upper, object$mkinmod, |
112 | 290217x |
object$transform_rates, |
113 | 290217x |
object$transform_fractions) |
114 | 290217x |
bparam[names(bpl), "Lower"] <- bpl |
115 | 290217x |
bparam[names(bpu), "Upper"] <- bpu |
116 |
} |
|
117 |
} |
|
118 | 52158x |
bparam[epnames, c("Lower", "Upper")] <- param[epnames, c("Lower", "Upper")] |
119 | ||
120 | 52158x |
ans <- list( |
121 | 52158x |
version = as.character(utils::packageVersion("mkin")), |
122 | 52158x |
Rversion = paste(R.version$major, R.version$minor, sep="."), |
123 | 52158x |
date.fit = object$date, |
124 | 52158x |
date.summary = date(), |
125 | 52158x |
solution_type = object$solution_type, |
126 | 52158x |
warnings = object$summary_warnings, |
127 | 52158x |
use_of_ff = object$mkinmod$use_of_ff, |
128 | 52158x |
error_model_algorithm = object$error_model_algorithm, |
129 | 52158x |
df = c(p, rdf), |
130 | 52158x |
covar = covar, |
131 | 52158x |
covar_notrans = covar_notrans, |
132 | 52158x |
err_mod = object$err_mod, |
133 | 52158x |
niter = object$iterations, |
134 | 52158x |
calls = object$calls, |
135 | 52158x |
time = object$time, |
136 | 52158x |
par = param, |
137 | 52158x |
bpar = bparam) |
138 | ||
139 | 52158x |
if (!is.null(object$version)) { |
140 | 52158x |
ans$fit_version <- object$version |
141 | 52158x |
ans$fit_Rversion <- object$Rversion |
142 | 52158x |
if (ans$fit_version >= "0.9.49.6") { |
143 | 52156x |
ans$AIC = AIC(object) |
144 | 52156x |
ans$BIC = BIC(object) |
145 | 52156x |
ans$logLik = logLik(object) |
146 |
} |
|
147 |
} |
|
148 | ||
149 | 52158x |
ans$diffs <- object$mkinmod$diffs |
150 | 52158x |
if(data) ans$data <- object$data |
151 | 52158x |
ans$start <- object$start |
152 | 52158x |
ans$start_transformed <- object$start_transformed |
153 | ||
154 | 52158x |
ans$fixed <- object$fixed |
155 | ||
156 | 52158x |
ans$errmin <- mkinerrmin(object, alpha = 0.05) |
157 | ||
158 | 52158x |
if (object$calls > 0) { |
159 | 52158x |
if (!is.null(ans$covar)){ |
160 | 52158x |
Corr <- cov2cor(ans$covar) |
161 | 52158x |
rownames(Corr) <- colnames(Corr) <- rownames(ans$par) |
162 | 52158x |
ans$Corr <- Corr |
163 |
} else { |
|
164 | ! |
warning("Could not calculate correlation; no covariance matrix") |
165 |
} |
|
166 |
} |
|
167 | ||
168 | 52158x |
ans$bparms.ode <- object$bparms.ode |
169 | 52158x |
ans$shapiro.p <- object$shapiro.p |
170 | 52158x |
ep <- endpoints(object) |
171 | 52158x |
if (length(ep$ff) != 0) |
172 | 15612x |
ans$ff <- ep$ff |
173 | 52158x |
if (distimes) ans$distimes <- ep$distimes |
174 | 2442x |
if (length(ep$SFORB) != 0) ans$SFORB <- ep$SFORB |
175 | 43972x |
if (!is.null(object$d_3_message)) ans$d_3_message <- object$d_3_message |
176 | 52158x |
class(ans) <- "summary.mkinfit" |
177 | 52158x |
return(ans) |
178 |
} |
|
179 | ||
180 |
#' @rdname summary.mkinfit |
|
181 |
#' @export |
|
182 |
print.summary.mkinfit <- function(x, digits = max(3, getOption("digits") - 3), ...) { |
|
183 | 4x |
if (is.null(x$fit_version)) { |
184 | ! |
cat("mkin version: ", x$version, "\n") |
185 | ! |
cat("R version: ", x$Rversion, "\n") |
186 |
} else { |
|
187 | 4x |
cat("mkin version used for fitting: ", x$fit_version, "\n") |
188 | 4x |
cat("R version used for fitting: ", x$fit_Rversion, "\n") |
189 |
} |
|
190 | ||
191 | 4x |
cat("Date of fit: ", x$date.fit, "\n") |
192 | 4x |
cat("Date of summary:", x$date.summary, "\n") |