1
utils::globalVariables(c("type", "variable", "observed"))
2

3
#' Plot the observed data and the fitted model of an mkinfit object
4
#'
5
#' Solves the differential equations with the optimised and fixed parameters
6
#' from a previous successful call to \code{\link{mkinfit}} and plots the
7
#' observed data together with the solution of the fitted model.
8
#'
9
#' If the current plot device is a \code{\link[tikzDevice]{tikz}} device, then
10
#' latex is being used for the formatting of the chi2 error level, if
11
#' \code{show_errmin = TRUE}.
12
#'
13
#' @aliases plot.mkinfit plot_sep plot_res plot_err
14
#' @param x Alias for fit introduced for compatibility with the generic S3
15
#'   method.
16
#' @param fit An object of class \code{\link{mkinfit}}.
17
#' @param obs_vars A character vector of names of the observed variables for
18
#'   which the data and the model should be plotted. Defauls to all observed
19
#'   variables in the model.
20
#' @param xlab Label for the x axis.
21
#' @param ylab Label for the y axis.
22
#' @param xlim Plot range in x direction.
23
#' @param ylim Plot range in y direction.
24
#' @param col_obs Colors used for plotting the observed data and the
25
#'   corresponding model prediction lines.
26
#' @param pch_obs Symbols to be used for plotting the data.
27
#' @param lty_obs Line types to be used for the model predictions.
28
#' @param add Should the plot be added to an existing plot?
29
#' @param legend Should a legend be added to the plot?
30
#' @param show_residuals Should residuals be shown? If only one plot of the
31
#'   fits is shown, the residual plot is in the lower third of the plot.
32
#'   Otherwise, i.e. if "sep_obs" is given, the residual plots will be located
33
#'   to the right of the plots of the fitted curves. If this is set to
34
#'   'standardized', a plot of the residuals divided by the standard deviation
35
#'    given by the fitted error model will be shown.
36
#' @param standardized When calling 'plot_res', should the residuals be
37
#'   standardized in the residual plot?
38
#' @param show_errplot Should squared residuals and the error model be shown?
39
#'   If only one plot of the fits is shown, this plot is in the lower third of
40
#'   the plot.  Otherwise, i.e. if "sep_obs" is given, the residual plots will
41
#'   be located to the right of the plots of the fitted curves.
42
#' @param maxabs Maximum absolute value of the residuals. This is used for the
43
#'   scaling of the y axis and defaults to "auto".
44
#' @param sep_obs Should the observed variables be shown in separate subplots?
45
#'   If yes, residual plots requested by "show_residuals" will be shown next
46
#'   to, not below the plot of the fits.
47
#' @param rel.height.middle The relative height of the middle plot, if more
48
#'   than two rows of plots are shown.
49
#' @param row_layout Should we use a row layout where the residual plot or the
50
#'   error model plot is shown to the right?
51
#' @param lpos Position(s) of the legend(s). Passed to \code{\link{legend}} as
52
#'   the first argument.  If not length one, this should be of the same length
53
#'   as the obs_var argument.
54
#' @param inset Passed to \code{\link{legend}} if applicable.
55
#' @param show_errmin Should the FOCUS chi2 error value be shown in the upper
56
#'   margin of the plot?
57
#' @param errmin_digits The number of significant digits for rounding the FOCUS
58
#'   chi2 error percentage.
59
#' @param frame Should a frame be drawn around the plots?
60
#' @param \dots Further arguments passed to \code{\link{plot}}.
61
#' @import graphics
62
#' @importFrom grDevices dev.cur
63
#' @return The function is called for its side effect.
64
#' @author Johannes Ranke
65
#' @examples
66
#'
67
#' # One parent compound, one metabolite, both single first order, path from
68
#' # parent to sink included
69
#' \dontrun{
70
#' SFO_SFO <- mkinmod(parent = mkinsub("SFO", "m1", full = "Parent"),
71
#'                    m1 = mkinsub("SFO", full = "Metabolite M1" ))
72
#' fit <- mkinfit(SFO_SFO, FOCUS_2006_D, quiet = TRUE)
73
#' fit <- mkinfit(SFO_SFO, FOCUS_2006_D, quiet = TRUE, error_model = "tc")
74
#' plot(fit)
75
#' plot_res(fit)
76
#' plot_res(fit, standardized = FALSE)
77
#' plot_err(fit)
78
#'
79
#' # Show the observed variables separately, with residuals
80
#' plot(fit, sep_obs = TRUE, show_residuals = TRUE, lpos = c("topright", "bottomright"),
81
#'      show_errmin = TRUE)
82
#'
83
#' # The same can be obtained with less typing, using the convenience function plot_sep
84
#' plot_sep(fit, lpos = c("topright", "bottomright"))
85
#'
86
#' # Show the observed variables separately, with the error model
87
#' plot(fit, sep_obs = TRUE, show_errplot = TRUE, lpos = c("topright", "bottomright"),
88
#'      show_errmin = TRUE)
89
#' }
90
#'
91
#' @export
92
plot.mkinfit <- function(x, fit = x,
93
  obs_vars = names(fit$mkinmod$map),
94
  xlab = "Time", ylab = "Observed",
95
  xlim = range(fit$data$time),
96
  ylim = "default",
97
  col_obs = 1:length(obs_vars),
98
  pch_obs = col_obs,
99
  lty_obs = rep(1, length(obs_vars)),
100
  add = FALSE, legend = !add,
101
  show_residuals = FALSE,
102
  show_errplot = FALSE,
103
  maxabs = "auto",
104
  sep_obs = FALSE, rel.height.middle = 0.9,
105
  row_layout = FALSE,
106
  lpos = "topright", inset = c(0.05, 0.05),
107
  show_errmin = FALSE, errmin_digits = 3,
108
  frame = TRUE, ...)
