--- title: "Case study: zebrafish thermal tolerance under hypoxia, normoxia, and hyperoxia" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Case study: zebrafish thermal tolerance under hypoxia, normoxia, and hyperoxia} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4, dpi = 96 ) ``` This article fits a lethal thermal-death-time (TDT) model to zebrafish (*Danio rerio*) larvae assayed under three oxygen treatments — **hypoxia, normoxia, and hyperoxia** — and asks a question motivated by the **oxygen- and capacity-limited thermal tolerance** (OCLTT) hypothesis (Pörtner 2001): does oxygen availability shift the upper thermal limit? OCLTT is actively debated rather than settled (see, e.g., Jutfelt et al. 2018), so the framing here is hypothesis-testing, not mechanism confirmation: if oxygen matters, the critical temperature `CTmax` and the thermal-sensitivity slope `z` should shift with the oxygen treatment. It mirrors the zebrafish case study in [`bayesTLS`](https://github.com/daniel1noble/bayesTLS), fit here by maximum likelihood instead of MCMC. The fit runs live (TMB, **no Stan**) and the treatment effect is the direct CTmax/z parameterisation — one `CTmax` and one `z` per oxygen level. Uncertainty is summarised by **confidence intervals**; for this well-identified three-group fit we use the fast Wald intervals, while the profile-likelihood intervals that are the `freqTLS` default are showcased on the single-fit studies (e.g. `vignette("case-study-shrimp")`). ```{r setup} library(freqTLS) ``` ## The dataset and the question `zebrafish_o2` is vendored from `bayesTLS` (Saruhashi et al. 2026) under CC BY 4.0; cite the source and `citation("freqTLS")` when you use it (see `?zebrafish_o2`). It records survival counts in a temperature × duration grid for diploid and triploid larvae across the three oxygen treatments. We take the **diploid** larvae — the focal group; triploids are a separate extension — and let `CTmax` and `z` depend on the oxygen treatment. ```{r data} data(zebrafish_o2) zf <- subset(zebrafish_o2, ploidy == "diploid") table(zf$oxygen) ``` ```{r standardize} std <- standardize_data( zf, temp = "temp", duration = "duration_min", n_total = "n_total", n_surv = "n_surv", duration_unit = "minutes" ) ``` The design spans assay temperatures from `r min(zf$temp)` to `r max(zf$temp)` °C and exposures up to `r max(zf$duration_min)` minutes, replicated across the three oxygen treatments — `r nrow(zf)` rows in total. `oxygen` is the covariate of interest. ## The grouped fit (live) A single `fit_4pl()` call fits all three oxygen treatments at once, each with its own `CTmax` and `z` but a shared constant 4PL shape (the temperature effect runs through the midpoint). `CTmax` is reported at a one-hour reference exposure (`t_ref = 60` minutes). ```{r fit} fit <- suppressWarnings(fit_4pl( std, ctmax = ~ 0 + oxygen, z = ~ 0 + oxygen, t_ref = 60, family = "beta_binomial" )) fit ``` Per-treatment `CTmax` and `z` with confidence intervals: ```{r tls} ox <- tls(fit, by = "oxygen", method = "wald") ox$summary ``` ```{r ranking, echo = FALSE} ct <- ox$summary[ox$summary$quantity == "CTmax", ] ct <- ct[order(ct$median), ] ``` Evaluate the OCLTT prediction with `CTmax` (the one-hour critical temperature): the treatments order from lowest to highest as **`r paste(ct$oxygen, sprintf("(%.1f °C)", ct$median), collapse = " < ")`**. Under the OCLTT hypothesis, restricting oxygen should *lower* the thermal limit, so hypoxia is expected at the cold end and hyperoxia at the warm end; the fitted ordering and the overlap (or separation) of the intervals are what the data say about that prediction. ## A Confidence Eye per treatment The default `freqTLS` uncertainty visual is the **Confidence Eye** — a confidence lens with a hollow point estimate, carrying no prior and making no probability statement about the parameter. One lens per oxygen treatment makes the `CTmax` comparison legible; the parameter names are read off the fit so they always match the fitted labels. ```{r eye-ctmax, fig.height = 4.5, fig.alt = "Confidence Eyes for zebrafish CTmax by oxygen treatment: three pale confidence lenses with hollow point estimates, one per treatment, the freqTLS uncertainty display rather than a posterior density."} plot_confidence_eye(fit, parm = get_ctmax(fit)$parameter, method = "wald") ``` The same display for the thermal-sensitivity slope `z`: ```{r eye-z, fig.height = 4.5, fig.alt = "Confidence Eyes for zebrafish z (thermal sensitivity) by oxygen treatment: three pale confidence lenses with hollow point estimates."} plot_confidence_eye(fit, parm = get_z(fit)$parameter, method = "wald") ``` ## Do the treatments differ? Reading the intervals The OCLTT prediction is a statement about *differences* between treatments. The honest frequentist reading is the overlap of the per-treatment confidence intervals above: where two treatments' `CTmax` intervals are disjoint, the data reject equality at the 95% level; where they overlap, the data are consistent with no difference. `freqTLS` also supports an explicit contrast (the difference in `CTmax` or `z` between two treatments, with its own confidence interval and a likelihood-ratio test) — see `vignette("frequentist-and-bayesian")` and `?confint.profile_tls` for the contrast interface. Because the relative threshold is the curve midpoint, these comparisons are prior-free and respect the asymmetry of the likelihood. ## Seeing the fit The fitted survival surface, one curve per oxygen treatment and assay temperature, with the observed proportions overlaid: ```{r survival, fig.height = 4.5, fig.alt = "Fitted zebrafish survival curves under three oxygen treatments: survival probability declining with exposure duration, observed proportions overlaid."} plot_survival_curves(fit) ``` ## Boundary: what this case study does not cover We fit only the **diploid** larvae; the triploid larvae are a separate ploidy contrast left as an extension. The fit holds the 4PL **shape** constant across treatments (the temperature effect runs through the midpoint), matching the `bayesTLS` configuration; relaxing that to a per-group shape (letting `low`, `up`, `k` vary) is the shape-covariate feature demonstrated in `vignette("comparing-to-bayesTLS")`. As a lethal endpoint, the survival-count 4PL applies directly; the sublethal loss-of-equilibrium endpoint is out of scope for `freqTLS`. **Where to next:** the aphid case study adds formal species contrasts; `vignette("random-effects")` covers hierarchical (many-group) designs.