From 20b9c584e7c43ecbb708459e531c24a1a4751e17 Mon Sep 17 00:00:00 2001
From: Johannes Ranke
Date: Sat, 9 Nov 2019 01:05:51 +0100
Subject: 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
---
NAMESPACE | 5 +
NEWS.md | 2 +
R/loftest.R | 112 +++++++++++++
R/logLik.mkinfit.R | 11 +-
_pkgdown.yml | 1 +
docs/news/index.html | 1 +
docs/reference/index.html | 6 +
docs/reference/loftest-1.png | Bin 0 -> 27354 bytes
docs/reference/loftest-2.png | Bin 0 -> 27721 bytes
docs/reference/loftest-3.png | Bin 0 -> 65409 bytes
docs/reference/loftest-4.png | Bin 0 -> 64457 bytes
docs/reference/loftest-5.png | Bin 0 -> 63057 bytes
docs/reference/loftest.html | 343 +++++++++++++++++++++++++++++++++++++++
docs/sitemap.xml | 3 +
man/loftest.Rd | 81 +++++++++
test.log | 26 +--
tests/testthat/FOCUS_2006_D.csf | 2 +-
tests/testthat/setup_script.R | 10 +-
tests/testthat/test_confidence.R | 7 +-
tests/testthat/test_tests.R | 22 ++-
20 files changed, 605 insertions(+), 27 deletions(-)
create mode 100644 R/loftest.R
create mode 100644 docs/reference/loftest-1.png
create mode 100644 docs/reference/loftest-2.png
create mode 100644 docs/reference/loftest-3.png
create mode 100644 docs/reference/loftest-4.png
create mode 100644 docs/reference/loftest-5.png
create mode 100644 docs/reference/loftest.html
create mode 100644 man/loftest.Rd
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 @@
mkin 0.9.49.8 (unreleased) 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
‘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
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
@@ -214,6 +214,12 @@ more datasets
Lack-of-fit test for models fitted to data with replicates
+
+
loftest.Rd
+
+
+
+
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.
+
+
+
loftest(object, ...)
+
+# S3 method for mkinfit
+loftest(object, ...)
+
+
Arguments
+
+
+
+
object
+
A model object with a defined loftest method
+
+
+
...
+
Not used
+
+
+
+
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.
+
See also
+
+
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
#> Likelihood ratio test
+#>
+#> Model 1: ANOVA with error model const
+#> Model 2: SFO with error model const
+#> #Df LogLik Df Chisq Pr(>Chisq)
+#> 1 10 -40.710
+#> 2 3 -63.954 -7 46.487 7.027e-08 ***
+#> ---
+#> Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#
+# 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
#> Likelihood ratio test
+#>
+#> Model 1: ANOVA with error model const
+#> Model 2: DFOP with error model const
+#> #Df LogLik Df Chisq Pr(>Chisq)
+#> 1 10 -40.710
+#> 2 5 -42.453 -5 3.485 0.6257
#
+# 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)
#> Successfully compiled differential equation model from auto-generated C code.
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
#> Likelihood ratio test
+#>
+#> Model 1: ANOVA with error model const
+#> Model 2: m_synth_DFOP_par with error model const and fixed parameter(s) M1_0, M2_0
+#> #Df LogLik Df Chisq Pr(>Chisq)
+#> 1 28 -93.606
+#> 2 9 -102.763 -19 18.313 0.5016
#
+# 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)
+
+
+
+
+
+
+
+
diff --git a/docs/sitemap.xml b/docs/sitemap.xml
index 3a56fe49..66b776b2 100644
--- a/docs/sitemap.xml
+++ b/docs/sitemap.xml
@@ -66,6 +66,9 @@
https://pkgdown.jrwb.de/mkin/reference/ilr.html
+
+ https://pkgdown.jrwb.de/mkin/reference/loftest.html
+ https://pkgdown.jrwb.de/mkin/reference/logLik.mkinfit.html
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)
})
--
cgit v1.2.1