---
title: "Case study: brown-shrimp lethal TDT"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Case study: brown-shrimp lethal TDT}
  %\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 is a focused walk-through of one dataset: the brown shrimp
(*Crangon crangon*) lethal thermal-death-time (TDT) assay vendored from
[`bayesTLS`](https://github.com/daniel1noble/bayesTLS). It is written for an
applied thermal-biology reader who wants to see the whole `freqTLS` workflow
end to end on a single, well-behaved lethal dataset — fit, visualise, derive
critical temperatures, and place the result beside the Bayesian and classical
two-stage estimates.

`shrimp_lethal` has **148 rows** at seven nominal assay temperatures (30–33 °C
in 0.5 °C steps) crossed with exposure durations from about five minutes to six
hours. The survival
counts are **reconstructed** from the source CSV mortality proportions:
`freqTLS` multiplies each proportion by the trial size and rounds, so
`deaths = round(mortality_prop * total)` and `survived = total - deaths` (the
"R-SHRIMP" reconstruction; see `?shrimp_lethal`).

The benchmark configuration is fixed throughout: reference time `tref = 1` hour,
the **relative** mortality threshold, a `beta_binomial` family, and a constant
4PL shape (`low`, `up`, `k` shared across temperatures). This is the matched
configuration for the two model-based fits. The classical two-stage estimate is
an absolute-LT50 approximation for this near-0/near-1 lethal curve (see
`vignette("comparing-to-bayesTLS")`).

**This article builds without Stan and renders fast.** The `freqTLS` fit runs
live; the Bayesian and classical two-stage numbers are read from a
version-stamped cache shipped with the package. The two chunks copied from
`vignette("comparing-to-bayesTLS")` — the live shrimp fit and the three-way
table — render Stan-free using that cache.

```{r setup}
library(freqTLS)
```

## The live `freqTLS` fit

The `freqTLS` fit needs nothing beyond this package. We standardise the raw
assay table with `standardize_data()`, fit the ungrouped shrimp 4PL by maximum
likelihood with `fit_4pl()`, and read profile-likelihood confidence intervals
for `CTmax` and `z` with `tls()`.

```{r profile-shrimp}
data(shrimp_lethal)
shrimp_std <- standardize_data(
  shrimp_lethal,
  temp = "Temperature_assay", duration = "Duration_exposure_hours",
  n_total = "N_individuals_after_trial", mortality = "Mortality_after_trial",
  duration_unit = "hours"
)
shrimp_fit <- fit_4pl(shrimp_std, t_ref = 1, family = "beta_binomial", quiet = TRUE)
tls(shrimp_fit, method = "profile")$summary
```

`CTmax` lands near 31.8 °C and `z` near 2.2 °C, each with a narrow asymmetry-
respecting profile interval. Biologically, `z` ≈ 2.2 °C is the temperature change
that scales the tolerated exposure tenfold: an exposure that is lethal in about an
hour at 31.8 °C would be lethal in ~6 minutes at 34.0 °C, or take ~10 hours at
29.6 °C. A larger `z` would mean tolerance declines more gradually with
temperature; a smaller `z`, more steeply.

## Seeing the fit

### The Confidence Eye

The default `freqTLS` uncertainty visual is the **Confidence Eye**: a
confidence lens with a hollow point estimate. It carries no prior and makes no
probability statement about the parameter — it is a likelihood confidence
display, not a posterior density.

```{r eye, fig.alt = "Confidence Eye for the shrimp CTmax and z: pale confidence lenses with hollow point estimates, the freqTLS uncertainty display. Both profiles close, so each eye is a closed lens."}
plot_confidence_eye(shrimp_fit, parm = c("CTmax", "z"), method = "profile")
```

### Survival curves

The fitted survival surface, drawn as one curve per assay temperature against
exposure duration:

```{r survival, fig.alt = "Fitted shrimp survival curves: probability of survival declining with exposure duration, one curve per assay temperature, hotter temperatures dropping faster."}
plot_survival_curves(shrimp_fit)
```

### The thermal-death-time line

Collapsing the survival surface to the threshold gives the classical TDT line —
log exposure time at the mortality threshold against temperature. Its slope is
`z` and its position fixes `CTmax`:

```{r tdt, fig.alt = "Shrimp thermal-death-time line: log exposure time at the mortality threshold falling linearly with assay temperature, the slope encoding z."}
plot_tdt_curve(shrimp_fit)
```

## Deriving critical temperatures

Because shrimp lethal TDT measures death, both critical-temperature derivations
are meaningful here. `derive_ctmax()` reads the absolute temperature giving 50%
survival at a one-hour exposure; `derive_tcrit()` returns the rate-multiplier
`T_crit` (valid because this is a lethal endpoint).

```{r derive}
c(
  CTmax_50pct_1h = round(derive_ctmax(shrimp_fit, surv = 0.5, duration = 1), 2),
  T_crit_rate1   = round(derive_tcrit(shrimp_fit, rate = 1), 2)
)
```

The 50%-survival critical temperature at one hour is about 31.7 °C; the
rate-multiplier `T_crit` is about 27.4 °C. These are deterministic transforms of
the fitted `CTmax` / `z`, not new fits. (`derive_tcrit()` prints an explicit
lethal-endpoint caveat: on a sublethal endpoint the `z` is estimated from a
functional decline rather than death, so feeding it into a lethal-damage
accumulator can drive `T_crit` to implausible values. Shrimp lethal TDT is a
lethal endpoint, so the value stands.)

## The three-way comparison, with real numbers

The cache holds the maintainer-built `bayesTLS` (posterior) and classical
two-stage summaries; the `freqTLS` column is computed live as this page
renders. The two model fits use the matched beta-binomial, relative-threshold,
constant-shape configuration at `tref = 1` hour. The classical two-stage column
uses absolute LT50 and is an approximate comparator because the fitted lethal
asymptotes are near zero and one.

```{r three-way, echo = FALSE}
cache_path <- system.file("extdata", "bayesTLS_benchmark_cache.rds", package = "freqTLS")
has_cache  <- nzchar(cache_path) && file.exists(cache_path)

fmt <- function(est, lo, hi) {
  ifelse(is.na(est), "--", sprintf("%.2f [%.2f, %.2f]", est, lo, hi))
}

if (has_cache) {
  cache <- readRDS(cache_path)
  pro <- confint(shrimp_fit$fit, c("CTmax", "z"), method = "profile")  # engine fit from above

  pick <- function(df, parm, cols) {
    r <- df[df$dataset == "shrimp" & df$parameter == parm, ]
    stats::setNames(as.numeric(r[cols]), c("est", "lo", "hi"))
  }
  ts_ct <- pick(cache$two_stage, "CTmax", c("estimate", "lower", "upper"))
  ts_z  <- pick(cache$two_stage, "z",     c("estimate", "lower", "upper"))
  by_ct <- pick(cache$bayesian,  "CTmax", c("median",   "lower", "upper"))
  by_z  <- pick(cache$bayesian,  "z",     c("median",   "lower", "upper"))
  pr_ct <- pro[pro$parameter == "CTmax", ]
  pr_z  <- pro[pro$parameter == "z", ]

  knitr::kable(data.frame(
    Quantity = c("CTmax (°C)", "z (°C / decade)"),
    `Two-stage (delta CI)`    = c(fmt(ts_ct["est"], ts_ct["lo"], ts_ct["hi"]),
                                  fmt(ts_z["est"],  ts_z["lo"],  ts_z["hi"])),
    `bayesTLS (95% CrI)`      = c(fmt(by_ct["est"], by_ct["lo"], by_ct["hi"]),
                                  fmt(by_z["est"],  by_z["lo"],  by_z["hi"])),
    `freqTLS (profile CI)` = c(fmt(pr_ct$estimate, pr_ct$conf.low, pr_ct$conf.high),
                                  fmt(pr_z$estimate,  pr_z$conf.low,  pr_z$conf.high)),
    check.names = FALSE
  ), caption = "Shrimp CTmax and z shown side by side: the two model fits use the relative midpoint; the classical estimator uses absolute LT50.")
} else {
  stop("The shipped bayesTLS benchmark cache is missing; reinstall freqTLS from a complete source tarball.")
}
```

The headline is the agreement between the two interval-bearing model fits: the `freqTLS`
profile **confidence** interval and the `bayesTLS` posterior credible interval
nearly coincide — and the `freqTLS` side is produced live, in milliseconds, with
no Stan. That is the complementary framing made concrete on one dataset: under
the matched configuration the likelihood and the posterior summarise the *same*
fitted curve, one prior-free and by optimisation, the other with a prior and
MCMC.

## Boundary: what this case study does not cover

The shrimp assay in `bayesTLS` also includes a **sublethal time-to-knockdown**
endpoint — time until loss of righting response. That is a time-to-event quantity
with a different likelihood, and it is a deliberate **non-goal** for
`freqTLS`, which fits the single binomial / beta-binomial survival-count 4PL.
For the sublethal knockdown analysis, see `bayesTLS`.

**Where to next:** for a grouped fit, see the zebrafish or aphid case study; to
derive heat-injury / `T_crit` from a fit, see `vignette("heat-injury")`.
