Learn / Inside the pipeline / 10 · storage

R5 vs R18: detecting seeds that freezing hurts

Most orthodox seeds last longest frozen — but some taxa are damaged by −18 °C and survive far better at 5 °C. Detecting that from monitoring data: compute per-species R5 (the longest supported interval at 5 °C) and R18 (at −18 °C), then flag freeze sensitivity by either of two triggers.

The ratio trigger: R18 ⁄ R5 ≤ 0.5 — the frozen interval is at most half the refrigerated one. The ≤ matters; a species whose frozen interval is exactly half was the edge case that forced it. The CI trigger: 5 °C has statistical support (GLM or Wilson) at its latest interval, −18 °C has none, and R5 > R18 — weaker evidence, so it requires both the confidence asymmetry and the raw ordering.

When one storage has no data the flag is NA (you can't compare), with one honest override: if the species' interval demonstrably comes from −18 °C data alone — it survived 20 years frozen — flag FALSE, because the seeds themselves already answered the question. Every flag carries a plain-language reason string; a boolean without its reason is unauditable.

Build it yourself
library(dplyr)
safe_max <- function(x) if (all(is.na(x))) NA_real_ else max(x, na.rm = TRUE)

freeze_flags <- intervals_with_ci %>%     # accession x storage rows + CI flags
  group_by(Family, TaxonName) %>%
  summarise(
    R5   = safe_max(max_year_meeting_p70[StoreDryCode == "5C"]),
    R18  = safe_max(max_year_meeting_p70[StoreDryCode == "-18C"]),
    ci5  = any(glm_support[StoreDryCode == "5C"]   |
               wilson_support[StoreDryCode == "5C"],   na.rm = TRUE),
    ci18 = any(glm_support[StoreDryCode == "-18C"] |
               wilson_support[StoreDryCode == "-18C"], na.rm = TRUE),
    .groups = "drop") %>%
  mutate(
    ratio_trigger = is.finite(R5) & is.finite(R18) & R5 > 0 & (R18 / R5 <= 0.5),
    ci_trigger    = ci5 & !ci18 & is.finite(R5) & is.finite(R18) & (R5 > R18),
    flag_freeze_sensitive = case_when(
      !is.finite(R5) | !is.finite(R18) ~ NA,     # can't compare...
      TRUE ~ ratio_trigger | ci_trigger),
    reason = case_when(
      !is.finite(R5)  ~ "no 5C data",
      !is.finite(R18) ~ "no -18C data",
      ratio_trigger   ~ sprintf("-18C interval (%.0f y) <= 50%% of 5C (%.0f y)",
                                R18, R5),
      ci_trigger      ~ "CI supports 5C at its latest; 5C interval > -18C",
      TRUE            ~ "no evidence of freeze sensitivity"))
# override: NA -> FALSE when the species interval comes from -18C data alone
The math behind this step: Comparing storages and catching freeze damage
From accessions to a species numberPotential supporters: where to look next