This vignette mirrors Manuscript Figure 5 of the
bayesTLS supplement – a single multi-taxon panel of the
thermal-sensitivity parameter z and the critical
temperature CTmax across the three redistributable case
studies – but draws the freqTLS Confidence
Eye for each estimate instead of a Bayesian posterior ridge.
Each row is a likelihood confidence interval (a pale
lens with a hollow point estimate), never a posterior density.
This vignette builds without Stan. Its
freqTLS maximum-likelihood fits and intervals are read from
a version-stamped maintainer cache generated by the package’s documented
fitting code. The individual case-study articles and test suite run the
same fit/profile paths live. The Bayesian multi-taxon ridge plot is the
bayesTLS complement; see the closing note.
The three shared case-study datasets span three corners of thermal physiology:
zebrafish_lethal life-stage
dataset — a separate experiment from the oxygen-gradient
zebrafish_o2 data in
vignette("case-study-zebrafish");The cross-study question is descriptive: laid side by side, how do
thermal limits compare across a crustacean, a fish (resolved by
developmental stage), and an insect (resolved by sex)?
CTmax (the critical temperature at a fixed reference
exposure) places each taxon on the temperature axis; z (the
change in temperature, in degrees Celsius, that multiplies tolerated
exposure time tenfold) measures how sharply tolerated exposure responds
to temperature: a smaller z is a steeper response
(a small warming sharply cuts tolerated time), a larger
z a more gradual one.
One caveat governs the whole panel. The reference exposures differ by study, because each follows the convention of its source assay:
| Taxon | Endpoint | Reference exposure (tref) |
Threshold |
|---|---|---|---|
| Shrimp | lethal | 1 hour | relative midpoint |
| Zebrafish (per stage) | lethal | 1 hour | relative midpoint |
| D. suzukii (per sex) | lethal | 4 hours (240 min) | relative midpoint |
Because a CTmax is defined at its reference
exposure, the four CTmax values are not on
a single common time scale: the fly number is the fitted relative
midpoint temperature at 4 hours, whereas the shrimp and zebrafish fits
use a 1-hour reference. The panel is therefore an illustrative
cross-taxon synthesis, not a single common-scale comparison.
z, by contrast, is a slope – degrees per tenfold change in
time – and is comparable across taxa regardless of the reference
exposure. Read the CTmax facet as four study-specific
anchors and the z facet as a like-with-like comparison of
duration sensitivity.
Each fit uses the configuration locked for its case study. The
grouped fits (zebrafish, fly) estimate a separate CTmax and
z per level with a shared shape (low,
up, k); the ungrouped shrimp fit estimates one
of each. All three use beta-binomial survival counts.
The cache records the package/source version, R and TMB versions, input
checksums, and the exact family, grouping, reference exposure,
threshold, shape, and interval configuration. The build script is
data-raw/build_case_study_summary_cache.R; the per-study
vignettes and tests exercise the live paths and their data-adequacy
warnings.
The vinegar-fly data ships per-individual (dsuzukii); we
aggregate it to (temp, time, sex) counts in base R, exactly
as the per-study vignette does.
summary_cache_path <- system.file(
"extdata", "case_study_summary_cache.rds", package = "freqTLS"
)
if (!nzchar(summary_cache_path) || !file.exists(summary_cache_path)) {
stop("The shipped cross-case-study cache is missing; reinstall freqTLS from a complete source tarball.")
}
summary_cache <- readRDS(summary_cache_path)
summary_cache$meta[c("schema_version", "generated_on", "freqTLS_version",
"freqTLS_source_commit", "R_version", "TMB_version")]
#> $schema_version
#> [1] 1
#>
#> $generated_on
#> [1] "2026-07-12"
#>
#> $freqTLS_version
#> [1] "0.1.0"
#>
#> $freqTLS_source_commit
#> [1] "589e3af6c7c226c571ddcbf682f86a578f77ad9c"
#>
#> $R_version
#> [1] "R version 4.6.0 (2026-04-24)"
#>
#> $TMB_version
#> [1] "1.9.21"The panel is built from one combined data frame of
(label, parameter, estimate, conf.low, conf.high), obtained
by calling confint(..., method = "profile") on each fit.
For the grouped fits we ask for the per-level parameter names
(CTmax:young_embryos, z:M, and so on); for the
ungrouped fits we ask for the bare CTmax and
z. Every interval here is a profile-likelihood confidence
interval, and – as the conf.status column confirms – every
profile closes.
panel <- summary_cache$panel
# Six rows, two parameters: a tidy printout of the headline numbers.
panel_wide <- data.frame(
Group = panel$label[panel$parameter == "CTmax"],
`CTmax estimate` = round(panel$estimate[panel$parameter == "CTmax"], 2),
`CTmax 95% CI` = sprintf("[%.2f, %.2f]",
panel$conf.low[panel$parameter == "CTmax"],
panel$conf.high[panel$parameter == "CTmax"]),
`z estimate` = round(panel$estimate[panel$parameter == "z"], 2),
`z 95% CI` = sprintf("[%.2f, %.2f]",
panel$conf.low[panel$parameter == "z"],
panel$conf.high[panel$parameter == "z"]),
check.names = FALSE
)
knitr::kable(
panel_wide,
caption = "Six taxon/group rows: CTmax (at the study reference exposure) and z, each with its profile-likelihood 95% confidence interval."
)| Group | CTmax estimate | CTmax 95% CI | z estimate | z 95% CI |
|---|---|---|---|---|
| Shrimp | 31.77 | [31.63, 31.92] | 2.19 | [1.96, 2.46] |
| Zebrafish: young embryos | 39.92 | [39.79, 40.04] | 2.00 | [1.82, 2.19] |
| Zebrafish: old embryos | 41.38 | [41.23, 41.61] | 1.80 | [1.53, 2.16] |
| Zebrafish: larvae | 39.79 | [39.67, 39.92] | 1.98 | [1.76, 2.22] |
| D. suzukii: female | 35.23 | [35.12, 35.32] | 3.01 | [2.86, 3.18] |
| D. suzukii: male | 35.25 | [35.16, 35.34] | 3.18 | [3.01, 3.36] |
The same headline numbers are shown beside the classical two-stage
estimator and the bayesTLS posterior, read from the
maintainer-built benchmark cache. The freqTLS and
bayesTLS fits use the matched relative-midpoint,
constant-shape configuration and the same per-study reference exposure.
The classical estimator uses absolute LT50 and is therefore an
approximate comparator for these lethal datasets, whose asymptotes lie
near zero and one.
| Group | Quantity | Two-stage (delta CI) | bayesTLS (95% CrI) | freqTLS (profile CI) |
|---|---|---|---|---|
| Shrimp | CTmax (°C) | 31.62 [31.34, 31.89] | 31.72 [31.60, 31.85] | 31.77 [31.63, 31.92] |
| Shrimp | z (°C / decade) | 2.04 [1.49, 2.60] | 2.17 [1.95, 2.43] | 2.19 [1.96, 2.46] |
| Zebrafish: young embryos | CTmax (°C) | 39.61 [39.34, 39.87] | 39.97 [39.82, 40.10] | 39.92 [39.79, 40.04] |
| Zebrafish: young embryos | z (°C / decade) | 2.22 [1.72, 2.71] | 1.93 [1.73, 2.13] | 2.00 [1.82, 2.19] |
| Zebrafish: old embryos | CTmax (°C) | 41.39 [40.88, 41.91] | 41.34 [41.19, 41.56] | 41.38 [41.23, 41.61] |
| Zebrafish: old embryos | z (°C / decade) | 2.33 [1.69, 2.96] | 1.90 [1.62, 2.24] | 1.80 [1.53, 2.16] |
| Zebrafish: larvae | CTmax (°C) | 39.82 [39.61, 40.02] | 39.73 [39.59, 39.85] | 39.79 [39.67, 39.92] |
| Zebrafish: larvae | z (°C / decade) | 2.16 [1.75, 2.57] | 2.02 [1.78, 2.26] | 1.98 [1.76, 2.22] |
| D. suzukii: female | CTmax (°C) | 34.80 [34.56, 35.04] | 35.20 [35.08, 35.30] | 35.23 [35.12, 35.32] |
| D. suzukii: female | z (°C / decade) | 2.99 [2.60, 3.38] | 3.02 [2.88, 3.18] | 3.01 [2.86, 3.18] |
| D. suzukii: male | CTmax (°C) | 34.98 [34.34, 35.62] | 35.22 [35.10, 35.32] | 35.25 [35.16, 35.34] |
| D. suzukii: male | z (°C / decade) | 3.40 [2.15, 4.65] | 3.18 [2.98, 3.39] | 3.18 [3.01, 3.36] |
Each row is drawn as an honest Confidence Eye – a pale, shallow
horizontal lens whose width is exactly the 95% confidence interval and
whose cosine taper is tallest at the estimate, with a hollow point
marking the estimate itself. The two facets carry independent x-axes
(CTmax in degrees Celsius; z in degrees
Celsius per tenfold change in exposure time), because the two parameters
live on different scales and, for CTmax, on different
reference exposures.
The geometry follows the freqTLS Confidence-Eye
contract: the shallow, wide lens reads as a confidence
interval, not a probability density, and a profile that did not
close would render as a hollow point with no lens. All
12 headline profiles here close (six groups for each of two parameters),
so every row carries a lens.
# Build the honest Confidence-Eye geometry by hand so all three fits share one
# cross-taxon panel: a cosine-tapered pale lens (width = confidence interval)
# plus a hollow point, faceted by parameter with free x-axes. This reuses the
# package lens shape (see ?plot_confidence_eye) but spans every fit in one figure.
stopifnot(requireNamespace("ggplot2", quietly = TRUE))
# Fixed top-to-bottom row order (shrimp, zebrafish stages, fly sexes).
row_order <- c(
"Shrimp",
"Zebrafish: young embryos", "Zebrafish: old embryos", "Zebrafish: larvae",
"D. suzukii: female", "D. suzukii: male"
)
panel$row <- match(panel$label, row_order)
panel$parameter <- factor(panel$parameter, levels = c("CTmax", "z"),
labels = c("CTmax (°C)", "z (°C / decade)"))
# One cosine-tapered lens polygon per row (tallest at the estimate, zero at each
# bound), built per facet so the free x-axes do not distort the taper.
lens_df <- do.call(rbind, lapply(seq_len(nrow(panel)), function(i) {
x <- seq(panel$conf.low[i], panel$conf.high[i], length.out = 80)
d_lo <- max(panel$estimate[i] - panel$conf.low[i], .Machine$double.eps)
d_hi <- max(panel$conf.high[i] - panel$estimate[i], .Machine$double.eps)
frac <- ifelse(x <= panel$estimate[i], (panel$estimate[i] - x) / d_lo,
(x - panel$estimate[i]) / d_hi)
w <- 0.32 * cos((pi / 2) * pmin(pmax(frac, 0), 1))
data.frame(id = i, parameter = panel$parameter[i], x = x,
ymin = panel$row[i] - w, ymax = panel$row[i] + w)
}))
# Reference-exposure annotation for the CTmax facet only.
tref_lab <- data.frame(
parameter = factor("CTmax (°C)",
levels = levels(panel$parameter)),
row = panel$row[panel$parameter == "CTmax (°C)"],
x = panel$conf.high[panel$parameter == "CTmax (°C)"],
lab = c("1 h", "1 h", "1 h", "1 h", "4 h", "4 h")
)
ggplot2::ggplot() +
ggplot2::geom_ribbon(
data = lens_df,
ggplot2::aes(x = x, ymin = ymin, ymax = ymax, group = id),
fill = "#1b7837", colour = NA, alpha = 0.30
) +
ggplot2::geom_point(
data = panel,
ggplot2::aes(x = estimate, y = row),
shape = 21, fill = "white", colour = "#1b7837", size = 3, stroke = 1
) +
ggplot2::geom_text(
data = tref_lab,
ggplot2::aes(x = x, y = row, label = lab),
hjust = -0.25, size = 2.7, colour = "grey35"
) +
ggplot2::scale_y_continuous(
breaks = seq_along(row_order), labels = row_order,
trans = "reverse", expand = ggplot2::expansion(add = 0.7)
) +
ggplot2::scale_x_continuous(
expand = ggplot2::expansion(mult = c(0.05, 0.20))
) +
ggplot2::facet_wrap(~ parameter, scales = "free_x") +
ggplot2::labs(
x = NULL, y = NULL,
title = "Thermal limits across three taxa: CTmax and z",
subtitle = "Confidence Eyes: pale lens = 95% confidence interval; hollow point = estimate.",
caption = paste(
"Profile-likelihood confidence intervals (freqTLS, no Stan).",
"CTmax reference exposure differs by study (annotated): not a common time scale."
)
) +
ggplot2::theme_minimal() +
ggplot2::theme(
panel.grid.major.y = ggplot2::element_blank(),
panel.grid.minor = ggplot2::element_blank(),
plot.caption = ggplot2::element_text(hjust = 0)
)The two grouped studies invite a within-taxon comparison: do the
zebrafish life stages differ, and do the fly sexes differ?
freqTLS requests a profile interval on the
difference (dCTmax:A-B, dz:A-B) and
uses its documented bootstrap fallback when a contrast profile does not
close. Both are prior-free frequentist counterparts to the
bayesTLS pMCMC bracket. A difference interval that excludes
zero is a clear separation; one that spans zero is not.
contrast_tbl <- summary_cache$contrasts
knitr::kable(
data.frame(
Contrast = contrast_tbl$parameter,
Difference = round(contrast_tbl$estimate, 3),
`95% CI` = sprintf("[%.3f, %.3f]",
contrast_tbl$conf.low, contrast_tbl$conf.high),
`Excludes 0` = ifelse(
contrast_tbl$conf.low > 0 | contrast_tbl$conf.high < 0, "yes", "no"
),
Method = contrast_tbl$method,
check.names = FALSE
),
caption = "Within-taxon contrasts: 95% confidence intervals from the requested profile or its documented bootstrap fallback."
)| Contrast | Difference | 95% CI | Excludes 0 | Method |
|---|---|---|---|---|
| dCTmax:old_embryos-young_embryos | 1.459 | [1.287, 1.653] | yes | bootstrap |
| dCTmax:larvae-young_embryos | -0.128 | [-0.281, 0.017] | no | bootstrap |
| dCTmax:larvae-old_embryos | -1.587 | [-1.808, -1.399] | yes | bootstrap |
| dz:old_embryos-young_embryos | -0.106 | [-0.285, 0.063] | no | bootstrap |
| dz:larvae-young_embryos | -0.008 | [-1.564, 1.598] | no | profile |
| dz:larvae-old_embryos | 0.098 | [-0.086, 0.283] | no | bootstrap |
| dCTmax:M-F | 0.024 | [-0.092, 0.154] | no | bootstrap |
| dz:M-F | 0.054 | [-0.026, 0.133] | no | bootstrap |
For zebrafish, the one clear separation in CTmax is
old embryos versus young embryos and larvae
versus old embryos: old embryos sit about 1.46 degrees Celsius
above young embryos, with a confidence interval well clear of zero,
while larvae are essentially indistinguishable from young embryos in
CTmax. None of the z contrasts excludes zero,
so the three stages share a common duration sensitivity even where their
critical temperatures differ. For D. suzukii, both the
CTmax and the z sex contrasts span zero: there
is no clear sex difference in either thermal limit, the same conclusion
Ørsted et al. (2024) reached for these data.
Two things stand out from the panel:
Resolving a taxon by an internal axis can matter or
not. Zebrafish life stage shifts CTmax by over a
degree (old embryos are the most heat-tolerant stage), whereas D.
suzukii sex shifts neither CTmax nor z
detectably. The Confidence Eyes make this visible at a glance: the
zebrafish lenses separate on the CTmax axis, while the two
fly lenses overlap almost completely.
Every estimate carries an honest, prior-free interval. All 12 headline profiles close (six groups for each of two parameters), so each row is a closed lens rather than a hollow point. Where a profile did not close (a weakly identified design or a boundary asymptote), the same display would show a hollow point with no lens – never a fabricated closed eye.
These are confidence intervals throughout: they summarise the likelihood’s support for each parameter and make no probability statement about the parameter itself.
This panel is the freqTLS (likelihood) counterpart to
Manuscript Figure 5 of the bayesTLS supplement. Its full
figure also includes the snow-gum PSII dataset, which is not
redistributed here because its source is CC BY-NC 4.0. The Bayesian
supplement draws the taxa as posterior ridge densities
with median points and 95% credible bars. The two displays answer the
same cross-study question from complementary inferential engines: the
bayesTLS ridge is a posterior density shaped by priors and
the data, whereas the freqTLS Confidence Eye is a
prior-free likelihood interval that cannot be read as a posterior. For
the Bayesian multi-taxon ridge plot, the within-taxon pMCMC contrasts,
and the full posterior workflow, see bayesTLS (https://github.com/daniel1noble/bayesTLS); for the
side-by-side posterior-versus-Confidence-Eye contrast on a single
dataset, see vignette("comparing-to-bayesTLS").