109
{
110 0
  if (identical(show_residuals, "standardized")) {
111 0
    show_residuals <- TRUE
112 0
    standardized <- TRUE
113
  } else {
114 0
    standardized <- FALSE
115
  }
116

117 0
  if (add && show_residuals) stop("If adding to an existing plot we can not show residuals")
118 0
  if (add && show_errplot) stop("If adding to an existing plot we can not show the error model plot")
119 0
  if (show_residuals && show_errplot) stop("We can either show residuals over time or the error model plot, not both")
120 0
  if (add && sep_obs) stop("If adding to an existing plot we can not show observed variables separately")
121

122

123 0
  solution_type = fit$solution_type
124 0
  parms.all <- c(fit$bparms.optim, fit$bparms.fixed)
125

126 0
  ininames <- c(
127 0
    rownames(subset(fit$start, type == "state")),
128 0
    rownames(subset(fit$fixed, type == "state")))
129 0
  odeini <- parms.all[ininames]
130

131
  # Order initial state variables
132 0
  names(odeini) <- sub("_0", "", names(odeini))
133 0
  odeini <- odeini[names(fit$mkinmod$diffs)]
134

135 0
  outtimes <- seq(xlim[1], xlim[2], length.out=100)
136

137 0
  odenames <- c(
138 0
    rownames(subset(fit$start, type == "deparm")),
139 0
    rownames(subset(fit$fixed, type == "deparm")))
140 0
  odeparms <- parms.all[odenames]
141

142 0
  out <- try(mkinpredict(fit$mkinmod, odeparms, odeini, outtimes,
143 0
             solution_type = solution_type, atol = fit$atol, rtol = fit$rtol),
144 0
             silent = TRUE)
145

146 0
  if (inherits(out, "try-error")) {
147 0
    out <- mkinpredict(fit$mkinmod, odeparms, odeini, outtimes,
148 0
             solution_type = solution_type, atol = fit$atol, rtol = fit$rtol,
149 0
             use_compiled = FALSE)
150
  }
151 0
  out <- as.data.frame(out)
152

153 0
  names(col_obs) <- names(pch_obs) <- names(lty_obs) <- obs_vars
154

155
  # Create a plot layout only if not to be added to an existing plot
156
  # or only a single plot is requested (e.g. by plot.mmkin)
157 0
  do_layout = FALSE
158 0
  if (show_residuals | sep_obs | show_errplot) do_layout = TRUE
159 0
  n_plot_rows = if (sep_obs) length(obs_vars) else 1
160

161 0
  if (do_layout) {
162
    # Layout should be restored afterwards
163 0
    oldpar <- par(no.readonly = TRUE)
164

165
    # If the observed variables are shown separately, or if requested, do row layout
166 0
    if (sep_obs | row_layout) {
167 0
      row_layout <- TRUE
168 0
      n_plot_cols = if (show_residuals | show_errplot) 2 else 1
169 0
      n_plots = n_plot_rows * n_plot_cols
170

171
      # Set relative plot heights, so the first and the last plot are the norm
172
      # and the middle plots (if n_plot_rows >2) are smaller by rel.height.middle
173 0
      rel.heights <- if (n_plot_rows > 2) c(1, rep(rel.height.middle, n_plot_rows - 2), 1)
174 0
                     else rep(1, n_plot_rows)
175 0
      layout_matrix = matrix(1:n_plots,
176 0
                             n_plot_rows, n_plot_cols, byrow = TRUE)
177 0
      layout(layout_matrix, heights = rel.heights)
178
    } else { # else show residuals in the lower third to keep compatibility
179 0
      layout(matrix(c(1, 2), 2, 1), heights = c(2, 1.3))
180 0
            par(mar = c(3, 4, 4, 2) + 0.1)
181
    }
182
  }
183

184
  # Replicate legend position argument if necessary
185 0
  if (length(lpos) == 1) lpos = rep(lpos, n_plot_rows)
186

187
  # Loop over plot rows
188 0
  for (plot_row in 1:n_plot_rows) {
189

190 0
    row_obs_vars = if (sep_obs) obs_vars[plot_row] else obs_vars
191

192
    # Set ylim to sensible default, or to the specified value
193 0
    if (ylim[[1]] == "default") {
194 0
      ylim_row = c(0, max(c(subset(fit$data, variable %in% row_obs_vars)$observed,
195 0
                          unlist(out[row_obs_vars])), na.rm = TRUE))
196
    } else {
197 0
      ylim_row = ylim
198
    }
199

200 0
    if (row_layout) {
201
      # Margins for top row of plots when we have more than one row
202
      # Reduce bottom margin by 2.1 - hides x axis legend
203 0
      if (plot_row == 1 & n_plot_rows > 1) {
204 0
        par(mar = c(3.0, 4.1, 4.1, 2.1))
205
      }
206

207
      # Margins for middle rows of plots, if any
208 0
      if (plot_row > 1 & plot_row < n_plot_rows) {
209
        # Reduce top margin by 2 after the first plot as we have no main title,
210
        # reduced plot height, therefore we need rel.height.middle in the layout
211 0
        par(mar = c(3.0, 4.1, 2.1, 2.1))
212
      }
213

214
      # Margins for bottom row of plots when we have more than one row
215 0
      if (plot_row == n_plot_rows & n_plot_rows > 1) {
216
        # Restore bottom margin for last plot to show x axis legend
217 0
        par(mar = c(5.1, 4.1, 2.1, 2.1))
218
      }
219
    }
220

221
    # Set up the main plot if not to be added to an existing plot
222 0
    if (add == FALSE) {
223 0
      plot(0, type="n",
224 0
        xlim = xlim, ylim = ylim_row,
225 0
        xlab = xlab, ylab = ylab, frame = frame, ...)
226
    }
227

228
    # Plot the data
229 0
    for (obs_var in row_obs_vars) {
230 0
      points(subset(fit$data, variable == obs_var, c(time, observed)),
231 0
        pch = pch_obs[obs_var], col = col_obs[obs_var])
232
    }
233

234
    # Plot the model output
235 0
    matlines(out$time, out[row_obs_vars], col = col_obs[row_obs_vars], lty = lty_obs[row_obs_vars])
236

237 0
    if (legend == TRUE) {
238
      # Get full names from model definition if they are available
239 0
      legend_names = lapply(row_obs_vars, function(x) {
240 0
                            if (!is.null(fit$mkinmod$spec[[x]]$full_name))
241 0
                              if (is.na(fit$mkinmod$spec[[x]]$full_name)) x
242 0
                              else fit$mkinmod$spec[[x]]$full_name
243 0
                            else x
244
        })
245 0
      legend(lpos[plot_row], inset= inset, legend = legend_names,
246 0
        col = col_obs[row_obs_vars], pch = pch_obs[row_obs_vars], lty = lty_obs[row_obs_vars])
247
    }
248

249
    # Show chi2 error value if requested
250 0
    if (show_errmin) {
251 0
      if (length(row_obs_vars) == 1) {
252 0
        errmin_var = row_obs_vars
253
      } else {
254 0
        errmin_var = "All data"
255 0
        if (length(row_obs_vars) != length(fit$mkinmod$map)) {
256 0
          warning("Showing chi2 error level for all data, but only ",
257 0
                  row_obs_vars, " were selected for plotting")
258
        }
259
      }
260

261 0
      chi2 <- signif(100 * mkinerrmin(fit)[errmin_var, "err.min"], errmin_digits)
262
      # Use LateX if the current plotting device is tikz
263 0
      if (names(dev.cur()) == "tikz output") {
264 0
        chi2_text <- paste0("$\\chi^2$ error level = ", chi2, "\\%")
265
      } else {
266 0
        chi2_perc <- paste0(chi2, "%")
267 0
        chi2_text <- bquote(chi^2 ~ "error level" == .(chi2_perc))
268
      }
269 0
      mtext(chi2_text, cex = 0.7, line = 0.4)
270
    }
271

272 0
    if (do_layout & !row_layout) {
273 0
      par(mar = c(5, 4, 0, 2) + 0.1)
274
    }
275

276
    # Show residuals if requested
277 0
    if (show_residuals) {
278 0
      mkinresplot(fit, obs_vars = row_obs_vars, standardized = standardized,
279 0
        pch_obs = pch_obs[row_obs_vars], col_obs = col_obs[row_obs_vars],
280 0
        legend = FALSE, frame = frame)
281
    }
282

283
    # Show error model plot if requested
284 0
    if (show_errplot) {
285 0
      mkinerrplot(fit, obs_vars = row_obs_vars,
286 0
        pch_obs = pch_obs[row_obs_vars], col_obs = col_obs[row_obs_vars],
287 0
        legend = FALSE, frame = frame)
288
    }
289
  }
290 0
  if (do_layout) par(oldpar, no.readonly = TRUE)
291
}
292

