Overview
toxval is an R package for estimating ecotoxicological toxicity metrics from fitted concentration-response models. It extracts effect concentrations (ECx) with ecx() and No-Significant-Effect Concentrations (NSEC) with nsec().
toxval currently has methods for Bayesian models fitted with brms or bayesnec and frequentist models fitted with drc. The package is designed so that more model classes can be easily added, letting users estimate toxicity metrics from whichever modelling framework they work in.
For every model class, the fitted curve is evaluated over a grid of concentrations once per realisation of the fit (once per posterior draw for a Bayesian fit, and once per parametric bootstrap replicate for a frequentist one), the metric is found for each realisation, and the realisations are summarised by an estimate and compatibility limits. The result is a toxval tibble with one row per curve and direction.
For the theoretical basis of the NSEC, see Fisher & Fox (2023). For model-averaged N(S)EC estimation across smooth and threshold model classes, see Fisher et al. (2024).
Installation
The current development version can be installed from GitHub with
if (!requireNamespace("remotes")) {
install.packages("remotes")
}
remotes::install_github("open-aims/toxval")Usage
ecx() and nsec() take a fitted model and return a toxval tibble, whichever framework the model was fitted with. The examples estimate the EC10, EC50 and NSEC of ryegrass root length from the ryegrass data in drc, first from a three-parameter log-logistic curve fitted with drc:
library(toxval)
library(drc)
fit_drc <- drm(rootl ~ conc, data = ryegrass, fct = LL.3())
ecx(fit_drc, proportion = c(0.1, 0.5), seed = 42)
#> # A tibble: 4 × 10
#> # Model: drc
#> # Settings: resolution 500 | 1000 bootstrap realisations
#> metric direction estimate conf.low conf.high proportion type control reference
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
#> 1 ecx decreasing 1.33 0.946 1.70 0.1 absolute 7.85 7.06
#> 2 ecx increasing NA NA NA 0.1 absolute 7.85 8.63
#> 3 ecx decreasing 3.27 2.89 3.66 0.5 absolute 7.85 3.92
#> 4 ecx increasing NA NA NA 0.5 absolute 7.85 11.8
#> # ℹ 1 more variable: asymptote <dbl>
nsec(fit_drc, seed = 42)
#> # A tibble: 2 × 14
#> # Model: drc
#> # Settings: resolution 500 | 1000 bootstrap realisations
#> metric direction estimate conf.low conf.high sig_val type anchor control reference
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl>
#> 1 nsec decreasing 1.06 0.504 1.40 0.01 absolute model 7.85 7.36
#> 2 nsec increasing NA NA NA 0.01 absolute model 7.85 8.31
#> # ℹ 4 more variables: asymptote <dbl>, ecnsec <dbl>, ecnsec.low <dbl>, ecnsec.high <dbl>Both directions are estimated every time. Root length falls with concentration, so the increasing rows have no crossing and are NA. The header records the model class and the settings the estimates were computed with. The realisations of a drc fit are parametric bootstrap replicates, so seed makes the results reproducible.
The same curve fitted with brms gives estimates in the same form, summarised over the posterior draws:
fit_brms <- brms::brm(
brms::bf(rootl ~ top / (1 + (conc / ec50)^b), top + ec50 + b ~ 1, nl = TRUE),
data = ryegrass,
prior = brms::set_prior("normal(0, 10)", nlpar = c("top", "ec50", "b"), lb = 0),
seed = 42,
refresh = 0
)
ecx(fit_brms, x_var = "conc", proportion = c(0.1, 0.5))
#> # A tibble: 4 × 10
#> # Model: brmsfit
#> # Settings: resolution 500 | 4000 posterior draws
#> metric direction estimate conf.low conf.high proportion type control reference
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
#> 1 ecx decreasing 1.39 1.00 1.87 0.1 absolute 7.82 7.04
#> 2 ecx increasing NA NA NA 0.1 absolute 7.82 8.60
#> 3 ecx decreasing 3.29 2.91 3.73 0.5 absolute 7.82 3.91
#> 4 ecx increasing NA NA NA 0.5 absolute 7.82 11.7
#> # ℹ 1 more variable: asymptote <dbl>
nsec(fit_brms, x_var = "conc")
#> # A tibble: 2 × 14
#> # Model: brmsfit
#> # Settings: resolution 500 | 4000 posterior draws
#> metric direction estimate conf.low conf.high sig_val type anchor control reference
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl>
#> 1 nsec decreasing 1.14 0.593 1.55 0.01 absolute model 7.82 7.31
#> 2 nsec increasing NA NA NA 0.01 absolute model 7.82 8.36
#> # ℹ 4 more variables: asymptote <dbl>, ecnsec <dbl>, ecnsec.low <dbl>, ecnsec.high <dbl>x_var is required for a brmsfit, since a brms model may have several predictors.
Single fits and model averages from bayesnec::bnec() are accepted when bayesnec is installed, here the model average manec_example. bayesnec also exports functions named ecx() and nsec(), so call them with toxval:::
toxval::ecx(bayesnec::manec_example, proportion = c(0.1, 0.5))
#> # A tibble: 4 × 10
#> # Model: bayesmanecfit
#> # Settings: resolution 500 | 100 posterior draws
#> metric direction estimate conf.low conf.high proportion type control reference
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
#> 1 ecx decreasing 1.49 0.940 1.56 0.1 absolute 2.18 1.96
#> 2 ecx increasing NA NA NA 0.1 absolute 2.18 2.40
#> 3 ecx decreasing 1.67 1.60 1.72 0.5 absolute 2.18 1.09
#> 4 ecx increasing NA NA NA 0.5 absolute 2.18 3.27
#> # ℹ 1 more variable: asymptote <dbl>
toxval::nsec(bayesnec::manec_example)
#> # A tibble: 2 × 14
#> # Model: bayesmanecfit
#> # Settings: resolution 500 | 100 posterior draws
#> metric direction estimate conf.low conf.high sig_val type anchor control reference
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr> <dbl> <dbl>
#> 1 nsec decreasing 1.47 0.766 1.55 0.01 absolute compon… 2.18 2.04
#> 2 nsec increasing NA NA NA 0.01 absolute compon… 2.18 2.33
#> # ℹ 4 more variables: asymptote <dbl>, ecnsec <dbl>, ecnsec.low <dbl>, ecnsec.high <dbl>See ?ecx and ?nsec for how the effect is defined (type), the per-realisation values (draws), one row per group (group_var), and multivariate fits.
Further documentation is on the project page and the reference page.
Changes from toxval 1.0.0
toxval 2.0.0 restructures the package so that every model class is handled the same way and every metric returns the same shape. Code written for toxval 1.0.0 needs the following changes.
-
ecx()andnsec()return atoxvaltibble rather than a vector with attributes. The per-realisation values thatposterior = TRUEreturned are in adrawslist column withdraws = TRUE;posterioris defunct. - Both directions are estimated every time and reported in the
directioncolumn.hormesis_defandhormeare deprecated and ignored. -
ecx_valis deprecated in favour ofproportion, a proportion rather than a percentage, so an EC10 isproportion = 0.1rather thanecx_val = 10. Undertype = "direct"the response value is supplied asreference. -
type = "relative"now measures from the control to the asymptote of the fitted curve, and warns when supplied. What it measured before is nowtype = "range". -
nsec_multi()is defunct.nsec()gives one row per response and direction for a multivariatebrmsfit; see?nsec_multifor how to convert a call. -
curveidandby_groupare deprecated. Supplygroup_varto get one row per level. -
ecx()supportsdrcfits. The realisations of adrcfit are parametric bootstrap replicates, so supplyseedfor reproducible results. -
bayesnecis suggested rather than imported.ecx()andnsec()accept model averages (bayesmanecfit) as well as single fits (bayesnecfit) whenbayesnecis installed.
Support for a new model class is added by writing a toxval_predict() method that returns a toxval_pred, the object every metric computes on. See ?toxval_predict and ?new_toxval_pred.
The NSEC
The No-Significant-Effect Concentration (NSEC) is a model-based alternative to the traditional No-Observed-Effect Concentration (NOEC). Unlike the NOEC, which is tied to the experimental design and is estimated via hypothesis testing, the NSEC:
- decouples the no-effect estimate from the set of concentrations actually tested;
- enables proper statistical inference with uncertainty quantification; and
- retains the intuitive concept of “no observable effect”.
For threshold-based models (NEC models), the NEC is a parameter of the fitted model. Model-averaged N(S)EC estimates combine NSEC values from smooth models with NEC values from threshold models, which is a more statistically defensible approach to no-effect estimation (see Fisher et al. 2024 and the bayesnec package). toxval does not extract the NEC parameter or compute model-averaged N(S)EC estimates.
Motivation
Contemporary ecotoxicology increasingly relies on model-based inference to derive toxicity metrics from concentration-response data. However, the statistical tools used to estimate these metrics remain fragmented, inconsistently defined, and often tied to specific modelling frameworks. These inconsistencies hinder transparency, reproducibility, and comparability in regulatory decision-making.
A core aim of toxval is to operationalise NSEC estimation across both frequentist and Bayesian concentration-response models and to generalise this capability to a broader suite of toxicity metrics. ECx values, while widely used, depend on how effect is defined relative to controls, model structure, and response scale, leading to inconsistencies that are not always transparent to users (see issue #19).
toxval addresses these challenges by providing:
- a consistent interface for extracting ECx and NSEC values from fitted models;
- support for Bayesian and frequentist inference frameworks;
- explicit definitions of effect and no-effect that are independent of model class; and
- reproducible and comparable toxicity estimates across modelling approaches.
Further Information
toxval is provided by the Australian Institute of Marine Science.
Copyright 2024-2026 Australian Institute of Marine Science. Copyright 2025-2026 Poisson Consulting Ltd.
Released under the GPL (>= 2) License.