diff --git a/NAMESPACE b/NAMESPACE index 06741e7eab9a60266f9993496c37c8b444cd9238..58db063863ead9fe8cb2bc7ceebf64bc51392656 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,6 +6,7 @@ export(effect_to_stress) export(plot_effect) export(plot_stress) export(predict_ecxsys) +export(predict_mixture) export(stress_to_effect) import(grDevices) import(graphics) diff --git a/NEWS.md b/NEWS.md index 22c5af0c2f0954f1d37c7daa13eaea418127bf86..21adea503dc8fa4e56ec37900e266b814ea88ee5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ # stressaddition (development version) -* Fix documentation of `ecxsys()` and `predict_ecxsys`. +* Added predict_mixture() for the prediction of the effects of mixtures of two toxicants. +* Fixed documentation of `ecxsys()` and `predict_ecxsys`. # stressaddition 2.0.0 diff --git a/R/predict_mixture.R b/R/predict_mixture.R new file mode 100644 index 0000000000000000000000000000000000000000..7dc546fa72ad078e44f60140d711075b42e4db82 --- /dev/null +++ b/R/predict_mixture.R @@ -0,0 +1,104 @@ +# TODO: reference to the paper TODO: explanatio how this works (mixture between +# sam, and ca). +# TODO: Add example with picture. Three plots: model_1, model_2, model_1 with +# predicted line. + +#' Predict the effect of a mixture of two toxicants +#' +#' This method is only suitable for experiments without environmental stress. +#' Any environmental stress in \code{model_1} or \code{model_2} is ignored. +#' +#' @param model_1 The ecxsys model of the toxicant that varies in concentration. +#' @param model_2 The ecxsys model of the toxicant with a fixed concentration. +#' @param concentration_1 The concentration of toxicant 1 in the mixture. +#' @param concentration_2 The concentration of toxicant 2 in the mixture. +#' @param proportion_ca How much of the combined toxicant stress is determined +#' by concentration addition. Must be between 0 and 1. +#' +#' @return A vector of the effects of the mixture, scaled to the effect_max +#' value of model_1. +#' +#' @examples toxicant_model_1 <- ecxsys( +#' concentration = c(0, 0.03, 0.3, 3, 10), +#' hormesis_concentration = 0.3, +#' effect_tox_observed = c(85, 76, 94, 35, 0) +#' ) +#' toxicant_model_2 <- ecxsys( +#' concentration = c(0, 0.1, 1, 10, 100, 1000), +#' hormesis_concentration = 10, +#' effect_tox_observed = c(26, 25, 24, 27, 5, 0), +#' effect_max = 30 +#' ) +#' predict_mixture( +#' toxicant_model_1, +#' toxicant_model_2, +#' c(0, 0.02, 0.2, 2, 20), +#' 5 +#' ) +#' @export +predict_mixture <- function(model_1, + model_2, + concentration_1, + concentration_2, + proportion_ca = 0.5) { + stopifnot( + inherits(model_1, "ecxsys"), + inherits(model_2, "ecxsys"), + is.numeric(concentration_1), + is.numeric(concentration_2), + length(concentration_2) == 1, + !is.na(concentration_2), + proportion_ca >= 0, + proportion_ca <= 1 + ) + + predicted_model_1 <- predict_ecxsys(model_1, concentration_1) + predicted_model_2 <- predict_ecxsys(model_2, concentration_2) + + # tox stress ---------------------------------------------------------- + stress_tox_sam <- predicted_model_1$stress_tox + predicted_model_2$stress_tox + + # Convert the model_2 concentration into an equivalent model_1 concentration + # anc vice versa. Clamp the response levels because drc::ED can't deal with + # 0 and 100. + response_level_2 <- 100 - predicted_model_2$effect_tox / model_2$args$effect_max * 100 + response_level_2 <- clamp(response_level_2, 1e-10, 100 - 1e-10) + concentration_2_equivalent <- drc::ED( + model_1$effect_tox_mod, + response_level_2, + display = FALSE + )[, "Estimate"] + effect_tox_ca_1 <- predict( + model_1$effect_tox_mod, + data.frame(concentration = concentration_1 + concentration_2_equivalent) + ) + + response_level_1 <- 100 - predicted_model_1$effect_tox / model_1$args$effect_max * 100 + response_level_1 <- clamp(response_level_1, 1e-10, 100 - 1e-10) + concentration_1_equivalent <- drc::ED( + model_2$effect_tox_mod, + response_level_1, + display = FALSE + )[, "Estimate"] + effect_tox_ca_2 <- predict( + model_2$effect_tox_mod, + data.frame(concentration = concentration_2 + concentration_1_equivalent) + ) + + stress_tox_ca <- effect_to_stress(effect_tox_ca_1 * 0.5 + effect_tox_ca_2 * 0.5) + + # sys ----------------------------------------------------------------- + stress_tox_max <- pmax(predicted_model_1$stress_tox, predicted_model_2$stress_tox) + sys_1 <- predict(model_1$sys_tox_mod, data.frame(stress_tox = stress_tox_max)) + sys_2 <- predict(model_2$sys_tox_mod, data.frame(stress_tox = stress_tox_max)) + sys_total <- sys_1 * 0.5 + sys_2 * 0.5 + + # combined stress and result ------------------------------------------ + proportion_sam <- 1 - proportion_ca + stress_tox_total <- stress_tox_ca * proportion_ca + stress_tox_sam * proportion_sam + stress_total <- stress_tox_total + sys_total + effect_total <- stress_to_effect(stress_total) * model_1$args$effect_max + + # unname() to remove the name when concentration_1 is a single number. + unname(effect_total) +} diff --git a/man/predict_mixture.Rd b/man/predict_mixture.Rd new file mode 100644 index 0000000000000000000000000000000000000000..901eba64e27c25c5a0d715960038d66fce0efe95 --- /dev/null +++ b/man/predict_mixture.Rd @@ -0,0 +1,53 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/predict_mixture.R +\name{predict_mixture} +\alias{predict_mixture} +\title{Predict the effect of a mixture of two toxicants} +\usage{ +predict_mixture( + model_1, + model_2, + concentration_1, + concentration_2, + proportion_ca = 0.5 +) +} +\arguments{ +\item{model_1}{The ecxsys model of the toxicant that varies in concentration.} + +\item{model_2}{The ecxsys model of the toxicant with a fixed concentration.} + +\item{concentration_1}{The concentration of toxicant 1 in the mixture.} + +\item{concentration_2}{The concentration of toxicant 2 in the mixture.} + +\item{proportion_ca}{How much of the combined toxicant stress is determined +by concentration addition. Must be between 0 and 1.} +} +\value{ +A vector of the effects of the mixture, scaled to the effect_max + value of model_1. +} +\description{ +This method is only suitable for experiments without environmental stress. +Any environmental stress in \code{model_1} or \code{model_2} is ignored. +} +\examples{ +toxicant_model_1 <- ecxsys( + concentration = c(0, 0.03, 0.3, 3, 10), + hormesis_concentration = 0.3, + effect_tox_observed = c(85, 76, 94, 35, 0) +) +toxicant_model_2 <- ecxsys( + concentration = c(0, 0.1, 1, 10, 100, 1000), + hormesis_concentration = 10, + effect_tox_observed = c(26, 25, 24, 27, 5, 0), + effect_max = 30 +) +predict_mixture( + toxicant_model_1, + toxicant_model_2, + c(0, 0.02, 0.2, 2, 20), + 5 +) +} diff --git a/tests/testthat/test-predict_mixture.R b/tests/testthat/test-predict_mixture.R new file mode 100644 index 0000000000000000000000000000000000000000..f482f5c5e3dd0e97052390678fac0ad4d9cf126e --- /dev/null +++ b/tests/testthat/test-predict_mixture.R @@ -0,0 +1,34 @@ +model_1 <- ecxsys( + concentration = c(0, 0.03, 0.3, 3, 10), + hormesis_concentration = 0.3, + effect_tox_observed = c(85, 76, 94, 35, 0), + effect_tox_env_observed = c(24, 23, 32, 0, 0), + effect_max = 101 +) +model_2 <- ecxsys( + concentration = c(0, 0.1, 1, 10, 100, 1000), + hormesis_concentration = 10, + effect_tox_observed = c(26, 25, 24, 27, 5, 0), + effect_max = 30 +) + + +test_that("results have not changed", { + new <- predict_mixture(model_1, model_2, c(0, 0.01, 0.1, 1, 7, 15), 5, 0.3) + reference <- c(90.8690654, 86.1041220, 76.2504530, 49.5585562, 0.1958095, 0) + expect_equal(new, reference, tolerance = 1e-5) +}) + + +test_that("predictions are symmetric", { + conc_1 <- c(0, 10^seq(log10(0.001), log10(20), length.out = 50)) + conc_2 <- 3.5 + prop_ca <- 0.8 + effect_12 <- predict_mixture(model_1, model_2, conc_1, conc_2, prop_ca) + effect_21 <- sapply( + conc_1, + function(x) predict_mixture(model_2, model_1, conc_2, x, prop_ca) + ) + effect_21 <- effect_21 / model_2$args$effect_max * model_1$args$effect_max + expect_equal(effect_12, effect_21) +})