Health
Clinical signs recorded at health exams, by year and by animal.
Viewing entire cohort · records through 17 Aug 2026
21% of scorable exams found a sign needing attention That is 75 of 349 exams, from 60 tortoises over 106 animal-years; resampling whole animals puts it between 17% and 26% (95% interval). Each exam counts once, so this is neither the share of tortoises with a sign nor a rate of disease.
- Needs attention graded moderate or severe
- Slight, noted graded slight
- Nothing seen graded none
- Not assessed not assessed or left blank
Rated over 431 health exams of 64 animals in 8 calendar years, never over the 2,479 encounter records. Recorded signs can only undercount: a sign is seen only on a day someone examined that animal.
How this is counted
The denominator is the 349 exams where every one of the five sign fields was assessed, so 82 of 431 exams stay out, on the assumption that what is missing is unrelated to what would have been recorded. The numerator is the 75 of them with at least one field graded moderate or severe.
Slight grades are noted in the grid but not counted here. For reference, 156 of the 349 scorable exams (45%) recorded a sign of any grade.
The interval comes from 2,000 resamples of whole animals, so one much-examined animal cannot read as many independent exams. The same rule, year by year, draws the band on the chart below.
Bear in mind: the unit is the exam occasion, not the animal, so this is the share of scorable exams that recorded a sign and never the share of tortoises ever affected; the denominator is complete-case, counting only exams where every sign field was assessed, and the interval resamples whole animals because one much-examined animal must not read as many independent exams.
Exams recording a sign needing attention, by year
A scorable exam is one where every field in the lens was assessed. It counts when at least one of those fields was graded moderate or severe.Values by year
Behind this number
4 functions compute it. Each is shown exactly as this build runs it.
.tor_hr_states
apps/hub/R/tor_health_revamp.R
.tor_hr_states <- function (grade)
{
g <- tolower(trimws(.tor_chr(grade)))
out <- rep("unexamined", length(g))
out[g %in% c("moderate", "severe")] <- "attention"
out[g == "slight"] <- "noted"
out[g == "none"] <- "clear"
out
}
.tor_hr_exam_flags
apps/hub/R/tor_health_revamp.R
.tor_hr_exam_flags <- function (health, lens = "any")
{
m <- .tor_hr_state_matrix(health)
cols <- if (identical(lens, "any"))
colnames(m)
else intersect(lens, colnames(m))
if (!length(cols))
stop("Unknown sign lens.", call. = FALSE)
sub <- m[, cols, drop = FALSE]
list(scorable = rowSums(sub == "unexamined") == 0L, attention = rowSums(sub ==
"attention") > 0L, any_sign = rowSums(sub == "attention" | sub ==
"noted") > 0L)
}
.tor_hr_boot
apps/hub/R/tor_health_revamp.R
.tor_hr_boot <- function (hits, exams, b = TOR_HR_RESAMPLES, seed = TOR_HR_SEED)
{
k <- length(exams)
if (!k || sum(exams) == 0)
return(c(NA, NA))
old <- if (exists(".Random.seed", envir = globalenv()))
get(".Random.seed", envir = globalenv())
else NULL
on.exit(if (is.null(old)) {
if (exists(".Random.seed", envir = globalenv())) rm(".Random.seed",
envir = globalenv())
} else assign(".Random.seed", old, envir = globalenv()), add = TRUE)
set.seed(seed)
pick <- matrix(sample.int(k, k * b, replace = TRUE), nrow = k)
share <- colSums(matrix(hits[pick], nrow = k))/colSums(matrix(exams[pick],
nrow = k))
stats::quantile(share, c(0.025, 0.975), names = FALSE, na.rm = TRUE)
}
.tor_hr_share_by_year
apps/hub/R/tor_health_revamp.R
.tor_hr_share_by_year <- function (loaded, lens = "any")
{
.tor_hr_cached(loaded, paste0("share:", lens), function() {
h <- loaded$frames$tor_health
year <- as.integer(format(.tor_date(h$exam_date), "%Y"))
animal <- .tor_chr(h$animal_id)
f <- .tor_hr_exam_flags(h, lens)
floor_n <- .tor_hr_floor("cohort")
years <- sort(unique(year[is.finite(year)]))
rows <- lapply(years, function(y) {
at <- which(year == y)
s <- at[f$scorable[at]]
ids <- unique(animal[s])
hits <- vapply(ids, function(id) sum(f$attention[s][animal[s] ==
id]), numeric(1))
exams <- vapply(ids, function(id) sum(animal[s] == id), numeric(1))
ci <- .tor_hr_boot(hits, exams, seed = TOR_HR_SEED + y)
data.frame(year = y, exams = length(at), scorable = length(s),
attention = sum(f$attention[s]), animals = length(ids), rate = if (length(s))
sum(f$attention[s])/length(s)
else NA, lo = ci[1], hi = ci[2], hedged = length(ids) < floor_n,
coverage = if (length(at))
length(s)/length(at)
else NA, stringsAsFactors = FALSE)
})
out <- if (length(rows))
do.call(rbind, rows)
else data.frame(year = integer(0), exams = integer(0), scorable = integer(0),
attention = integer(0), animals = integer(0), rate = numeric(0),
lo = numeric(0), hi = numeric(0), hedged = logical(0), coverage = numeric(0))
attr(out, "floor") <- floor_n
attr(out, "resamples") <- TOR_HR_RESAMPLES
out
})
}
Read-only excerpts of this build's calculation code (hash dd5f535358ca). No record appears in them, and this page displays them without running them.
Condition index by year, by program arm
The index stored on each exam: mass divided by shell length, height and width. Each arm is gated under its own claim and drawn as its own series; no line averages the two.- Long-term plot survey (PLOT)
- Radio-telemetry (TELEM)
Bear in mind: the stored index is drawn as recorded and cannot be recomputed, because two of the four measurements its published formula needs are absent from these records.
Values by year and arm
| Year | Arm | Exams | Animals voting | Median | Middle half | Held (unit error) |
|---|---|---|---|---|---|---|
| 2019 | Long-term plot survey (PLOT) | 2 | 2 | 0.613 | Too few animals | 0 |
| 2020 | Long-term plot survey (PLOT) | 16 | 9 | 0.597 | 0.557 to 0.608 | 7 |
| 2021 | Long-term plot survey (PLOT) | 3 | 3 | 0.634 | Too few animals | 0 |
| 2022 | Long-term plot survey (PLOT) | 2 | 2 | 0.582 | Too few animals | 0 |
| 2023 | Long-term plot survey (PLOT) | 6 | 6 | 0.623 | Too few animals | 0 |
| 2024 | Long-term plot survey (PLOT) | 2 | 2 | 0.623 | Too few animals | 0 |
| 2025 | Long-term plot survey (PLOT) | 10 | 10 | 0.627 | 0.606 to 0.678 | 0 |
| 2026 | Long-term plot survey (PLOT) | 8 | 8 | 0.591 | 0.580 to 0.661 | 0 |
| 2021 | Radio-telemetry (TELEM) | 145 | 24 | 0.633 | 0.587 to 0.652 | 0 |
| 2022 | Radio-telemetry (TELEM) | 113 | 18 | 0.625 | 0.577 to 0.639 | 0 |
| 2023 | Radio-telemetry (TELEM) | 74 | 14 | 0.599 | 0.572 to 0.623 | 0 |
| 2024 | Radio-telemetry (TELEM) | 39 | 7 | 0.596 | Too few animals | 0 |
| 2025 | Radio-telemetry (TELEM) | 11 | 3 | 0.617 | Too few animals | 0 |
Behind this number
One function computes it. Each is shown exactly as this build runs it.
.tor_hr_condition_by_arm
apps/hub/R/tor_health_revamp.R
.tor_hr_condition_by_arm <- function (loaded)
{
h <- loaded$frames$tor_health
a <- loaded$frames$tor_animal
arm <- .tor_chr(a$programme)[match(.tor_chr(h$animal_id), .tor_chr(a$animal_id))]
date <- .tor_date(h$exam_date)
year <- as.integer(format(date, "%Y"))
value <- .torp_num(h$condition_index_g_cm3)
unit_error <- tolower(trimws(.tor_chr(h$is_bci_unit_error))) %in% c("1",
"true", "t", "yes")
scored <- is.finite(value) & !unit_error
floor_n <- .tor_hr_floor("condition_arm")
keys <- unique(data.frame(arm = arm, year = year, stringsAsFactors = FALSE))
keys <- keys[!is.na(keys$arm) & nzchar(keys$arm) & is.finite(keys$year),
, drop = FALSE]
keys <- keys[order(keys$arm, keys$year), , drop = FALSE]
rows <- lapply(seq_len(nrow(keys)), function(i) {
at <- which(arm == keys$arm[i] & year == keys$year[i])
ok <- at[scored[at]]
ok <- ok[order(.tor_chr(h$animal_id)[ok], date[ok], decreasing = TRUE)]
votes <- ok[!duplicated(.tor_chr(h$animal_id)[ok])]
v <- value[votes]
q <- if (length(v))
stats::quantile(v, c(0.25, 0.5, 0.75), names = FALSE, type = 7)
else rep(NA, 3)
data.frame(arm = keys$arm[i], year = keys$year[i], exams = length(at),
animals = length(unique(.tor_chr(h$animal_id)[at])), votes = length(votes),
held = sum(unit_error[at]), q1 = q[1], median = q[2], q3 = q[3],
band = length(votes) >= floor_n, stringsAsFactors = FALSE)
})
out <- if (length(rows))
do.call(rbind, rows)
else data.frame(arm = character(0), year = integer(0), exams = integer(0),
animals = integer(0), votes = integer(0), held = integer(0), q1 = numeric(0),
median = numeric(0), q3 = numeric(0), band = logical(0))
attr(out, "floor") <- floor_n
out
}
Read-only excerpts of this build's calculation code (hash 5454c88bf4f8). No record appears in them, and this page displays them without running them.