293
#' @rdname plot.mkinfit
294
#' @export
295
plot_sep <- function(fit, show_errmin = TRUE,
296
  show_residuals = ifelse(identical(fit$err_mod, "const"), TRUE, "standardized"), ...) {
297 0
  plot.mkinfit(fit, sep_obs = TRUE, show_residuals = show_residuals,
298 0
          show_errmin = show_errmin, ...)
299
}
300

301
#' @rdname plot.mkinfit
302
#' @export
303
plot_res <- function(fit, sep_obs = FALSE, show_errmin = sep_obs,
304
  standardized = ifelse(identical(fit$err_mod, "const"), FALSE, TRUE), ...)
305
{
306 0
  plot.mkinfit(fit, sep_obs = sep_obs, show_errmin = show_errmin,
307 0
    show_residuals = ifelse(standardized, "standardized", TRUE),
308 0
    row_layout = TRUE, ...)
309
}
310

311
#' @rdname plot.mkinfit
312
#' @export
313
plot_err <- function(fit, sep_obs = FALSE, show_errmin = sep_obs, ...) {
314 0
  plot.mkinfit(fit, sep_obs = sep_obs, show_errmin = show_errmin,
315 0
    show_errplot = TRUE, row_layout = TRUE, ...)
316
}
317

318
#' Plot the observed data and the fitted model of an mkinfit object
319
#'
320
#' Deprecated function. It now only calls the plot method
321
#' \code{\link{plot.mkinfit}}.
322
#'
323
#' @param fit an object of class \code{\link{mkinfit}}.
324
#' @param \dots further arguments passed to \code{\link{plot.mkinfit}}.
325
#' @return The function is called for its side effect.
326
#' @author Johannes Ranke
327
#' @export
328
mkinplot <- function(fit, ...)
329
{
330 0
  plot(fit, ...)
331
}

Read our documentation on viewing source code .

Loading