aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Ranke <jranke@uni-bremen.de>2019-11-09 01:05:51 +0100
committerJohannes Ranke <jranke@uni-bremen.de>2019-11-09 01:05:51 +0100
commit20b9c584e7c43ecbb708459e531c24a1a4751e17 (patch)
treea0dd523fc6cb60e33420b0eb9bf79307e5b2a2a4
parentead1f286271923f57d83aed41cb34181a10773ef (diff)
Add a lack-of-fit test
- Switch an example dataset in the test setup to a dataset with replicates, adapt tests - Skip the test for lrtest with an update specification as it does not only fail when pkgdown generates static help pages, but also in testthat
-rw-r--r--NAMESPACE5
-rw-r--r--NEWS.md2
-rw-r--r--R/loftest.R112
-rw-r--r--R/logLik.mkinfit.R11
-rw-r--r--_pkgdown.yml1
-rw-r--r--docs/news/index.html1
-rw-r--r--docs/reference/index.html6
-rw-r--r--docs/reference/loftest-1.pngbin0 -> 27354 bytes
-rw-r--r--docs/reference/loftest-2.pngbin0 -> 27721 bytes
-rw-r--r--docs/reference/loftest-3.pngbin0 -> 65409 bytes
-rw-r--r--docs/reference/loftest-4.pngbin0 -> 64457 bytes
-rw-r--r--docs/reference/loftest-5.pngbin0 -> 63057 bytes
-rw-r--r--docs/reference/loftest.html343
-rw-r--r--docs/sitemap.xml3
-rw-r--r--man/loftest.Rd81
-rw-r--r--test.log26
-rw-r--r--tests/testthat/FOCUS_2006_D.csf2
-rw-r--r--tests/testthat/setup_script.R10
-rw-r--r--tests/testthat/test_confidence.R7
-rw-r--r--tests/testthat/test_tests.R22
20 files changed, 605 insertions, 27 deletions
diff --git a/NAMESPACE b/NAMESPACE
index 8ea4c684..f428a612 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -4,6 +4,7 @@ S3method("[",mmkin)
S3method(AIC,mmkin)
S3method(BIC,mmkin)
S3method(confint,mkinfit)
+S3method(loftest,mkinfit)
S3method(logLik,mkinfit)
S3method(lrtest,mkinfit)
S3method(mkinpredict,mkinfit)
@@ -32,6 +33,7 @@ export(backtransform_odeparms)
export(endpoints)
export(ilr)
export(invilr)
+export(loftest)
export(logistic.solution)
export(lrtest)
export(max_twa_dfop)
@@ -73,8 +75,11 @@ importFrom(parallel,parLapply)
importFrom(stats,AIC)
importFrom(stats,BIC)
importFrom(stats,aggregate)
+importFrom(stats,coef)
importFrom(stats,cov2cor)
importFrom(stats,dist)
+importFrom(stats,dnorm)
+importFrom(stats,lm)
importFrom(stats,logLik)
importFrom(stats,nlminb)
importFrom(stats,nobs)
diff --git a/NEWS.md b/NEWS.md
index 395cd623..965105f4 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,7 @@
# mkin 0.9.49.8 (unreleased)
+- 'loftest': Add a lack-of-fit test
+
- 'plot_res', 'plot_sep' and 'mkinerrplot': Add the possibility to show standardized residuals and make it the default for fits with error models other than 'const'
- 'lrtest.mkinfit': Improve naming of the compared fits in the case of fixed parameters
diff --git a/R/loftest.R b/R/loftest.R
new file mode 100644
index 00000000..29721e23
--- /dev/null
+++ b/R/loftest.R
@@ -0,0 +1,112 @@
+#' Lack-of-fit test for models fitted to data with replicates
+#'
+#' This is a generic function with a method currently only defined for mkinfit
+#' objects. It fits an anova model to the data contained in the object and
+#' compares the likelihoods using the likelihood ratio test
+#' \code{\link[lmtest]{lrtest.default}} from the lmtest package.
+#'
+#' The anova model is interpreted as the simplest form of an mkinfit model,
+#' assuming only a constant variance about the means, but not enforcing any
+#' structure of the means, so we have one model parameter for every mean
+#' of replicate samples.
+#'
+#' @param object A model object with a defined loftest method
+#' @param \dots Not used
+#' @export
+loftest <- function(object, ...) {
+ UseMethod("loftest")
+}
+
+#' @rdname loftest
+#' @importFrom stats logLik lm dnorm coef
+#' @seealso lrtest
+#' @examples
+#' \dontrun{
+#' test_data <- subset(synthetic_data_for_UBA_2014[[12]]$data, name == "parent")
+#' sfo_fit <- mkinfit("SFO", test_data, quiet = TRUE)
+#' plot_res(sfo_fit) # We see a clear pattern in the residuals
+#' loftest(sfo_fit) # We have a clear lack of fit
+#' #
+#' # We try a different model (the one that was used to generate the data)
+#' dfop_fit <- mkinfit("DFOP", test_data, quiet = TRUE)
+#' plot_res(dfop_fit) # We don't see systematic deviations, but heteroscedastic residuals
+#' # therefore we should consider adapting the error model, although we have
+#' loftest(dfop_fit) # no lack of fit
+#' #
+#' # This is the anova model used internally for the comparison
+#' test_data_anova <- test_data
+#' test_data_anova$time <- as.factor(test_data_anova$time)
+#' anova_fit <- lm(value ~ time, data = test_data_anova)
+#' summary(anova_fit)
+#' logLik(anova_fit) # We get the same likelihood and degrees of freedom
+#' #
+#' test_data_2 <- synthetic_data_for_UBA_2014[[12]]$data
+#' m_synth_SFO_lin <- mkinmod(parent = list(type = "SFO", to = "M1"),
+#' M1 = list(type = "SFO", to = "M2"),
+#' M2 = list(type = "SFO"), use_of_ff = "max")
+#' sfo_lin_fit <- mkinfit(m_synth_SFO_lin, test_data_2, quiet = TRUE)
+#' plot_res(sfo_lin_fit) # not a good model, we try parallel formation
+#' loftest(sfo_lin_fit)
+#' #
+#' m_synth_SFO_par <- mkinmod(parent = list(type = "SFO", to = c("M1", "M2")),
+#' M1 = list(type = "SFO"),
+#' M2 = list(type = "SFO"), use_of_ff = "max")
+#' sfo_par_fit <- mkinfit(m_synth_SFO_par, test_data_2, quiet = TRUE)
+#' plot_res(sfo_par_fit) # much better for metabolites
+#' loftest(sfo_par_fit)
+#' #
+#' m_synth_DFOP_par <- mkinmod(parent = list(type = "DFOP", to = c("M1", "M2")),
+#' M1 = list(type = "SFO"),
+#' M2 = list(type = "SFO"), use_of_ff = "max")
+#' dfop_par_fit <- mkinfit(m_synth_DFOP_par, test_data_2, quiet = TRUE)
+#' plot_res(dfop_par_fit) # No visual lack of fit
+#' loftest(dfop_par_fit) # no lack of fit found by the test
+#' #
+#' # The anova model used for comparison in the case of transformation products
+#' test_data_anova_2 <- dfop_par_fit$data
+#' test_data_anova_2$variable <- as.factor(test_data_anova_2$variable)
+#' test_data_anova_2$time <- as.factor(test_data_anova_2$time)
+#' anova_fit_2 <- lm(observed ~ time:variable - 1, data = test_data_anova_2)
+#' summary(anova_fit_2)
+#' }
+#' @export
+loftest.mkinfit <- function(object, ...) {
+
+ name_function <- function(x) {
+ object_name <- paste(x$mkinmod$name, "with error model", x$err_mod)
+ if (length(x$bparms.fixed) > 0) {
+ object_name <- paste(object_name,
+ "and fixed parameter(s)",
+ paste(names(x$bparms.fixed), collapse = ", "))
+ }
+ return(object_name)
+ }
+
+ # Check if we have replicates in the data
+ if (max(aggregate(object$data$observed,
+ by = list(object$data$variable, object$data$time), length)$x) == 1) {
+ stop("Not defined for fits to data without replicates")
+ }
+
+ data_anova <- object$data
+ data_anova$time <- as.factor(data_anova$time)
+ data_anova$variable <- as.factor(data_anova$variable)
+ if (nlevels(data_anova$variable) == 1) {
+ object_2 <- lm(observed ~ time - 1, data = data_anova)
+ } else {
+ object_2 <- lm(observed ~ variable:time - 1,
+ data = data_anova)
+ }
+
+ object_2$mkinmod <- list(name = "ANOVA")
+ object_2$err_mod <- "const"
+ sigma_mle <- sqrt(sum(residuals(object_2)^2)/nobs(object_2))
+ object_2$logLik <- sum(dnorm(x = object_2$residuals,
+ mean = 0, sd = sigma_mle, log = TRUE))
+ object_2$data <- object$data # to make the nobs.mkinfit method work
+ object_2$bparms.optim <- coef(object_2)
+ object_2$errparms <- 1 # We have estimated one error model parameter
+ class(object_2) <- "mkinfit"
+
+ lmtest::lrtest.default(object_2, object, name = name_function)
+}
diff --git a/R/logLik.mkinfit.R b/R/logLik.mkinfit.R
index cadc0d0a..1c025893 100644
--- a/R/logLik.mkinfit.R
+++ b/R/logLik.mkinfit.R
@@ -1,15 +1,15 @@
#' Calculated the log-likelihood of a fitted mkinfit object
-#'
+#'
#' This function returns the product of the likelihood densities of each
#' observed value, as calculated as part of the fitting procedure using
#' \code{\link{dnorm}}, i.e. assuming normal distribution, and with the means
#' predicted by the degradation model, and the standard deviations predicted by
#' the error model.
-#'
+#'
#' The total number of estimated parameters returned with the value of the
#' likelihood is calculated as the sum of fitted degradation model parameters
#' and the fitted error model parameters.
-#'
+#'
#' @param object An object of class \code{\link{mkinfit}}.
#' @param \dots For compatibility with the generic method
#' @return An object of class \code{\link{logLik}} with the number of estimated
@@ -19,7 +19,7 @@
#' @seealso Compare the AIC of columns of \code{\link{mmkin}} objects using
#' \code{\link{AIC.mmkin}}.
#' @examples
-#'
+#'
#' \dontrun{
#' sfo_sfo <- mkinmod(
#' parent = mkinsub("SFO", to = "m1"),
@@ -31,11 +31,12 @@
#' f_tc <- mkinfit(sfo_sfo, d_t, error_model = "tc", quiet = TRUE)
#' AIC(f_nw, f_obs, f_tc)
#' }
-#'
+#'
#' @export
logLik.mkinfit <- function(object, ...) {
val <- object$logLik
# Number of estimated parameters
attr(val, "df") <- length(object$bparms.optim) + length(object$errparms)
+ class(val) <- "logLik"
return(val)
}
diff --git a/_pkgdown.yml b/_pkgdown.yml
index c222a817..c298256f 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -22,6 +22,7 @@ reference:
- confint.mkinfit
- update.mkinfit
- lrtest.mkinfit
+ - loftest
- mkinerrmin
- endpoints
- CAKE_export
diff --git a/docs/news/index.html b/docs/news/index.html
index 48ba25e5..9aa2e18b 100644
--- a/docs/news/index.html
+++ b/docs/news/index.html
@@ -134,6 +134,7 @@
<a href="#mkin-0-9-49-8-unreleased" class="anchor"></a>mkin 0.9.49.8 (unreleased)<small> Unreleased </small>
</h1>
<ul>
+<li><p>‘loftest’: Add a lack-of-fit test</p></li>
<li><p>‘plot_res’, ‘plot_sep’ and ‘mkinerrplot’: Add the possibility to show standardized residuals and make it the default for fits with error models other than ‘const’</p></li>
<li><p>‘lrtest.mkinfit’: Improve naming of the compared fits in the case of fixed parameters</p></li>
<li><p>‘confint.mkinfit’: Make the quadratic approximation the default, as the likelihood profiling takes a lot of time, especially if the fit has more than three parameters</p></li>
diff --git a/docs/reference/index.html b/docs/reference/index.html
index 1c9975e0..0947ff94 100644
--- a/docs/reference/index.html
+++ b/docs/reference/index.html
@@ -215,6 +215,12 @@ more datasets</p></td>
</tr><tr>
<td>
+ <p><code><a href="loftest.html">loftest()</a></code> </p>
+ </td>
+ <td><p>Lack-of-fit test for models fitted to data with replicates</p></td>
+ </tr><tr>
+
+ <td>
<p><code><a href="mkinerrmin.html">mkinerrmin()</a></code> </p>
</td>
<td><p>Calculate the minimum error to assume in order to pass the variance test</p></td>
diff --git a/docs/reference/loftest-1.png b/docs/reference/loftest-1.png
new file mode 100644
index 00000000..3d20f41e
--- /dev/null
+++ b/docs/reference/loftest-1.png
Binary files differ
diff --git a/docs/reference/loftest-2.png b/docs/reference/loftest-2.png
new file mode 100644
index 00000000..be8bf815
--- /dev/null
+++ b/docs/reference/loftest-2.png
Binary files differ
diff --git a/docs/reference/loftest-3.png b/docs/reference/loftest-3.png
new file mode 100644
index 00000000..c66c95f1
--- /dev/null
+++ b/docs/reference/loftest-3.png
Binary files differ
diff --git a/docs/reference/loftest-4.png b/docs/reference/loftest-4.png
new file mode 100644
index 00000000..da86d97f
--- /dev/null
+++ b/docs/reference/loftest-4.png
Binary files differ
diff --git a/docs/reference/loftest-5.png b/docs/reference/loftest-5.png
new file mode 100644
index 00000000..54b176e7
--- /dev/null
+++ b/docs/reference/loftest-5.png
Binary files differ
diff --git a/docs/reference/loftest.html b/docs/reference/loftest.html
new file mode 100644
index 00000000..757f0bbe
--- /dev/null
+++ b/docs/reference/loftest.html
@@ -0,0 +1,343 @@
+<!-- Generated by pkgdown: do not edit by hand -->
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+<title>Lack-of-fit test for models fitted to data with replicates — loftest • mkin</title>
+
+
+<!-- jquery -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
+<!-- Bootstrap -->
+
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=" crossorigin="anonymous" />
+
+<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" crossorigin="anonymous"></script>
+
+<!-- Font Awesome icons -->
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.7.1/css/all.min.css" integrity="sha256-nAmazAk6vS34Xqo0BSrTb+abbtFlgsFK7NKSi6o7Y78=" crossorigin="anonymous" />
+<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.7.1/css/v4-shims.min.css" integrity="sha256-6qHlizsOWFskGlwVOKuns+D1nB6ssZrHQrNj1wGplHc=" crossorigin="anonymous" />
+
+<!-- clipboard.js -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js" integrity="sha256-FiZwavyI2V6+EXO1U+xzLG3IKldpiTFf3153ea9zikQ=" crossorigin="anonymous"></script>
+
+<!-- headroom.js -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/headroom/0.9.4/headroom.min.js" integrity="sha256-DJFC1kqIhelURkuza0AvYal5RxMtpzLjFhsnVIeuk+U=" crossorigin="anonymous"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/headroom/0.9.4/jQuery.headroom.min.js" integrity="sha256-ZX/yNShbjqsohH1k95liqY9Gd8uOiE1S4vZc+9KQ1K4=" crossorigin="anonymous"></script>
+
+<!-- pkgdown -->
+<link href="../pkgdown.css" rel="stylesheet">
+<script src="../pkgdown.js"></script>
+
+
+
+
+<meta property="og:title" content="Lack-of-fit test for models fitted to data with replicates — loftest" />
+<meta property="og:description" content="This is a generic function with a method currently only defined for mkinfit
+objects. It fits an anova model to the data contained in the object and
+compares the likelihoods using the likelihood ratio test
+lrtest.default from the lmtest package." />
+<meta name="twitter:card" content="summary" />
+
+
+
+
+<!-- mathjax -->
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js" integrity="sha256-nvJJv9wWKEm88qvoQl9ekL2J+k/RWIsaSScxxlsrv8k=" crossorigin="anonymous"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/config/TeX-AMS-MML_HTMLorMML.js" integrity="sha256-84DKXVJXs0/F8OTMzX4UR909+jtl4G7SPypPavF+GfA=" crossorigin="anonymous"></script>
+
+<!--[if lt IE 9]>
+<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
+<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+<![endif]-->
+
+
+
+ </head>
+
+ <body>
+ <div class="container template-reference-topic">
+ <header>
+ <div class="navbar navbar-default navbar-fixed-top" role="navigation">
+ <div class="container">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <span class="navbar-brand">
+ <a class="navbar-link" href="../index.html">mkin</a>
+ <span class="version label label-default" data-toggle="tooltip" data-placement="bottom" title="Released version">0.9.49.8</span>
+ </span>
+ </div>
+
+ <div id="navbar" class="navbar-collapse collapse">
+ <ul class="nav navbar-nav">
+ <li>
+ <a href="../reference/index.html">Functions and data</a>
+</li>
+<li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
+ Articles
+
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu" role="menu">
+ <li>
+ <a href="../articles/mkin.html">Introduction to mkin</a>
+ </li>
+ <li>
+ <a href="../articles/FOCUS_D.html">Example evaluation of FOCUS Example Dataset D</a>
+ </li>
+ <li>
+ <a href="../articles/FOCUS_L.html">Example evaluation of FOCUS Laboratory Data L1 to L3</a>
+ </li>
+ <li>
+ <a href="../articles/web_only/FOCUS_Z.html">Example evaluation of FOCUS Example Dataset Z</a>
+ </li>
+ <li>
+ <a href="../articles/web_only/compiled_models.html">Performance benefit by using compiled model definitions in mkin</a>
+ </li>
+ <li>
+ <a href="../articles/twa.html">Calculation of time weighted average concentrations with mkin</a>
+ </li>
+ <li>
+ <a href="../articles/web_only/NAFTA_examples.html">Example evaluation of NAFTA SOP Attachment examples</a>
+ </li>
+ </ul>
+</li>
+<li>
+ <a href="../news/index.html">News</a>
+</li>
+ </ul>
+ <ul class="nav navbar-nav navbar-right">
+
+ </ul>
+
+ </div><!--/.nav-collapse -->
+ </div><!--/.container -->
+</div><!--/.navbar -->
+
+
+
+ </header>
+
+<div class="row">
+ <div class="col-md-9 contents">
+ <div class="page-header">
+ <h1>Lack-of-fit test for models fitted to data with replicates</h1>
+
+ <div class="hidden name"><code>loftest.Rd</code></div>
+ </div>
+
+ <div class="ref-description">
+ <p>This is a generic function with a method currently only defined for mkinfit
+objects. It fits an anova model to the data contained in the object and
+compares the likelihoods using the likelihood ratio test
+<code><a href='https://rdrr.io/pkg/lmtest/man/lrtest.html'>lrtest.default</a></code> from the lmtest package.</p>
+ </div>
+
+ <pre class="usage"><span class='fu'>loftest</span>(<span class='no'>object</span>, <span class='no'>...</span>)
+
+<span class='co'># S3 method for mkinfit</span>
+<span class='fu'>loftest</span>(<span class='no'>object</span>, <span class='no'>...</span>)</pre>
+
+ <h2 class="hasAnchor" id="arguments"><a class="anchor" href="#arguments"></a>Arguments</h2>
+ <table class="ref-arguments">
+ <colgroup><col class="name" /><col class="desc" /></colgroup>
+ <tr>
+ <th>object</th>
+ <td><p>A model object with a defined loftest method</p></td>
+ </tr>
+ <tr>
+ <th>...</th>
+ <td><p>Not used</p></td>
+ </tr>
+ </table>
+
+ <h2 class="hasAnchor" id="details"><a class="anchor" href="#details"></a>Details</h2>
+
+ <p>The anova model is interpreted as the simplest form of an mkinfit model,
+assuming only a constant variance about the means, but not enforcing any
+structure of the means, so we have one model parameter for every mean
+of replicate samples.</p>
+ <h2 class="hasAnchor" id="see-also"><a class="anchor" href="#see-also"></a>See also</h2>
+
+ <div class='dont-index'><p>lrtest</p></div>
+
+ <h2 class="hasAnchor" id="examples"><a class="anchor" href="#examples"></a>Examples</h2>
+ <pre class="examples"><div class='input'><span class='co'># \dontrun{</span>
+<span class='no'>test_data</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='https://rdrr.io/r/base/subset.html'>subset</a></span>(<span class='no'>synthetic_data_for_UBA_2014</span><span class='kw'>[[</span><span class='fl'>12</span>]]$<span class='no'>data</span>, <span class='no'>name</span> <span class='kw'>==</span> <span class='st'>"parent"</span>)
+<span class='no'>sfo_fit</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='mkinfit.html'>mkinfit</a></span>(<span class='st'>"SFO"</span>, <span class='no'>test_data</span>, <span class='kw'>quiet</span> <span class='kw'>=</span> <span class='fl'>TRUE</span>)
+<span class='fu'><a href='plot.mkinfit.html'>plot_res</a></span>(<span class='no'>sfo_fit</span>) <span class='co'># We see a clear pattern in the residuals</span></div><div class='img'><img src='loftest-1.png' alt='' width='700' height='433' /></div><div class='input'><span class='fu'>loftest</span>(<span class='no'>sfo_fit</span>) <span class='co'># We have a clear lack of fit</span></div><div class='output co'>#&gt; Likelihood ratio test
+#&gt;
+#&gt; Model 1: ANOVA with error model const
+#&gt; Model 2: SFO with error model const
+#&gt; #Df LogLik Df Chisq Pr(&gt;Chisq)
+#&gt; 1 10 -40.710
+#&gt; 2 3 -63.954 -7 46.487 7.027e-08 ***
+#&gt; ---
+#&gt; Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1</div><div class='input'><span class='co'>#</span>
+<span class='co'># We try a different model (the one that was used to generate the data)</span>
+<span class='no'>dfop_fit</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='mkinfit.html'>mkinfit</a></span>(<span class='st'>"DFOP"</span>, <span class='no'>test_data</span>, <span class='kw'>quiet</span> <span class='kw'>=</span> <span class='fl'>TRUE</span>)
+<span class='fu'><a href='plot.mkinfit.html'>plot_res</a></span>(<span class='no'>dfop_fit</span>) <span class='co'># We don't see systematic deviations, but heteroscedastic residuals</span></div><div class='img'><img src='loftest-2.png' alt='' width='700' height='433' /></div><div class='input'><span class='co'># therefore we should consider adapting the error model, although we have</span>
+<span class='fu'>loftest</span>(<span class='no'>dfop_fit</span>) <span class='co'># no lack of fit</span></div><div class='output co'>#&gt; Likelihood ratio test
+#&gt;
+#&gt; Model 1: ANOVA with error model const
+#&gt; Model 2: DFOP with error model const
+#&gt; #Df LogLik Df Chisq Pr(&gt;Chisq)
+#&gt; 1 10 -40.710
+#&gt; 2 5 -42.453 -5 3.485 0.6257</div><div class='input'><span class='co'>#</span>
+<span class='co'># This is the anova model used internally for the comparison</span>
+<span class='no'>test_data_anova</span> <span class='kw'>&lt;-</span> <span class='no'>test_data</span>
+<span class='no'>test_data_anova</span>$<span class='no'>time</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='https://rdrr.io/r/base/factor.html'>as.factor</a></span>(<span class='no'>test_data_anova</span>$<span class='no'>time</span>)
+<span class='no'>anova_fit</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='https://rdrr.io/r/stats/lm.html'>lm</a></span>(<span class='no'>value</span> ~ <span class='no'>time</span>, <span class='kw'>data</span> <span class='kw'>=</span> <span class='no'>test_data_anova</span>)
+<span class='fu'><a href='https://rdrr.io/r/base/summary.html'>summary</a></span>(<span class='no'>anova_fit</span>)</div><div class='output co'>#&gt;
+#&gt; Call:
+#&gt; lm(formula = value ~ time, data = test_data_anova)
+#&gt;
+#&gt; Residuals:
+#&gt; Min 1Q Median 3Q Max
+#&gt; -6.1000 -0.5625 0.0000 0.5625 6.1000
+#&gt;
+#&gt; Coefficients:
+#&gt; Estimate Std. Error t value Pr(&gt;|t|)
+#&gt; (Intercept) 103.150 2.323 44.409 7.44e-12 ***
+#&gt; time1 -19.950 3.285 -6.073 0.000185 ***
+#&gt; time3 -50.800 3.285 -15.465 8.65e-08 ***
+#&gt; time7 -68.500 3.285 -20.854 6.28e-09 ***
+#&gt; time14 -79.750 3.285 -24.278 1.63e-09 ***
+#&gt; time28 -86.000 3.285 -26.181 8.35e-10 ***
+#&gt; time60 -94.900 3.285 -28.891 3.48e-10 ***
+#&gt; time90 -98.500 3.285 -29.986 2.49e-10 ***
+#&gt; time120 -100.450 3.285 -30.580 2.09e-10 ***
+#&gt; ---
+#&gt; Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
+#&gt;
+#&gt; Residual standard error: 3.285 on 9 degrees of freedom
+#&gt; Multiple R-squared: 0.9953, Adjusted R-squared: 0.9912
+#&gt; F-statistic: 240.5 on 8 and 9 DF, p-value: 1.417e-09
+#&gt; </div><div class='input'><span class='fu'><a href='https://rdrr.io/r/stats/logLik.html'>logLik</a></span>(<span class='no'>anova_fit</span>) <span class='co'># We get the same likelihood and degrees of freedom</span></div><div class='output co'>#&gt; 'log Lik.' -40.71015 (df=10)</div><div class='input'><span class='co'>#</span>
+<span class='no'>test_data_2</span> <span class='kw'>&lt;-</span> <span class='no'>synthetic_data_for_UBA_2014</span><span class='kw'>[[</span><span class='fl'>12</span>]]$<span class='no'>data</span>
+<span class='no'>m_synth_SFO_lin</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='mkinmod.html'>mkinmod</a></span>(<span class='kw'>parent</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"SFO"</span>, <span class='kw'>to</span> <span class='kw'>=</span> <span class='st'>"M1"</span>),
+ <span class='kw'>M1</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"SFO"</span>, <span class='kw'>to</span> <span class='kw'>=</span> <span class='st'>"M2"</span>),
+ <span class='kw'>M2</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"SFO"</span>), <span class='kw'>use_of_ff</span> <span class='kw'>=</span> <span class='st'>"max"</span>)</div><div class='output co'>#&gt; <span class='message'>Successfully compiled differential equation model from auto-generated C code.</span></div><div class='input'><span class='no'>sfo_lin_fit</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='mkinfit.html'>mkinfit</a></span>(<span class='no'>m_synth_SFO_lin</span>, <span class='no'>test_data_2</span>, <span class='kw'>quiet</span> <span class='kw'>=</span> <span class='fl'>TRUE</span>)
+<span class='fu'><a href='plot.mkinfit.html'>plot_res</a></span>(<span class='no'>sfo_lin_fit</span>) <span class='co'># not a good model, we try parallel formation</span></div><div class='img'><img src='loftest-3.png' alt='' width='700' height='433' /></div><div class='input'><span class='fu'>loftest</span>(<span class='no'>sfo_lin_fit</span>)</div><div class='output co'>#&gt; Likelihood ratio test
+#&gt;
+#&gt; Model 1: ANOVA with error model const
+#&gt; Model 2: m_synth_SFO_lin with error model const and fixed parameter(s) M1_0, M2_0
+#&gt; #Df LogLik Df Chisq Pr(&gt;Chisq)
+#&gt; 1 28 -93.606
+#&gt; 2 7 -171.927 -21 156.64 &lt; 2.2e-16 ***
+#&gt; ---
+#&gt; Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1</div><div class='input'><span class='co'>#</span>
+<span class='no'>m_synth_SFO_par</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='mkinmod.html'>mkinmod</a></span>(<span class='kw'>parent</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"SFO"</span>, <span class='kw'>to</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/c.html'>c</a></span>(<span class='st'>"M1"</span>, <span class='st'>"M2"</span>)),
+ <span class='kw'>M1</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"SFO"</span>),
+ <span class='kw'>M2</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"SFO"</span>), <span class='kw'>use_of_ff</span> <span class='kw'>=</span> <span class='st'>"max"</span>)</div><div class='output co'>#&gt; <span class='message'>Successfully compiled differential equation model from auto-generated C code.</span></div><div class='input'><span class='no'>sfo_par_fit</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='mkinfit.html'>mkinfit</a></span>(<span class='no'>m_synth_SFO_par</span>, <span class='no'>test_data_2</span>, <span class='kw'>quiet</span> <span class='kw'>=</span> <span class='fl'>TRUE</span>)
+<span class='fu'><a href='plot.mkinfit.html'>plot_res</a></span>(<span class='no'>sfo_par_fit</span>) <span class='co'># much better for metabolites</span></div><div class='img'><img src='loftest-4.png' alt='' width='700' height='433' /></div><div class='input'><span class='fu'>loftest</span>(<span class='no'>sfo_par_fit</span>)</div><div class='output co'>#&gt; Likelihood ratio test
+#&gt;
+#&gt; Model 1: ANOVA with error model const
+#&gt; Model 2: m_synth_SFO_par with error model const and fixed parameter(s) M1_0, M2_0
+#&gt; #Df LogLik Df Chisq Pr(&gt;Chisq)
+#&gt; 1 28 -93.606
+#&gt; 2 7 -156.331 -21 125.45 &lt; 2.2e-16 ***
+#&gt; ---
+#&gt; Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1</div><div class='input'><span class='co'>#</span>
+<span class='no'>m_synth_DFOP_par</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='mkinmod.html'>mkinmod</a></span>(<span class='kw'>parent</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"DFOP"</span>, <span class='kw'>to</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/c.html'>c</a></span>(<span class='st'>"M1"</span>, <span class='st'>"M2"</span>)),
+ <span class='kw'>M1</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"SFO"</span>),
+ <span class='kw'>M2</span> <span class='kw'>=</span> <span class='fu'><a href='https://rdrr.io/r/base/list.html'>list</a></span>(<span class='kw'>type</span> <span class='kw'>=</span> <span class='st'>"SFO"</span>), <span class='kw'>use_of_ff</span> <span class='kw'>=</span> <span class='st'>"max"</span>)</div><div class='output co'>#&gt; <span class='message'>Successfully compiled differential equation model from auto-generated C code.</span></div><div class='input'><span class='no'>dfop_par_fit</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='mkinfit.html'>mkinfit</a></span>(<span class='no'>m_synth_DFOP_par</span>, <span class='no'>test_data_2</span>, <span class='kw'>quiet</span> <span class='kw'>=</span> <span class='fl'>TRUE</span>)
+<span class='fu'><a href='plot.mkinfit.html'>plot_res</a></span>(<span class='no'>dfop_par_fit</span>) <span class='co'># No visual lack of fit</span></div><div class='img'><img src='loftest-5.png' alt='' width='700' height='433' /></div><div class='input'><span class='fu'>loftest</span>(<span class='no'>dfop_par_fit</span>) <span class='co'># no lack of fit found by the test</span></div><div class='output co'>#&gt; Likelihood ratio test
+#&gt;
+#&gt; Model 1: ANOVA with error model const
+#&gt; Model 2: m_synth_DFOP_par with error model const and fixed parameter(s) M1_0, M2_0
+#&gt; #Df LogLik Df Chisq Pr(&gt;Chisq)
+#&gt; 1 28 -93.606
+#&gt; 2 9 -102.763 -19 18.313 0.5016</div><div class='input'><span class='co'>#</span>
+<span class='co'># The anova model used for comparison in the case of transformation products</span>
+<span class='no'>test_data_anova_2</span> <span class='kw'>&lt;-</span> <span class='no'>dfop_par_fit</span>$<span class='no'>data</span>
+<span class='no'>test_data_anova_2</span>$<span class='no'>variable</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='https://rdrr.io/r/base/factor.html'>as.factor</a></span>(<span class='no'>test_data_anova_2</span>$<span class='no'>variable</span>)
+<span class='no'>test_data_anova_2</span>$<span class='no'>time</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='https://rdrr.io/r/base/factor.html'>as.factor</a></span>(<span class='no'>test_data_anova_2</span>$<span class='no'>time</span>)
+<span class='no'>anova_fit_2</span> <span class='kw'>&lt;-</span> <span class='fu'><a href='https://rdrr.io/r/stats/lm.html'>lm</a></span>(<span class='no'>observed</span> ~ <span class='no'>time</span>:<span class='no'>variable</span> - <span class='fl'>1</span>, <span class='kw'>data</span> <span class='kw'>=</span> <span class='no'>test_data_anova_2</span>)
+<span class='fu'><a href='https://rdrr.io/r/base/summary.html'>summary</a></span>(<span class='no'>anova_fit_2</span>)</div><div class='output co'>#&gt;
+#&gt; Call:
+#&gt; lm(formula = observed ~ time:variable - 1, data = test_data_anova_2)
+#&gt;
+#&gt; Residuals:
+#&gt; Min 1Q Median 3Q Max
+#&gt; -6.1000 -0.5875 0.0000 0.5875 6.1000
+#&gt;
+#&gt; Coefficients: (2 not defined because of singularities)
+#&gt; Estimate Std. Error t value Pr(&gt;|t|)
+#&gt; time0:variableparent 103.150 1.573 65.562 &lt; 2e-16 ***
+#&gt; time1:variableparent 83.200 1.573 52.882 &lt; 2e-16 ***
+#&gt; time3:variableparent 52.350 1.573 33.274 &lt; 2e-16 ***
+#&gt; time7:variableparent 34.650 1.573 22.024 &lt; 2e-16 ***
+#&gt; time14:variableparent 23.400 1.573 14.873 6.35e-14 ***
+#&gt; time28:variableparent 17.150 1.573 10.901 5.47e-11 ***
+#&gt; time60:variableparent 8.250 1.573 5.244 1.99e-05 ***
+#&gt; time90:variableparent 4.650 1.573 2.956 0.006717 **
+#&gt; time120:variableparent 2.700 1.573 1.716 0.098507 .
+#&gt; time0:variableM1 NA NA NA NA
+#&gt; time1:variableM1 11.850 1.573 7.532 6.93e-08 ***
+#&gt; time3:variableM1 22.700 1.573 14.428 1.26e-13 ***
+#&gt; time7:variableM1 33.050 1.573 21.007 &lt; 2e-16 ***
+#&gt; time14:variableM1 31.250 1.573 19.863 &lt; 2e-16 ***
+#&gt; time28:variableM1 18.900 1.573 12.013 7.02e-12 ***
+#&gt; time60:variableM1 7.550 1.573 4.799 6.28e-05 ***
+#&gt; time90:variableM1 3.850 1.573 2.447 0.021772 *
+#&gt; time120:variableM1 2.050 1.573 1.303 0.204454
+#&gt; time0:variableM2 NA NA NA NA
+#&gt; time1:variableM2 6.700 1.573 4.259 0.000254 ***
+#&gt; time3:variableM2 16.750 1.573 10.646 8.93e-11 ***
+#&gt; time7:variableM2 25.800 1.573 16.399 6.89e-15 ***
+#&gt; time14:variableM2 28.600 1.573 18.178 6.35e-16 ***
+#&gt; time28:variableM2 25.400 1.573 16.144 9.85e-15 ***
+#&gt; time60:variableM2 21.600 1.573 13.729 3.81e-13 ***
+#&gt; time90:variableM2 17.800 1.573 11.314 2.51e-11 ***
+#&gt; time120:variableM2 14.100 1.573 8.962 2.79e-09 ***
+#&gt; ---
+#&gt; Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
+#&gt;
+#&gt; Residual standard error: 2.225 on 25 degrees of freedom
+#&gt; Multiple R-squared: 0.9979, Adjusted R-squared: 0.9957
+#&gt; F-statistic: 469.2 on 25 and 25 DF, p-value: &lt; 2.2e-16
+#&gt; </div><div class='input'># }
+</div></pre>
+ </div>
+ <div class="col-md-3 hidden-xs hidden-sm" id="sidebar">
+ <h2>Contents</h2>
+ <ul class="nav nav-pills nav-stacked">
+ <li><a href="#arguments">Arguments</a></li>
+ <li><a href="#details">Details</a></li>
+ <li><a href="#see-also">See also</a></li>
+ <li><a href="#examples">Examples</a></li>
+ </ul>
+
+ </div>
+</div>
+
+
+ <footer>
+ <div class="copyright">
+ <p>Developed by Johannes Ranke.</p>
+</div>
+
+<div class="pkgdown">
+ <p>Site built with <a href="https://pkgdown.r-lib.org/">pkgdown</a> 1.4.1.</p>
+</div>
+
+ </footer>
+ </div>
+
+
+
+
+ </body>
+</html>
+
+
diff --git a/docs/sitemap.xml b/docs/sitemap.xml
index 3a56fe49..66b776b2 100644
--- a/docs/sitemap.xml
+++ b/docs/sitemap.xml
@@ -67,6 +67,9 @@
<loc>https://pkgdown.jrwb.de/mkin/reference/ilr.html</loc>
</url>
<url>
+ <loc>https://pkgdown.jrwb.de/mkin/reference/loftest.html</loc>
+ </url>
+ <url>
<loc>https://pkgdown.jrwb.de/mkin/reference/logLik.mkinfit.html</loc>
</url>
<url>
diff --git a/man/loftest.Rd b/man/loftest.Rd
new file mode 100644
index 00000000..397b5c08
--- /dev/null
+++ b/man/loftest.Rd
@@ -0,0 +1,81 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/loftest.R
+\name{loftest}
+\alias{loftest}
+\alias{loftest.mkinfit}
+\title{Lack-of-fit test for models fitted to data with replicates}
+\usage{
+loftest(object, ...)
+
+\method{loftest}{mkinfit}(object, ...)
+}
+\arguments{
+\item{object}{A model object with a defined loftest method}
+
+\item{\dots}{Not used}
+}
+\description{
+This is a generic function with a method currently only defined for mkinfit
+objects. It fits an anova model to the data contained in the object and
+compares the likelihoods using the likelihood ratio test
+\code{\link[lmtest]{lrtest.default}} from the lmtest package.
+}
+\details{
+The anova model is interpreted as the simplest form of an mkinfit model,
+assuming only a constant variance about the means, but not enforcing any
+structure of the means, so we have one model parameter for every mean
+of replicate samples.
+}
+\examples{
+\dontrun{
+test_data <- subset(synthetic_data_for_UBA_2014[[12]]$data, name == "parent")
+sfo_fit <- mkinfit("SFO", test_data, quiet = TRUE)
+plot_res(sfo_fit) # We see a clear pattern in the residuals
+loftest(sfo_fit) # We have a clear lack of fit
+#
+# We try a different model (the one that was used to generate the data)
+dfop_fit <- mkinfit("DFOP", test_data, quiet = TRUE)
+plot_res(dfop_fit) # We don't see systematic deviations, but heteroscedastic residuals
+# therefore we should consider adapting the error model, although we have
+loftest(dfop_fit) # no lack of fit
+#
+# This is the anova model used internally for the comparison
+test_data_anova <- test_data
+test_data_anova$time <- as.factor(test_data_anova$time)
+anova_fit <- lm(value ~ time, data = test_data_anova)
+summary(anova_fit)
+logLik(anova_fit) # We get the same likelihood and degrees of freedom
+#
+test_data_2 <- synthetic_data_for_UBA_2014[[12]]$data
+m_synth_SFO_lin <- mkinmod(parent = list(type = "SFO", to = "M1"),
+ M1 = list(type = "SFO", to = "M2"),
+ M2 = list(type = "SFO"), use_of_ff = "max")
+sfo_lin_fit <- mkinfit(m_synth_SFO_lin, test_data_2, quiet = TRUE)
+plot_res(sfo_lin_fit) # not a good model, we try parallel formation
+loftest(sfo_lin_fit)
+#
+m_synth_SFO_par <- mkinmod(parent = list(type = "SFO", to = c("M1", "M2")),
+ M1 = list(type = "SFO"),
+ M2 = list(type = "SFO"), use_of_ff = "max")
+sfo_par_fit <- mkinfit(m_synth_SFO_par, test_data_2, quiet = TRUE)
+plot_res(sfo_par_fit) # much better for metabolites
+loftest(sfo_par_fit)
+#
+m_synth_DFOP_par <- mkinmod(parent = list(type = "DFOP", to = c("M1", "M2")),
+ M1 = list(type = "SFO"),
+ M2 = list(type = "SFO"), use_of_ff = "max")
+dfop_par_fit <- mkinfit(m_synth_DFOP_par, test_data_2, quiet = TRUE)
+plot_res(dfop_par_fit) # No visual lack of fit
+loftest(dfop_par_fit) # no lack of fit found by the test
+#
+# The anova model used for comparison in the case of transformation products
+test_data_anova_2 <- dfop_par_fit$data
+test_data_anova_2$variable <- as.factor(test_data_anova_2$variable)
+test_data_anova_2$time <- as.factor(test_data_anova_2$time)
+anova_fit_2 <- lm(observed ~ time:variable - 1, data = test_data_anova_2)
+summary(anova_fit_2)
+}
+}
+\seealso{
+lrtest
+}
diff --git a/test.log b/test.log
index 806c72b3..bc6d26ae 100644
--- a/test.log
+++ b/test.log
@@ -2,32 +2,36 @@ Loading mkin
Testing mkin
✔ | OK F W S | Context
✔ | 2 | Export dataset for reading into CAKE
-✔ | 10 | Confidence intervals and p-values [9.7 s]
-✔ | 14 | Error model fitting [36.5 s]
+✔ | 10 | Confidence intervals and p-values [10.1 s]
+✔ | 14 | Error model fitting [40.5 s]
✔ | 4 | Calculation of FOCUS chi2 error levels [2.2 s]
-✔ | 13 | Results for FOCUS D established in expertise for UBA (Ranke 2014) [3.3 s]
+✔ | 13 | Results for FOCUS D established in expertise for UBA (Ranke 2014) [3.4 s]
✔ | 6 | Test fitting the decline of metabolites from their maximum [0.7 s]
✔ | 1 | Fitting the logistic model [0.9 s]
✔ | 1 | Test dataset class mkinds used in gmkin
✔ | 12 | Special cases of mkinfit calls [2.4 s]
✔ | 9 | mkinmod model generation and printing [0.2 s]
✔ | 3 | Model predictions with mkinpredict [0.3 s]
-✔ | 16 | Evaluations according to 2015 NAFTA guidance [4.0 s]
+✔ | 16 | Evaluations according to 2015 NAFTA guidance [4.1 s]
✔ | 4 | Calculation of maximum time weighted average concentrations (TWAs) [2.3 s]
✔ | 3 | Summary
✔ | 11 | Plotting [0.6 s]
✔ | 4 | AIC calculation
✔ | 2 | Residuals extracted from mkinfit models
-✔ | 2 | Complex test case from Schaefer et al. (2007) Piacenza paper [5.3 s]
-✔ | 4 | Fitting the SFORB model [1.7 s]
+✔ | 2 | Complex test case from Schaefer et al. (2007) Piacenza paper [5.6 s]
+✔ | 4 | Fitting the SFORB model [1.8 s]
✔ | 1 | Summaries of old mkinfit objects
-✔ | 4 | Results for synthetic data established in expertise for UBA (Ranke 2014) [7.1 s]
-✔ | 6 | Hypothesis tests [31.2 s]
+✔ | 4 | Results for synthetic data established in expertise for UBA (Ranke 2014) [7.5 s]
+✔ | 7 1 | Hypothesis tests [34.1 s]
+────────────────────────────────────────────────────────────────────────────────
+test_tests.R:59: skip: We can do a likelihood ratio test using an update specification
+Reason: This errors out if called by testthat while it works in a normal R session
+────────────────────────────────────────────────────────────────────────────────
══ Results ═════════════════════════════════════════════════════════════════════
-Duration: 108.3 s
+Duration: 116.9 s
-OK: 132
+OK: 133
Failed: 0
Warnings: 0
-Skipped: 0
+Skipped: 1
diff --git a/tests/testthat/FOCUS_2006_D.csf b/tests/testthat/FOCUS_2006_D.csf
index 528e2b61..09940aa3 100644
--- a/tests/testthat/FOCUS_2006_D.csf
+++ b/tests/testthat/FOCUS_2006_D.csf
@@ -5,7 +5,7 @@ Description:
MeasurementUnits: % AR
TimeUnits: days
Comments: Created using mkin::CAKE_export
-Date: 2019-11-05
+Date: 2019-11-09
Optimiser: IRLS
[Data]
diff --git a/tests/testthat/setup_script.R b/tests/testthat/setup_script.R
index 9becdd2a..e33f4af7 100644
--- a/tests/testthat/setup_script.R
+++ b/tests/testthat/setup_script.R
@@ -32,9 +32,6 @@ f_1_mkin_trans <- mkinfit("SFO", FOCUS_2006_A, quiet = TRUE)
f_1_mkin_notrans <- mkinfit("SFO", FOCUS_2006_A, quiet = TRUE,
transform_rates = FALSE)
-f_2_mkin <- mkinfit("DFOP", FOCUS_2006_C, quiet = TRUE)
-f_2_nls <- nls(value ~ SSbiexp(time, A1, lrc1, A2, lrc2), data = FOCUS_2006_C)
-
# mmkin object of parent fits for tests
models <- c("SFO", "FOMC", "DFOP", "HS")
fits <- mmkin(models,
@@ -62,11 +59,14 @@ f_sfo_sfo.ff <- mkinfit(SFO_SFO.ff,
subset(FOCUS_2006_D, value != 0),
quiet = TRUE)
-# Two metabolites
SFO_lin_a <- synthetic_data_for_UBA_2014[[1]]$data
-
DFOP_par_c <- synthetic_data_for_UBA_2014[[12]]$data
+f_2_mkin <- mkinfit("DFOP", DFOP_par_c, quiet = TRUE)
+f_2_nls <- nls(value ~ SSbiexp(time, A1, lrc1, A2, lrc2), data = subset(DFOP_par_c, name == "parent"))
+f_2_anova <- lm(value ~ as.factor(time), data = subset(DFOP_par_c, name == "parent"))
+
+# Two metabolites
m_synth_SFO_lin <- mkinmod(
parent = mkinsub("SFO", "M1"),
M1 = mkinsub("SFO", "M2"),
diff --git a/tests/testthat/test_confidence.R b/tests/testthat/test_confidence.R
index a2bf1401..e85fdb7a 100644
--- a/tests/testthat/test_confidence.R
+++ b/tests/testthat/test_confidence.R
@@ -54,11 +54,12 @@ test_that("Quadratic confidence intervals for rate constants are comparable to v
# Another case:
se_mkin_2 <- summary(f_2_mkin)$par[1:4, "Std. Error"]
se_nls_2 <- summary(f_2_nls)$coefficients[, "Std. Error"]
- # Here we the ratio of standard errors can be explained by the same
+ # Here the ratio of standard errors can be explained by the same
# principle up to about 3%
+ nobs_DFOP_par_c_parent <- nrow(subset(DFOP_par_c, name == "parent"))
expect_equivalent(
se_nls_2[c("lrc1", "lrc2")] / se_mkin_2[c("log_k1", "log_k2")],
- rep(sqrt(nrow(FOCUS_2006_C) / (nrow(FOCUS_2006_C) - 4)), 2),
+ rep(sqrt(nobs_DFOP_par_c_parent / (nobs_DFOP_par_c_parent - 4)), 2),
tolerance = 0.03)
})
@@ -73,7 +74,7 @@ test_that("Likelihood profile based confidence intervals work", {
}
f_mle <- stats4::mle(f_nll, start = as.list(parms(f)), nobs = nrow(FOCUS_2006_C))
- ci_mkin_1_p_0.95 <- confint(f, method = "profile", level = 0.95,
+ ci_mkin_1_p_0.95 <- confint(f, method = "profile", level = 0.95,
cores = n_cores, quiet = TRUE)
# Magically, we get very similar boundaries as stats4::mle
diff --git a/tests/testthat/test_tests.R b/tests/testthat/test_tests.R
index 5a522f8e..bdc72f08 100644
--- a/tests/testthat/test_tests.R
+++ b/tests/testthat/test_tests.R
@@ -1,5 +1,20 @@
context("Hypothesis tests")
+test_that("The lack-of-fit test works and can be reproduced using nls", {
+
+ expect_error(loftest(f_1_mkin_trans), "Not defined for fits to data without replicates")
+
+ loftest_mkin <- loftest(f_2_mkin)
+
+ # This code is inspired by Ritz and Streibig (2008) Nonlinear Regression using R, p. 64
+ Q <- as.numeric(- 2 * (logLik(f_2_nls) - logLik(f_2_anova)))
+ df.Q <- df.residual(f_2_nls) - df.residual(f_2_anova)
+ p_nls <- 1 - pchisq(Q, df.Q)
+
+ expect_equal(loftest_mkin[["2", "Pr(>Chisq)"]], p_nls, tolerance = 1e-5)
+
+})
+
test_that("The likelihood ratio test works", {
expect_error(lrtest(f_1_mkin_trans, f_2_mkin), "not been fitted to the same data")
@@ -25,7 +40,7 @@ test_that("Updating fitted models works", {
parent = mkinsub("DFOP", to = "A1"),
A1 = mkinsub("SFO", to = "A2"),
A2 = mkinsub("SFO"),
- use_of_ff = "max"
+ use_of_ff = "max", quiet = TRUE
)
f_soil_1_tc <- mkinfit(dfop_sfo_sfo,
@@ -41,6 +56,9 @@ test_that("Updating fitted models works", {
})
test_that("We can do a likelihood ratio test using an update specification", {
+ skip("This errors out if called by testthat while it works in a normal R session")
test_2_mkin_k2 <- lrtest(f_2_mkin, fixed_parms = c(k2 = 0))
- expect_equivalent(test_2_mkin_k2[["2", "Pr(>Chisq)"]], 1.139e-6, tolerance = 1e-8)
+ expect_equivalent(test_2_mkin_k2[["2", "Pr(>Chisq)"]], 4.851e-8, tolerance = 1e-8)
+ test_2_mkin_tc <- lrtest(f_2_mkin, error_model = "tc")
+ expect_equivalent(test_2_mkin_tc[["2", "Pr(>Chisq)"]], 7.302e-5, tolerance = 1e-7)
})

Contact - Imprint