INTRO
This was an additional resazurin assay experiment performed on 2026-06-04 to test the response of nine USDA Magallana gigas families to a 36°C heat stress treatment. The goal was to determine if we could detect differences in metabolic activity between families.
METHODS
Resazurin assays
11 oysters from each of the nine families were distributed across nine, 12-well plates and submerged in 4mL of freshly prepared resazurin working solution. Each plate included one well with working solution only as a negative control. Plates were incubated at 36C for four hours, and fluorescence was measured every 30mins using a Synergy HTX (Agilent) plate reader.
Oyster measurements
Oyster area was measured using ImageJ. Oysters were photographed in their plates with a ruler for scale, and the area of each oyster was calculated using ImageJ “Measure Particles” tool.
Data analysis
Analysis was conducted in this R Markdown file:
The rendered markdown is below.
RESULTS
Using the Area Under the Curve (AUC) approach (metabolism), no significant differences were observed between the nine families in their response to heat stress at 36C.
1 Background
Juvenile oysters from nine USDA families were submerged in 4 mL of room temperature resazurin working solution in 12-well plates and held at 36°C. At each designated timepoint, fluorescence was measured using a Synergy HTX (Agilent) plate reader.
See Resazurin/data/20260604-mgig-36C/README.md for full experimental notes.
1.1 Expected inputs
| Path | Description |
|---|---|
Resazurin/data/20260604-mgig-36C/plate-*-T*.txt |
Plate reader fluorescence exports (one file per plate per timepoint) |
Resazurin/data/20260604-mgig-36C/layout.csv |
Well metadata: plate ID, well ID, blank flag, family groups, sample IDs, area measurements (mm², from ImageJ) |
1.2 Expected outputs
All outputs are written to Resazurin/outputs/01.00-resazurin-20260604-mgig-36C/.
| File | Description |
|---|---|
figures/ |
All plots generated by this script |
auc_all_metrics.csv |
Per-individual AUC values for every active measurement metric |
auc_summary.csv |
Group-level AUC summary statistics (mean, SD, SE, median) |
metabolism.csv |
Full per-well per-timepoint metabolism data frame |
pairwise_stats.csv |
Tukey-adjusted pairwise comparisons from AUC linear models |
2 Setup
2.1 Knitr options
knitr::opts_chunk$set(
echo = TRUE, # Display code chunks
eval = TRUE, # Evaluate code chunks
warning = FALSE, # Hide warnings
message = FALSE, # Hide messages
comment = "", # Prevents appending '##' to beginning of lines in code output
results = 'hold' # Holds output so it's all printed together after code chunk
)2.2 Load libraries
library(tidyverse)
library(pracma) # trapz()
library(lme4)
library(lmerTest)
library(emmeans)
library(multcompView)
library(cowplot)
library(colorspace) # qualitative_hcl() for large palettes3 Helper Functions
normalize_well_id <- function(x) {
x <- toupper(trimws(x))
valid <- str_detect(x, "^[A-Z]+[0-9]+$")
out <- rep(NA_character_, length(x))
if (!any(valid)) return(out)
m <- str_match(x[valid], "^([A-Z]+)([0-9]+)$")
out[valid] <- paste0(m[, 2], as.integer(m[, 3]))
out
}
parse_time_hr <- function(path) {
hit <- str_match(basename(path),
"(?i)-T([0-9]+(?:\\.[0-9]+)?)\\.txt$")
as.numeric(hit[, 2])
}
parse_plate_id <- function(path) {
hit <- str_match(basename(path),
"(?i)^plate-([A-Za-z0-9-]+)-T[0-9]+(?:\\.[0-9]+)?\\.txt$")
id <- hit[, 2]
ifelse(is.na(id), "unknown", id)
}
extract_results_block <- function(lines) {
results_idx <- which(trimws(lines) == "Results")
if (length(results_idx) == 0) stop("No Results section found")
idx <- results_idx[1]
header_tokens <- str_split(lines[idx + 1], "\\t")[[1]] |> trimws()
col_ids <- header_tokens[
header_tokens != "" & str_detect(header_tokens, "^[0-9]+$")]
j <- idx + 2
data_lines <- character()
while (j <= length(lines)) {
line <- lines[j]
if (trimws(line) == "") break
if (!str_detect(line, "^[A-Za-z]\\t")) break
data_lines <- c(data_lines, line)
j <- j + 1
}
list(col_ids = col_ids, data_lines = data_lines)
}
parse_plate_export <- function(path) {
lines <- readLines(path, warn = FALSE)
res <- extract_results_block(lines)
map_dfr(res$data_lines, function(line) {
tokens <- str_split(line, "\\t")[[1]] |> trimws()
tokens <- tokens[tokens != ""]
row_letter <- tokens[1]
nums <- suppressWarnings(as.numeric(tokens[-1]))
valid_idx <- which(!is.na(nums))
if (length(valid_idx) == 0) return(tibble())
vals <- nums[valid_idx]
n <- min(length(vals), length(res$col_ids))
tibble(
row_id = toupper(row_letter),
col_id = as.integer(res$col_ids[seq_len(n)]),
well_id = normalize_well_id(
paste0(toupper(row_letter), res$col_ids[seq_len(n)])),
value = vals[seq_len(n)]
)
}) %>%
mutate(
plate_id = str_to_lower(parse_plate_id(path)),
time_hr = parse_time_hr(path)
)
}
trapezoid_auc <- function(time_hr, value) {
ok <- is.finite(time_hr) & is.finite(value)
t <- time_hr[ok]
v <- value[ok]
if (length(t) < 2) return(NA_real_)
ord <- order(t)
t <- t[ord]; v <- v[ord]
sum(diff(t) * (head(v, -1) + tail(v, -1)) / 2)
}
# Shared helper: extract display unit string from a measurement column name.
# e.g. "area_mm2_measurement" -> "mm²", "weight_mg_measurement" -> "mg"
parse_meas_unit <- function(col_name) {
unit_raw <- col_name |>
str_remove("^metabolism_per_") |>
str_remove("_measurement$") |>
str_extract("[^_]+$")
case_when(
unit_raw == "mm2" ~ "mm²",
unit_raw == "cm2" ~ "cm²",
unit_raw == "mm3" ~ "mm³",
unit_raw == "cm3" ~ "cm³",
TRUE ~ unit_raw
)
}
# y-axis label for metabolism line plots: "fold change/mm²"
metabolism_y_label <- function(col_name) {
paste0("Metabolism (fold change/", parse_meas_unit(col_name), ")")
}
# y-axis label for AUC box plots: "Metabolism (AUC; mm²)"
auc_y_label <- function(metric_name) {
paste0("Metabolism (AUC; ", parse_meas_unit(metric_name), ")")
}4 Load Data
4.1 Plate export files
proj_root <- rprojroot::find_rstudio_root_file()
data_dir <- file.path(proj_root, "Resazurin", "data", "20260604-mgig-36C")
out_dir <- file.path(proj_root, "Resazurin", "outputs",
"01.00-resazurin-20260604-mgig-36C")
fig_dir <- file.path(out_dir, "figures")
dir.create(fig_dir, recursive = TRUE, showWarnings = FALSE)
dir.create(out_dir, recursive = TRUE, showWarnings = FALSE)
plate_files <- list.files(
data_dir,
pattern = "(?i)^plate-.*-T[0-9]+(?:\\.[0-9]+)?\\.txt$",
full.names = TRUE
)
plate_raw <- map_dfr(plate_files, function(path) {
tryCatch(parse_plate_export(path),
error = function(e) {
message("Parse error in ", basename(path), ": ", e$message)
tibble()
})
})
str(plate_raw)tibble [948 × 6] (S3: tbl_df/tbl/data.frame)
$ row_id : chr [1:948] "A" "A" "A" "A" ...
$ col_id : int [1:948] 1 2 3 4 1 2 3 4 1 2 ...
$ well_id : chr [1:948] "A1" "A2" "A3" "A4" ...
$ value : num [1:948] 188 158 173 184 181 173 175 200 171 182 ...
$ plate_id: chr [1:948] "b" "b" "b" "b" ...
$ time_hr : num [1:948] 0 0 0 0 0 0 0 0 0 0 ...
4.2 Plate consistency check
Checks that every plate has the same number of wells at every timepoint. The expected well count is the mode across all plate × timepoint reads. Any plate with at least one deviating read is flagged and dropped entirely before any further analysis — removing only the aberrant timepoint would break the fold-change baseline calculation.
well_counts <- plate_raw %>%
group_by(plate_id, time_hr) %>%
summarise(n_wells = n_distinct(well_id), .groups = "drop")
expected_n_wells <- as.integer(
names(which.max(table(well_counts$n_wells)))
)
inconsistent_reads <- well_counts %>%
filter(n_wells != expected_n_wells) %>%
arrange(plate_id, time_hr)
inconsistent_plate_ids <- unique(inconsistent_reads$plate_id)
if (nrow(inconsistent_reads) > 0) {
cat("**Plate consistency check FAILED.**",
"Expected", expected_n_wells, "wells per plate-timepoint read.",
length(inconsistent_plate_ids),
"plate(s) have at least one deviating read and are excluded",
"from all analyses:\n\n")
cat(knitr::kable(
inconsistent_reads,
col.names = c("Plate", "Time (h)", "Wells read"),
caption = paste("Expected:", expected_n_wells, "wells per read")
), sep = "\n")
cat("\n")
plate_raw <- plate_raw %>%
filter(!plate_id %in% inconsistent_plate_ids)
message(length(inconsistent_plate_ids),
" plate(s) removed from plate_raw: ",
paste(inconsistent_plate_ids, collapse = ", "))
} else {
cat("Plate consistency check passed: all",
n_distinct(well_counts$plate_id), "plates have",
expected_n_wells, "wells at every timepoint.\n")
}Plate consistency check passed: all 9 plates have 12 wells at every timepoint.
4.3 Layout file
layout_path <- file.path(data_dir, "layout.csv")
layout_raw <- read_csv(layout_path,
col_types = cols(.default = "c"),
show_col_types = FALSE)
# Standardise column names to snake_case
names(layout_raw) <- names(layout_raw) |>
str_to_lower() |>
str_replace_all("[^a-z0-9]+", "_") |>
str_replace_all("_+", "_") |>
str_replace("_$", "")
# Normalise plate_id to match plate file ids (strip "plate-" prefix)
layout_clean <- layout_raw %>%
mutate(
plate_id = str_remove(str_to_lower(plate_id), "^plate-"),
well_id = normalize_well_id(plate_well),
is_blank = if ("is_blank" %in% names(layout_raw))
toupper(trimws(is_blank)) %in% c("TRUE", "T", "1", "YES", "Y")
else
FALSE
)
found_exclude_col <- intersect(
c("exclude_from_analysis", "exclude", "omit", "not_analyzed"),
names(layout_clean)
)[1]
layout_clean <- layout_clean %>%
mutate(
exclude_from_analysis = if (!is.na(found_exclude_col))
toupper(trimws(.data[[found_exclude_col]])) %in%
c("TRUE", "T", "1", "YES", "Y")
else
FALSE
)
# Identify measurement columns and group columns
measurement_cols <- names(layout_clean)[
str_detect(names(layout_clean), "_measurement$")]
group_cols <- names(layout_clean)[
str_detect(names(layout_clean), "_group$")]
# Cast measurement columns to numeric
layout_clean <- layout_clean %>%
mutate(across(all_of(measurement_cols),
~ suppressWarnings(as.numeric(.x))))
# Determine which measurement columns actually contain finite data
active_meas_cols <- measurement_cols[
sapply(measurement_cols, function(col)
any(is.finite(layout_clean[[col]]), na.rm = TRUE))]
# Normalise group values to lowercase so they match colour scale definitions
layout_clean <- layout_clean %>%
mutate(across(all_of(group_cols),
~ str_to_lower(trimws(as.character(.x)))))
message("Group columns: ", paste(group_cols, collapse = ", "))
message("Active measurement columns: ",
paste(active_meas_cols, collapse = ", "))
str(layout_clean)tibble [108 × 14] (S3: tbl_df/tbl/data.frame)
$ plate_id : chr [1:108] "b" "b" "b" "b" ...
$ plate_well : chr [1:108] "A01" "A02" "A03" "A04" ...
$ is_blank : logi [1:108] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ family_id_group : chr [1:108] "7" "1" "9" "3" ...
$ sample_id_group : chr [1:108] "1" "2" "3" "4" ...
$ exclude_from_analysis: logi [1:108] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_reason : chr [1:108] NA NA NA NA ...
$ weight_g_measurement : num [1:108] NA NA NA NA NA NA NA NA NA NA ...
$ width_mm_measurement : num [1:108] NA NA NA NA NA NA NA NA NA NA ...
$ length_mm_measurement: num [1:108] NA NA NA NA NA NA NA NA NA NA ...
$ treatment_group : chr [1:108] NA NA NA NA ...
$ area_mm2_measurement : num [1:108] 239.7 78.4 117.2 118.7 224.2 ...
$ imagej_id : chr [1:108] "3" "4" "2" "1" ...
$ well_id : chr [1:108] "A1" "A2" "A3" "A4" ...
5 Merge Plate Data with Layout
dat <- plate_raw %>%
left_join(
layout_clean %>%
select(plate_id, well_id, is_blank, exclude_from_analysis,
any_of("exclude_reason"),
all_of(group_cols), all_of(measurement_cols)),
by = c("plate_id", "well_id")
) %>%
mutate(
is_blank = replace_na(is_blank, FALSE),
exclude_from_analysis = replace_na(exclude_from_analysis, FALSE)
)
str(dat)tibble [948 × 16] (S3: tbl_df/tbl/data.frame)
$ row_id : chr [1:948] "A" "A" "A" "A" ...
$ col_id : int [1:948] 1 2 3 4 1 2 3 4 1 2 ...
$ well_id : chr [1:948] "A1" "A2" "A3" "A4" ...
$ value : num [1:948] 188 158 173 184 181 173 175 200 171 182 ...
$ plate_id : chr [1:948] "b" "b" "b" "b" ...
$ time_hr : num [1:948] 0 0 0 0 0 0 0 0 0 0 ...
$ is_blank : logi [1:948] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_from_analysis: logi [1:948] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_reason : chr [1:948] NA NA NA NA ...
$ family_id_group : chr [1:948] "7" "1" "9" "3" ...
$ sample_id_group : chr [1:948] "1" "2" "3" "4" ...
$ treatment_group : chr [1:948] NA NA NA NA ...
$ weight_g_measurement : num [1:948] NA NA NA NA NA NA NA NA NA NA ...
$ width_mm_measurement : num [1:948] NA NA NA NA NA NA NA NA NA NA ...
$ length_mm_measurement: num [1:948] NA NA NA NA NA NA NA NA NA NA ...
$ area_mm2_measurement : num [1:948] 239.7 78.4 117.2 118.7 224.2 ...
6 Raw Fluorescence
6.1 Data frame
# Wells in the plate reader output that have no layout entry get all-NA group
# columns after the join. Keep only wells assigned to at least one group.
active_gc <- intersect(group_cols, names(dat))
raw_df <- dat %>%
filter(
!is_blank,
if (length(active_gc) > 0)
if_any(all_of(active_gc), ~ !is.na(.))
else
TRUE
) %>%
mutate(
trace_id = if_else(
!is.na(sample_id_group) & trimws(as.character(sample_id_group)) != "",
as.character(sample_id_group),
paste(plate_id, well_id, sep = "_")
)
)
families <- str_sort(unique(na.omit(raw_df$family_id_group)), numeric = TRUE)
treatments <- sort(unique(na.omit(raw_df$treatment_group)))
n_fam <- length(families)
n_trt <- length(treatments)
# Palette strategy:
# <= 7 groups : Okabe-Ito (gold standard for colorblind-safe figures).
# > 7 groups : colorspace::qualitative_hcl("Dynamic") scales to any N
# using perceptually uniform HCL space — no colour collisions.
# Black (#000000) is excluded from both and reserved for blank wells.
okabe_ito_7 <- c(
"#E69F00", "#56B4E9", "#009E73", "#F0E442",
"#0072B2", "#D55E00", "#CC79A7"
)
make_palette <- function(n) {
if (n == 0L) return(character(0))
if (n <= length(okabe_ito_7)) return(okabe_ito_7[seq_len(n)])
colorspace::qualitative_hcl(n, palette = "Dynamic")
}
all_colours <- make_palette(n_fam + n_trt)
fam_colours <- setNames(all_colours[seq_len(n_fam)], families)
trt_colours <- setNames(all_colours[n_fam + seq_len(n_trt)], treatments)
lty_pool <- c("solid", "dashed", "dotted", "dotdash", "longdash")
trt_linetypes <- setNames(
lty_pool[(seq_len(n_trt) - 1L) %% length(lty_pool) + 1L],
treatments
)
plate_well_colours <- c(blank = "black", fam_colours)
has_trt <- n_trt > 0
str(raw_df)tibble [869 × 17] (S3: tbl_df/tbl/data.frame)
$ row_id : chr [1:869] "A" "A" "A" "A" ...
$ col_id : int [1:869] 1 2 3 4 1 2 3 4 1 2 ...
$ well_id : chr [1:869] "A1" "A2" "A3" "A4" ...
$ value : num [1:869] 188 158 173 184 181 173 175 200 171 182 ...
$ plate_id : chr [1:869] "b" "b" "b" "b" ...
$ time_hr : num [1:869] 0 0 0 0 0 0 0 0 0 0 ...
$ is_blank : logi [1:869] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_from_analysis: logi [1:869] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_reason : chr [1:869] NA NA NA NA ...
$ family_id_group : chr [1:869] "7" "1" "9" "3" ...
$ sample_id_group : chr [1:869] "1" "2" "3" "4" ...
$ treatment_group : chr [1:869] NA NA NA NA ...
$ weight_g_measurement : num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ width_mm_measurement : num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ length_mm_measurement: num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ area_mm2_measurement : num [1:869] 239.7 78.4 117.2 118.7 224.2 ...
$ trace_id : chr [1:869] "1" "2" "3" "4" ...
6.2 Raw fluorescence by plate (including blanks)
p_raw_plates <- dat %>%
filter(is.finite(time_hr), is.finite(value)) %>%
mutate(
colour_group = if_else(is_blank, "blank",
coalesce(family_id_group, "sample")),
trace_id = paste(plate_id, well_id, sep = "_")
) %>%
ggplot(aes(x = time_hr, y = value,
group = trace_id, colour = colour_group)) +
geom_line(alpha = 0.6) +
geom_point(size = 1, alpha = 0.7) +
facet_wrap(~ plate_id) +
scale_colour_manual(
values = plate_well_colours,
name = "Group",
breaks = names(plate_well_colours),
na.value = "grey80"
) +
labs(x = "Time (h)", y = "Raw fluorescence (RFU)") +
theme_classic(base_size = 12) +
theme(strip.background = element_blank(),
strip.text = element_text(face = "bold"))
p_raw_plates
ggsave(file.path(fig_dir, "raw_fluor_by_plate.png"),
p_raw_plates, width = 10, height = 8)6.3 Mean raw fluorescence by family
raw_family_summary <- raw_df %>%
filter(!is.na(family_id_group), !exclude_from_analysis) %>%
group_by(family_id_group, treatment_group, time_hr) %>%
summarise(
mean_fluor = mean(value, na.rm = TRUE),
se_fluor = sd(value, na.rm = TRUE) /
sqrt(sum(!is.na(value))),
n = sum(!is.na(value)),
.groups = "drop"
) %>%
mutate(group_var = if (has_trt)
paste(family_id_group, treatment_group, sep = ".")
else
family_id_group)
p_raw_mean <- ggplot(raw_family_summary,
aes(x = time_hr, y = mean_fluor,
colour = family_id_group,
group = group_var)) +
geom_ribbon(aes(ymin = mean_fluor - se_fluor,
ymax = mean_fluor + se_fluor,
fill = family_id_group),
alpha = 0.15, colour = NA) +
geom_line(
mapping = if (has_trt) aes(linetype = treatment_group) else NULL,
linewidth = 1) +
geom_point(size = 2) +
scale_colour_manual(values = fam_colours, name = "Family",
breaks = families) +
scale_fill_manual(values = fam_colours, name = "Family",
breaks = families) +
labs(x = "Time (h)", y = "Mean raw fluorescence (RFU ± SE)") +
theme_classic(base_size = 13) +
if (has_trt) scale_linetype_manual(values = trt_linetypes, name = "Treatment") else NULL
p_raw_mean
ggsave(file.path(fig_dir, "raw_mean_by_family.png"),
p_raw_mean, width = 8, height = 5)6.4 Individual raw fluorescence traces by family
p_raw_by_family <- raw_df %>%
filter(!is.na(family_id_group)) %>%
ggplot(aes(x = time_hr, y = value, group = trace_id,
colour = .data[[if (has_trt) "treatment_group" else "family_id_group"]])) +
geom_line(alpha = 0.6) +
geom_point(size = 1.2, alpha = 0.7) +
facet_wrap(~ family_id_group) +
scale_colour_manual(
values = if (has_trt) trt_colours else fam_colours,
name = if (has_trt) "Treatment" else "Family",
breaks = if (has_trt) treatments else families) +
labs(x = "Time (h)", y = "Raw fluorescence (RFU)") +
theme_classic(base_size = 12) +
theme(strip.background = element_blank(),
strip.text = element_text(face = "bold"))
p_raw_by_family
ggsave(file.path(fig_dir, "raw_individual_by_family.png"),
p_raw_by_family, width = 10, height = 5)6.5 Individual raw fluorescence traces by treatment
if (has_trt) {
p_raw_by_treatment <- raw_df %>%
ggplot(aes(x = time_hr, y = value,
group = trace_id, colour = family_id_group)) +
geom_line(alpha = 0.6) +
geom_point(size = 1.2, alpha = 0.7) +
facet_wrap(~ treatment_group) +
scale_colour_manual(values = fam_colours, name = "Family",
breaks = families) +
labs(x = "Time (h)", y = "Raw fluorescence (RFU)") +
theme_classic(base_size = 12) +
theme(strip.background = element_blank(),
strip.text = element_text(face = "bold"))
p_raw_by_treatment
ggsave(file.path(fig_dir, "raw_individual_by_treatment.png"),
p_raw_by_treatment, width = 10, height = 5)
}6.6 Excluded samples
Wells flagged exclude_from_analysis = TRUE appear in the raw fluorescence plots above but are omitted from all analyses that follow.
excluded_wells <- dat %>%
filter(!is_blank, exclude_from_analysis) %>%
mutate(
sample = if_else(
!is.na(sample_id_group) & trimws(as.character(sample_id_group)) != "",
as.character(sample_id_group),
paste(plate_id, well_id, sep = "_")
)
) %>%
select(plate_id, well_id, sample, family_id_group, treatment_group,
any_of("exclude_reason")) %>%
distinct() %>%
arrange(plate_id, well_id)
if (nrow(excluded_wells) > 0) {
col_names <- c("Plate", "Well", "Sample", "Family", "Treatment")
if ("exclude_reason" %in% names(excluded_wells))
col_names <- c(col_names, "Reason")
cat(knitr::kable(excluded_wells, col.names = col_names), sep = "\n")
} else {
cat("No wells are excluded from analysis.\n")
}No wells are excluded from analysis.
7 Blank Correction via Fold-Change Normalization
T0 is the earliest timepoint present in the dataset (not necessarily 0 hr). Sample fold-change is expressed relative to each individual’s T0 reading, resolved by sample_id_group when that column is populated — allowing the same animal to be tracked across plates — or by plate_id + well_id when no sample IDs exist (backward-compatible with single-plate, multi-timepoint designs). Blank fold-change is the per-plate mean blank RFU at each timepoint divided by the pooled mean blank RFU at T0. Subtracting blank fold-change from sample fold-change removes background fluorescence drift; all samples start at exactly 0 at T0 by construction.
7.1 Step 1 – Identify T0 and compute per-sample fold-change
# T0 = earliest timepoint present in the dataset
t0_time <- min(dat$time_hr[is.finite(dat$time_hr)], na.rm = TRUE)
message("T0 timepoint: ", t0_time, " hr")
# T0 reference value per individual.
# Resolved by sample_id_group (cross-plate tracking) when available;
# falls back to plate+well for layouts without explicit sample IDs.
t0_all <- dat %>%
filter(time_hr == t0_time, !is_blank, is.finite(value)) %>%
mutate(sample_key = if_else(
!is.na(sample_id_group) & trimws(as.character(sample_id_group)) != "",
as.character(sample_id_group),
paste(plate_id, well_id, sep = "_")
)) %>%
group_by(sample_key) %>%
summarise(value_t0 = mean(value, na.rm = TRUE), .groups = "drop")
dat_fc <- dat %>%
mutate(sample_key = if_else(
!is_blank &
!is.na(sample_id_group) & trimws(as.character(sample_id_group)) != "",
as.character(sample_id_group),
paste(plate_id, well_id, sep = "_")
)) %>%
left_join(t0_all, by = "sample_key") %>%
mutate(fold_change = if_else(
!is_blank & is.finite(value_t0) & value_t0 > 0,
value / value_t0,
NA_real_
))
str(dat_fc)tibble [948 × 19] (S3: tbl_df/tbl/data.frame)
$ row_id : chr [1:948] "A" "A" "A" "A" ...
$ col_id : int [1:948] 1 2 3 4 1 2 3 4 1 2 ...
$ well_id : chr [1:948] "A1" "A2" "A3" "A4" ...
$ value : num [1:948] 188 158 173 184 181 173 175 200 171 182 ...
$ plate_id : chr [1:948] "b" "b" "b" "b" ...
$ time_hr : num [1:948] 0 0 0 0 0 0 0 0 0 0 ...
$ is_blank : logi [1:948] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_from_analysis: logi [1:948] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_reason : chr [1:948] NA NA NA NA ...
$ family_id_group : chr [1:948] "7" "1" "9" "3" ...
$ sample_id_group : chr [1:948] "1" "2" "3" "4" ...
$ treatment_group : chr [1:948] NA NA NA NA ...
$ weight_g_measurement : num [1:948] NA NA NA NA NA NA NA NA NA NA ...
$ width_mm_measurement : num [1:948] NA NA NA NA NA NA NA NA NA NA ...
$ length_mm_measurement: num [1:948] NA NA NA NA NA NA NA NA NA NA ...
$ area_mm2_measurement : num [1:948] 239.7 78.4 117.2 118.7 224.2 ...
$ sample_key : chr [1:948] "1" "2" "3" "4" ...
$ value_t0 : num [1:948] 188 158 173 184 181 173 175 200 171 182 ...
$ fold_change : num [1:948] 1 1 1 1 1 1 1 1 1 1 ...
7.2 Step 2 – Blank fold-change reference per plate per timepoint
# Pooled mean blank RFU at T0 across all T0 plates
mean_blank_t0 <- dat %>%
filter(is_blank, time_hr == t0_time, is.finite(value)) %>%
pull(value) %>%
mean(na.rm = TRUE)
if (!is.finite(mean_blank_t0))
message("No blank readings found at T0 (", t0_time,
" hr); blank correction will produce NA.")
# Per-plate per-timepoint mean blank expressed as fold-change relative to T0
blank_fc_ref <- dat %>%
filter(is_blank, is.finite(value)) %>%
group_by(plate_id, time_hr) %>%
summarise(mean_blank_rfu = mean(value, na.rm = TRUE), .groups = "drop") %>%
mutate(mean_blank_fc = mean_blank_rfu / mean_blank_t0)
str(blank_fc_ref)tibble [79 × 4] (S3: tbl_df/tbl/data.frame)
$ plate_id : chr [1:79] "b" "b" "b" "b" ...
$ time_hr : num [1:79] 0 0.5 1 1.5 2 2.5 3 3.5 4 0 ...
$ mean_blank_rfu: num [1:79] 148 157 164 168 171 174 175 175 176 145 ...
$ mean_blank_fc : num [1:79] 1.01 1.07 1.12 1.15 1.17 ...
7.3 Step 3 – Subtract blank fold-change from sample fold-change
samples <- dat_fc %>%
filter(!is_blank, !exclude_from_analysis) %>%
mutate(
trace_id = if_else(
!is.na(sample_id_group) & trimws(as.character(sample_id_group)) != "",
as.character(sample_id_group),
paste(plate_id, well_id, sep = "_")
)
) %>%
left_join(blank_fc_ref, by = c("plate_id", "time_hr")) %>%
mutate(corrected_fc = fold_change - mean_blank_fc)
str(samples)tibble [869 × 23] (S3: tbl_df/tbl/data.frame)
$ row_id : chr [1:869] "A" "A" "A" "A" ...
$ col_id : int [1:869] 1 2 3 4 1 2 3 4 1 2 ...
$ well_id : chr [1:869] "A1" "A2" "A3" "A4" ...
$ value : num [1:869] 188 158 173 184 181 173 175 200 171 182 ...
$ plate_id : chr [1:869] "b" "b" "b" "b" ...
$ time_hr : num [1:869] 0 0 0 0 0 0 0 0 0 0 ...
$ is_blank : logi [1:869] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_from_analysis: logi [1:869] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_reason : chr [1:869] NA NA NA NA ...
$ family_id_group : chr [1:869] "7" "1" "9" "3" ...
$ sample_id_group : chr [1:869] "1" "2" "3" "4" ...
$ treatment_group : chr [1:869] NA NA NA NA ...
$ weight_g_measurement : num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ width_mm_measurement : num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ length_mm_measurement: num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ area_mm2_measurement : num [1:869] 239.7 78.4 117.2 118.7 224.2 ...
$ sample_key : chr [1:869] "1" "2" "3" "4" ...
$ value_t0 : num [1:869] 188 158 173 184 181 173 175 200 171 182 ...
$ fold_change : num [1:869] 1 1 1 1 1 1 1 1 1 1 ...
$ trace_id : chr [1:869] "1" "2" "3" "4" ...
$ mean_blank_rfu : num [1:869] 148 148 148 148 148 148 148 148 148 148 ...
$ mean_blank_fc : num [1:869] 1.01 1.01 1.01 1.01 1.01 ...
$ corrected_fc : num [1:869] -0.00909 -0.00909 -0.00909 -0.00909 -0.00909 ...
8 Blank-Corrected Fold-Change
8.1 Mean by family
bc_fc_summary <- samples %>%
filter(!is.na(family_id_group), !exclude_from_analysis) %>%
group_by(family_id_group, treatment_group, time_hr) %>%
summarise(
mean_val = mean(corrected_fc, na.rm = TRUE),
se_val = sd(corrected_fc, na.rm = TRUE) /
sqrt(sum(!is.na(corrected_fc))),
n = sum(!is.na(corrected_fc)),
.groups = "drop"
) %>%
mutate(group_var = if (has_trt)
paste(family_id_group, treatment_group, sep = ".")
else
family_id_group)
p_bc_fc_mean <- ggplot(bc_fc_summary,
aes(x = time_hr, y = mean_val,
colour = family_id_group,
group = group_var)) +
geom_ribbon(aes(ymin = mean_val - se_val,
ymax = mean_val + se_val,
fill = family_id_group),
alpha = 0.15, colour = NA) +
geom_line(
mapping = if (has_trt) aes(linetype = treatment_group) else NULL,
linewidth = 1) +
geom_point(size = 2) +
scale_colour_manual(values = fam_colours, name = "Family",
breaks = families) +
scale_fill_manual(values = fam_colours, name = "Family",
breaks = families) +
labs(x = "Time (h)",
y = "Mean blank-corrected fold-change (± SE)") +
theme_classic(base_size = 13) +
if (has_trt) scale_linetype_manual(values = trt_linetypes, name = "Treatment") else NULL
p_bc_fc_mean
ggsave(file.path(fig_dir, "blank_corrected_fc_mean_by_family.png"),
p_bc_fc_mean, width = 8, height = 5)8.2 Individual traces by family
p_bc_fc_by_family <- samples %>%
filter(!is.na(family_id_group)) %>%
ggplot(aes(x = time_hr, y = corrected_fc, group = trace_id,
colour = .data[[if (has_trt) "treatment_group" else "family_id_group"]])) +
geom_line(alpha = 0.6) +
geom_point(size = 1.2, alpha = 0.7) +
facet_wrap(~ family_id_group) +
scale_colour_manual(
values = if (has_trt) trt_colours else fam_colours,
name = if (has_trt) "Treatment" else "Family",
breaks = if (has_trt) treatments else families) +
labs(x = "Time (h)", y = "Blank-corrected fold-change") +
theme_classic(base_size = 12) +
theme(strip.background = element_blank(),
strip.text = element_text(face = "bold"))
p_bc_fc_by_family
ggsave(file.path(fig_dir, "blank_corrected_fc_by_family.png"),
p_bc_fc_by_family, width = 10, height = 5)8.3 Individual blank-corrected fold-change traces by treatment
if (has_trt) {
p_bc_fc_by_treatment <- samples %>%
ggplot(aes(x = time_hr, y = corrected_fc,
group = trace_id, colour = family_id_group)) +
geom_line(alpha = 0.6) +
geom_point(size = 1.2, alpha = 0.7) +
facet_wrap(~ treatment_group) +
scale_colour_manual(values = fam_colours, name = "Family",
breaks = families) +
labs(x = "Time (h)", y = "Blank-corrected fold-change") +
theme_classic(base_size = 12) +
theme(strip.background = element_blank(),
strip.text = element_text(face = "bold"))
p_bc_fc_by_treatment
ggsave(file.path(fig_dir, "blank_corrected_fc_by_treatment.png"),
p_bc_fc_by_treatment, width = 10, height = 5)
}9 Metabolism (Size-Normalised Fold-Change)
Blank-corrected fold-change divided by each active measurement column. This is “metabolism” as defined in Huffmyer et al.
if (length(active_meas_cols) == 0) {
message("No active measurement columns: skipping metabolism calculation.")
metabolism_df <- tibble()
} else {
metabolism_df <- samples
for (mc in active_meas_cols) {
out_col <- paste0("metabolism_per_", mc)
metabolism_df <- metabolism_df %>%
mutate(!!out_col := if_else(
is.finite(.data[[mc]]) & .data[[mc]] > 0 &
is.finite(corrected_fc),
corrected_fc / .data[[mc]],
NA_real_
))
}
}
str(metabolism_df)tibble [869 × 24] (S3: tbl_df/tbl/data.frame)
$ row_id : chr [1:869] "A" "A" "A" "A" ...
$ col_id : int [1:869] 1 2 3 4 1 2 3 4 1 2 ...
$ well_id : chr [1:869] "A1" "A2" "A3" "A4" ...
$ value : num [1:869] 188 158 173 184 181 173 175 200 171 182 ...
$ plate_id : chr [1:869] "b" "b" "b" "b" ...
$ time_hr : num [1:869] 0 0 0 0 0 0 0 0 0 0 ...
$ is_blank : logi [1:869] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_from_analysis : logi [1:869] FALSE FALSE FALSE FALSE FALSE FALSE ...
$ exclude_reason : chr [1:869] NA NA NA NA ...
$ family_id_group : chr [1:869] "7" "1" "9" "3" ...
$ sample_id_group : chr [1:869] "1" "2" "3" "4" ...
$ treatment_group : chr [1:869] NA NA NA NA ...
$ weight_g_measurement : num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ width_mm_measurement : num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ length_mm_measurement : num [1:869] NA NA NA NA NA NA NA NA NA NA ...
$ area_mm2_measurement : num [1:869] 239.7 78.4 117.2 118.7 224.2 ...
$ sample_key : chr [1:869] "1" "2" "3" "4" ...
$ value_t0 : num [1:869] 188 158 173 184 181 173 175 200 171 182 ...
$ fold_change : num [1:869] 1 1 1 1 1 1 1 1 1 1 ...
$ trace_id : chr [1:869] "1" "2" "3" "4" ...
$ mean_blank_rfu : num [1:869] 148 148 148 148 148 148 148 148 148 148 ...
$ mean_blank_fc : num [1:869] 1.01 1.01 1.01 1.01 1.01 ...
$ corrected_fc : num [1:869] -0.00909 -0.00909 -0.00909 -0.00909 -0.00909 ...
$ metabolism_per_area_mm2_measurement: num [1:869] -3.79e-05 -1.16e-04 -7.76e-05 -7.66e-05 -4.05e-05 ...
9.1 Mean metabolism by family
if (nrow(metabolism_df) > 0) {
metab_cols <- paste0("metabolism_per_", active_meas_cols)
for (col in metab_cols) {
if (!col %in% names(metabolism_df)) next
mc_label <- str_remove(col, "^metabolism_per_")
metab_summary <- metabolism_df %>%
filter(!is.na(family_id_group), !exclude_from_analysis) %>%
group_by(family_id_group, treatment_group, time_hr) %>%
summarise(
mean_val = mean(.data[[col]], na.rm = TRUE),
se_val = sd(.data[[col]], na.rm = TRUE) /
sqrt(sum(!is.na(.data[[col]]))),
.groups = "drop"
) %>%
mutate(group_var = if (has_trt)
paste(family_id_group, treatment_group, sep = ".")
else
family_id_group)
p_metab_mean <- ggplot(metab_summary,
aes(x = time_hr, y = mean_val,
colour = family_id_group,
group = group_var)) +
geom_ribbon(aes(ymin = mean_val - se_val,
ymax = mean_val + se_val,
fill = family_id_group),
alpha = 0.15, colour = NA) +
geom_line(
mapping = if (has_trt) aes(linetype = treatment_group) else NULL,
linewidth = 1) +
geom_point(size = 2) +
scale_colour_manual(values = fam_colours, name = "Family",
breaks = families) +
scale_fill_manual(values = fam_colours, name = "Family",
breaks = families) +
labs(x = "Time (h)",
y = paste0(metabolism_y_label(col), " (± SE)")) +
theme_classic(base_size = 13) +
if (has_trt) scale_linetype_manual(values = trt_linetypes, name = "Treatment") else NULL
print(p_metab_mean)
ggsave(
file.path(fig_dir,
paste0("metabolism_mean_", mc_label, ".png")),
p_metab_mean, width = 8, height = 5)
}
}
9.2 Individual metabolism traces by family
if (nrow(metabolism_df) > 0) {
for (col in metab_cols) {
if (!col %in% names(metabolism_df)) next
mc_label <- str_remove(col, "^metabolism_per_")
p_metab_by_family <- metabolism_df %>%
filter(!is.na(family_id_group)) %>%
ggplot(aes(x = time_hr, y = .data[[col]], group = trace_id,
colour = .data[[if (has_trt) "treatment_group" else "family_id_group"]])) +
geom_line(alpha = 0.6) +
geom_point(size = 1.2, alpha = 0.7) +
facet_wrap(~ family_id_group) +
scale_colour_manual(
values = if (has_trt) trt_colours else fam_colours,
name = if (has_trt) "Treatment" else "Family",
breaks = if (has_trt) treatments else families) +
labs(x = "Time (h)", y = metabolism_y_label(col)) +
theme_classic(base_size = 12) +
theme(strip.background = element_blank(),
strip.text = element_text(face = "bold"))
print(p_metab_by_family)
ggsave(
file.path(fig_dir,
paste0("metabolism_individual_", mc_label, "_by_family.png")),
p_metab_by_family, width = 10, height = 5)
if (has_trt) {
p_metab_by_treatment <- ggplot(metabolism_df,
aes(x = time_hr, y = .data[[col]],
group = trace_id, colour = family_id_group)) +
geom_line(alpha = 0.6) +
geom_point(size = 1.2, alpha = 0.7) +
facet_wrap(~ treatment_group) +
scale_colour_manual(values = fam_colours, name = "Family",
breaks = families) +
labs(x = "Time (h)", y = metabolism_y_label(col)) +
theme_classic(base_size = 12) +
theme(strip.background = element_blank(),
strip.text = element_text(face = "bold"))
print(p_metab_by_treatment)
ggsave(
file.path(fig_dir,
paste0("metabolism_individual_", mc_label, "_by_treatment.png")),
p_metab_by_treatment, width = 10, height = 5)
}
}
}
10 Time-Series Statistical Analysis
Linear mixed effects models test the effect of experimental variables on metabolism over time. Individual (sample_id_group) is included as a random intercept to account for repeated measures across timepoints. Type III ANOVA with Satterthwaite’s approximation (lmerTest) assesses significance; post-hoc pairwise comparisons use estimated marginal means (emmeans, Tukey adjustment).
run_ts_stats <- function(df, value_col) {
has_family <- "family_id_group" %in% names(df) &&
length(unique(na.omit(df$family_id_group))) > 1
has_treatment <- "treatment_group" %in% names(df) &&
length(unique(na.omit(df$treatment_group))) > 1
if (!has_family && !has_treatment) return(NULL)
df <- df %>%
filter(is.finite(.data[[value_col]]), is.finite(time_hr)) %>%
mutate(
time_f = factor(time_hr),
individual = factor(trace_id)
)
if (nrow(df) == 0) return(NULL)
if (has_family) df <- df %>% mutate(family = factor(family_id_group))
if (has_treatment) df <- df %>% mutate(treatment = factor(treatment_group))
if (has_family && length(unique(na.omit(df$family))) < 2) return(NULL)
if (has_treatment && length(unique(na.omit(df$treatment))) < 2) return(NULL)
fixed <- if (has_family && has_treatment) {
paste0(value_col, " ~ time_f * family * treatment")
} else if (has_family) {
paste0(value_col, " ~ time_f * family")
} else {
paste0(value_col, " ~ time_f * treatment")
}
model <- lmer(
as.formula(paste0(fixed, " + (1 | individual)")),
data = df
)
anova_res <- anova(model, type = 3, ddf = "Satterthwaite")
# Pairwise comparisons of group combinations at each timepoint
emm_spec <- if (has_family && has_treatment) {
~ family * treatment | time_f
} else if (has_family) {
~ family | time_f
} else {
~ treatment | time_f
}
emm <- emmeans(model, emm_spec)
pairs_res <- as.data.frame(pairs(emm, adjust = "tukey"))
# Main-effect marginal means (collapsed across time)
emm_main <- if (has_family && has_treatment) {
emmeans(model, ~ family * treatment)
} else if (has_family) {
emmeans(model, ~ family)
} else {
emmeans(model, ~ treatment)
}
pairs_main <- as.data.frame(pairs(emm_main, adjust = "tukey"))
list(
model = model,
anova = anova_res,
pairs_by_time = pairs_res,
pairs_main = pairs_main,
has_family = has_family,
has_treatment = has_treatment
)
}
ts_stats <- list()
if (nrow(metabolism_df) > 0) {
for (mc in active_meas_cols) {
col <- paste0("metabolism_per_", mc)
if (col %in% names(metabolism_df))
ts_stats[[col]] <- run_ts_stats(metabolism_df, col)
}
}10.1 Results
for (col in names(ts_stats)) {
res <- ts_stats[[col]]
if (is.null(res)) next
cat("\n\n### Metric:", col, "\n\n")
cat("**Type III ANOVA (Satterthwaite approximation):**\n\n")
cat(knitr::kable(as.data.frame(res$anova), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
cat("**Marginal means – main effects (collapsed across time):**\n\n")
cat(knitr::kable(as.data.frame(res$pairs_main), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
cat("**Pairwise comparisons by timepoint (Tukey):**\n\n")
cat(knitr::kable(as.data.frame(res$pairs_by_time), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
}10.1.1 Metric: metabolism_per_area_mm2_measurement
Type III ANOVA (Satterthwaite approximation):
| Sum Sq | Mean Sq | NumDF | DenDF | F value | Pr(>F) | |
|---|---|---|---|---|---|---|
| time_f | 3.3605 | 0.4201 | 8 | 698.2986 | 312.3834 | 0.0000 |
| family | 0.0181 | 0.0023 | 8 | 90.0142 | 1.6823 | 0.1135 |
| time_f:family | 0.2015 | 0.0031 | 64 | 698.2947 | 2.3417 | 0.0000 |
Marginal means – main effects (collapsed across time):
| contrast | estimate | SE | df | t.ratio | p.value |
|---|---|---|---|---|---|
| 1 - 10 | 0.0186 | 0.0216 | 90.0923 | 0.8620 | 0.9943 |
| 1 - 3 | 0.0157 | 0.0215 | 90.0146 | 0.7281 | 0.9982 |
| 1 - 5 | 0.0447 | 0.0216 | 90.0923 | 2.0720 | 0.4987 |
| 1 - 6 | 0.0142 | 0.0215 | 90.0146 | 0.6583 | 0.9992 |
| 1 - 7 | 0.0101 | 0.0215 | 90.0146 | 0.4703 | 0.9999 |
| 1 - 8 | 0.0319 | 0.0215 | 90.0146 | 1.4785 | 0.8625 |
| 1 - 9 | 0.0586 | 0.0216 | 90.0923 | 2.7179 | 0.1564 |
| 1 - 9b | 0.0474 | 0.0215 | 90.0146 | 2.1999 | 0.4147 |
| 10 - 3 | -0.0029 | 0.0215 | 90.0146 | -0.1341 | 1.0000 |
| 10 - 5 | 0.0261 | 0.0216 | 90.0923 | 1.2100 | 0.9523 |
| 10 - 6 | -0.0044 | 0.0215 | 90.0146 | -0.2039 | 1.0000 |
| 10 - 7 | -0.0084 | 0.0215 | 90.0146 | -0.3920 | 1.0000 |
| 10 - 8 | 0.0133 | 0.0215 | 90.0146 | 0.6163 | 0.9995 |
| 10 - 9 | 0.0400 | 0.0216 | 90.0923 | 1.8559 | 0.6452 |
| 10 - 9b | 0.0288 | 0.0215 | 90.0146 | 1.3377 | 0.9171 |
| 3 - 5 | 0.0290 | 0.0215 | 90.0146 | 1.3444 | 0.9149 |
| 3 - 6 | -0.0015 | 0.0215 | 89.9370 | -0.0698 | 1.0000 |
| 3 - 7 | -0.0056 | 0.0215 | 89.9370 | -0.2579 | 1.0000 |
| 3 - 8 | 0.0162 | 0.0215 | 89.9370 | 0.7505 | 0.9978 |
| 3 - 9 | 0.0429 | 0.0215 | 90.0146 | 1.9904 | 0.5540 |
| 3 - 9b | 0.0317 | 0.0215 | 89.9370 | 1.4721 | 0.8653 |
| 5 - 6 | -0.0305 | 0.0215 | 90.0146 | -1.4142 | 0.8895 |
| 5 - 7 | -0.0345 | 0.0215 | 90.0146 | -1.6022 | 0.8011 |
| 5 - 8 | -0.0128 | 0.0215 | 90.0146 | -0.5940 | 0.9996 |
| 5 - 9 | 0.0139 | 0.0216 | 90.0923 | 0.6459 | 0.9993 |
| 5 - 9b | 0.0027 | 0.0215 | 90.0146 | 0.1274 | 1.0000 |
| 6 - 7 | -0.0041 | 0.0215 | 89.9370 | -0.1881 | 1.0000 |
| 6 - 8 | 0.0177 | 0.0215 | 89.9370 | 0.8204 | 0.9960 |
| 6 - 9 | 0.0444 | 0.0215 | 90.0146 | 2.0602 | 0.5066 |
| 6 - 9b | 0.0332 | 0.0215 | 89.9370 | 1.5419 | 0.8325 |
| 7 - 8 | 0.0217 | 0.0215 | 89.9370 | 1.0085 | 0.9842 |
| 7 - 9 | 0.0484 | 0.0215 | 90.0146 | 2.2482 | 0.3845 |
| 7 - 9b | 0.0373 | 0.0215 | 89.9370 | 1.7300 | 0.7265 |
| 8 - 9 | 0.0267 | 0.0215 | 90.0146 | 1.2400 | 0.9452 |
| 8 - 9b | 0.0155 | 0.0215 | 89.9370 | 0.7216 | 0.9984 |
| 9 - 9b | -0.0112 | 0.0215 | 90.0146 | -0.5186 | 0.9999 |
Pairwise comparisons by timepoint (Tukey):
| contrast | time_f | estimate | SE | df | t.ratio | p.value |
|---|---|---|---|---|---|---|
| 1 - 10 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0015 | 1.0000 |
| 1 - 3 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0003 | 1.0000 |
| 1 - 5 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0005 | 1.0000 |
| 1 - 6 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0009 | 1.0000 |
| 1 - 7 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0004 | 1.0000 |
| 1 - 8 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0008 | 1.0000 |
| 1 - 9 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0019 | 1.0000 |
| 1 - 9b | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0010 | 1.0000 |
| 10 - 3 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0011 | 1.0000 |
| 10 - 5 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0010 | 1.0000 |
| 10 - 6 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0006 | 1.0000 |
| 10 - 7 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0011 | 1.0000 |
| 10 - 8 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0007 | 1.0000 |
| 10 - 9 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0004 | 1.0000 |
| 10 - 9b | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0005 | 1.0000 |
| 3 - 5 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0002 | 1.0000 |
| 3 - 6 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0005 | 1.0000 |
| 3 - 7 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0000 | 1.0000 |
| 3 - 8 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0004 | 1.0000 |
| 3 - 9 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0015 | 1.0000 |
| 3 - 9b | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0006 | 1.0000 |
| 5 - 6 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0004 | 1.0000 |
| 5 - 7 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0002 | 1.0000 |
| 5 - 8 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0002 | 1.0000 |
| 5 - 9 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0013 | 1.0000 |
| 5 - 9b | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0005 | 1.0000 |
| 6 - 7 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0005 | 1.0000 |
| 6 - 8 | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0001 | 1.0000 |
| 6 - 9 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0010 | 1.0000 |
| 6 - 9b | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0001 | 1.0000 |
| 7 - 8 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0004 | 1.0000 |
| 7 - 9 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0015 | 1.0000 |
| 7 - 9b | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0006 | 1.0000 |
| 8 - 9 | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0011 | 1.0000 |
| 8 - 9b | 0 | 0.0000 | 0.0261 | 188.2462 | -0.0002 | 1.0000 |
| 9 - 9b | 0 | 0.0000 | 0.0261 | 188.2462 | 0.0009 | 1.0000 |
| 1 - 10 | 0.5 | -0.0034 | 0.0269 | 210.5352 | -0.1276 | 1.0000 |
| 1 - 3 | 0.5 | -0.0090 | 0.0266 | 202.0187 | -0.3388 | 1.0000 |
| 1 - 5 | 0.5 | 0.0019 | 0.0266 | 202.0196 | 0.0714 | 1.0000 |
| 1 - 6 | 0.5 | -0.0138 | 0.0266 | 202.0187 | -0.5183 | 0.9999 |
| 1 - 7 | 0.5 | -0.0006 | 0.0266 | 202.0187 | -0.0230 | 1.0000 |
| 1 - 8 | 0.5 | 0.0041 | 0.0266 | 202.0187 | 0.1540 | 1.0000 |
| 1 - 9 | 0.5 | 0.0045 | 0.0269 | 210.5352 | 0.1664 | 1.0000 |
| 1 - 9b | 0.5 | -0.0022 | 0.0266 | 202.0187 | -0.0837 | 1.0000 |
| 10 - 3 | 0.5 | -0.0056 | 0.0269 | 210.5343 | -0.2072 | 1.0000 |
| 10 - 5 | 0.5 | 0.0053 | 0.0269 | 210.5352 | 0.1981 | 1.0000 |
| 10 - 6 | 0.5 | -0.0104 | 0.0269 | 210.5343 | -0.3847 | 1.0000 |
| 10 - 7 | 0.5 | 0.0028 | 0.0269 | 210.5343 | 0.1049 | 1.0000 |
| 10 - 8 | 0.5 | 0.0075 | 0.0269 | 210.5343 | 0.2798 | 1.0000 |
| 10 - 9 | 0.5 | 0.0079 | 0.0272 | 219.1144 | 0.2907 | 1.0000 |
| 10 - 9b | 0.5 | 0.0012 | 0.0269 | 210.5343 | 0.0449 | 1.0000 |
| 3 - 5 | 0.5 | 0.0109 | 0.0266 | 202.0187 | 0.4101 | 1.0000 |
| 3 - 6 | 0.5 | -0.0048 | 0.0266 | 202.0178 | -0.1796 | 1.0000 |
| 3 - 7 | 0.5 | 0.0084 | 0.0266 | 202.0178 | 0.3158 | 1.0000 |
| 3 - 8 | 0.5 | 0.0131 | 0.0266 | 202.0178 | 0.4927 | 0.9999 |
| 3 - 9 | 0.5 | 0.0135 | 0.0269 | 210.5343 | 0.5013 | 0.9999 |
| 3 - 9b | 0.5 | 0.0068 | 0.0266 | 202.0178 | 0.2551 | 1.0000 |
| 5 - 6 | 0.5 | -0.0157 | 0.0266 | 202.0187 | -0.5897 | 0.9996 |
| 5 - 7 | 0.5 | -0.0025 | 0.0266 | 202.0187 | -0.0944 | 1.0000 |
| 5 - 8 | 0.5 | 0.0022 | 0.0266 | 202.0187 | 0.0826 | 1.0000 |
| 5 - 9 | 0.5 | 0.0026 | 0.0269 | 210.5352 | 0.0959 | 1.0000 |
| 5 - 9b | 0.5 | -0.0041 | 0.0266 | 202.0187 | -0.1550 | 1.0000 |
| 6 - 7 | 0.5 | 0.0132 | 0.0266 | 202.0178 | 0.4953 | 0.9999 |
| 6 - 8 | 0.5 | 0.0179 | 0.0266 | 202.0178 | 0.6723 | 0.9991 |
| 6 - 9 | 0.5 | 0.0183 | 0.0269 | 210.5343 | 0.6787 | 0.9990 |
| 6 - 9b | 0.5 | 0.0116 | 0.0266 | 202.0178 | 0.4346 | 1.0000 |
| 7 - 8 | 0.5 | 0.0047 | 0.0266 | 202.0178 | 0.1770 | 1.0000 |
| 7 - 9 | 0.5 | 0.0051 | 0.0269 | 210.5343 | 0.1892 | 1.0000 |
| 7 - 9b | 0.5 | -0.0016 | 0.0266 | 202.0178 | -0.0607 | 1.0000 |
| 8 - 9 | 0.5 | 0.0004 | 0.0269 | 210.5343 | 0.0142 | 1.0000 |
| 8 - 9b | 0.5 | -0.0063 | 0.0266 | 202.0178 | -0.2377 | 1.0000 |
| 9 - 9b | 0.5 | -0.0067 | 0.0269 | 210.5343 | -0.2491 | 1.0000 |
| 1 - 10 | 1 | 0.0120 | 0.0261 | 188.2462 | 0.4591 | 0.9999 |
| 1 - 3 | 1 | -0.0174 | 0.0261 | 188.2462 | -0.6666 | 0.9991 |
| 1 - 5 | 1 | -0.0040 | 0.0261 | 188.2462 | -0.1531 | 1.0000 |
| 1 - 6 | 1 | -0.0353 | 0.0261 | 188.2462 | -1.3527 | 0.9136 |
| 1 - 7 | 1 | -0.0060 | 0.0261 | 188.2462 | -0.2299 | 1.0000 |
| 1 - 8 | 1 | -0.0127 | 0.0261 | 188.2462 | -0.4882 | 0.9999 |
| 1 - 9 | 1 | -0.0084 | 0.0261 | 188.2462 | -0.3231 | 1.0000 |
| 1 - 9b | 1 | 0.0057 | 0.0261 | 188.2462 | 0.2186 | 1.0000 |
| 10 - 3 | 1 | -0.0294 | 0.0261 | 188.2462 | -1.1258 | 0.9696 |
| 10 - 5 | 1 | -0.0160 | 0.0261 | 188.2462 | -0.6122 | 0.9995 |
| 10 - 6 | 1 | -0.0473 | 0.0261 | 188.2462 | -1.8119 | 0.6743 |
| 10 - 7 | 1 | -0.0180 | 0.0261 | 188.2462 | -0.6890 | 0.9989 |
| 10 - 8 | 1 | -0.0247 | 0.0261 | 188.2462 | -0.9474 | 0.9898 |
| 10 - 9 | 1 | -0.0204 | 0.0261 | 188.2462 | -0.7823 | 0.9972 |
| 10 - 9b | 1 | -0.0063 | 0.0261 | 188.2462 | -0.2405 | 1.0000 |
| 3 - 5 | 1 | 0.0134 | 0.0261 | 188.2462 | 0.5136 | 0.9999 |
| 3 - 6 | 1 | -0.0179 | 0.0261 | 188.2462 | -0.6861 | 0.9989 |
| 3 - 7 | 1 | 0.0114 | 0.0261 | 188.2462 | 0.4368 | 1.0000 |
| 3 - 8 | 1 | 0.0047 | 0.0261 | 188.2462 | 0.1784 | 1.0000 |
| 3 - 9 | 1 | 0.0090 | 0.0261 | 188.2462 | 0.3435 | 1.0000 |
| 3 - 9b | 1 | 0.0231 | 0.0261 | 188.2462 | 0.8853 | 0.9935 |
| 5 - 6 | 1 | -0.0313 | 0.0261 | 188.2462 | -1.1997 | 0.9557 |
| 5 - 7 | 1 | -0.0020 | 0.0261 | 188.2462 | -0.0768 | 1.0000 |
| 5 - 8 | 1 | -0.0087 | 0.0261 | 188.2462 | -0.3351 | 1.0000 |
| 5 - 9 | 1 | -0.0044 | 0.0261 | 188.2462 | -0.1701 | 1.0000 |
| 5 - 9b | 1 | 0.0097 | 0.0261 | 188.2462 | 0.3717 | 1.0000 |
| 6 - 7 | 1 | 0.0293 | 0.0261 | 188.2462 | 1.1229 | 0.9701 |
| 6 - 8 | 1 | 0.0226 | 0.0261 | 188.2462 | 0.8645 | 0.9945 |
| 6 - 9 | 1 | 0.0269 | 0.0261 | 188.2462 | 1.0296 | 0.9825 |
| 6 - 9b | 1 | 0.0410 | 0.0261 | 188.2462 | 1.5714 | 0.8191 |
| 7 - 8 | 1 | -0.0067 | 0.0261 | 188.2462 | -0.2584 | 1.0000 |
| 7 - 9 | 1 | -0.0024 | 0.0261 | 188.2462 | -0.0933 | 1.0000 |
| 7 - 9b | 1 | 0.0117 | 0.0261 | 188.2462 | 0.4485 | 1.0000 |
| 8 - 9 | 1 | 0.0043 | 0.0261 | 188.2462 | 0.1651 | 1.0000 |
| 8 - 9b | 1 | 0.0184 | 0.0261 | 188.2462 | 0.7069 | 0.9987 |
| 9 - 9b | 1 | 0.0141 | 0.0261 | 188.2462 | 0.5418 | 0.9998 |
| 1 - 10 | 1.5 | 0.0280 | 0.0261 | 188.2462 | 1.0743 | 0.9772 |
| 1 - 3 | 1.5 | 0.0066 | 0.0261 | 188.2462 | 0.2516 | 1.0000 |
| 1 - 5 | 1.5 | 0.0345 | 0.0261 | 188.2462 | 1.3231 | 0.9233 |
| 1 - 6 | 1.5 | 0.0103 | 0.0261 | 188.2462 | 0.3937 | 1.0000 |
| 1 - 7 | 1.5 | 0.0143 | 0.0261 | 188.2462 | 0.5484 | 0.9998 |
| 1 - 8 | 1.5 | 0.0282 | 0.0261 | 188.2462 | 1.0814 | 0.9763 |
| 1 - 9 | 1.5 | 0.0371 | 0.0261 | 188.2462 | 1.4213 | 0.8884 |
| 1 - 9b | 1.5 | 0.0442 | 0.0261 | 188.2462 | 1.6942 | 0.7494 |
| 10 - 3 | 1.5 | -0.0215 | 0.0261 | 188.2462 | -0.8227 | 0.9961 |
| 10 - 5 | 1.5 | 0.0065 | 0.0261 | 188.2462 | 0.2488 | 1.0000 |
| 10 - 6 | 1.5 | -0.0178 | 0.0261 | 188.2462 | -0.6806 | 0.9990 |
| 10 - 7 | 1.5 | -0.0137 | 0.0261 | 188.2462 | -0.5259 | 0.9998 |
| 10 - 8 | 1.5 | 0.0002 | 0.0261 | 188.2462 | 0.0071 | 1.0000 |
| 10 - 9 | 1.5 | 0.0091 | 0.0261 | 188.2462 | 0.3470 | 1.0000 |
| 10 - 9b | 1.5 | 0.0162 | 0.0261 | 188.2462 | 0.6199 | 0.9995 |
| 3 - 5 | 1.5 | 0.0280 | 0.0261 | 188.2462 | 1.0715 | 0.9776 |
| 3 - 6 | 1.5 | 0.0037 | 0.0261 | 188.2462 | 0.1422 | 1.0000 |
| 3 - 7 | 1.5 | 0.0077 | 0.0261 | 188.2462 | 0.2968 | 1.0000 |
| 3 - 8 | 1.5 | 0.0217 | 0.0261 | 188.2462 | 0.8298 | 0.9958 |
| 3 - 9 | 1.5 | 0.0305 | 0.0261 | 188.2462 | 1.1697 | 0.9618 |
| 3 - 9b | 1.5 | 0.0376 | 0.0261 | 188.2462 | 1.4426 | 0.8797 |
| 5 - 6 | 1.5 | -0.0242 | 0.0261 | 188.2462 | -0.9294 | 0.9910 |
| 5 - 7 | 1.5 | -0.0202 | 0.0261 | 188.2462 | -0.7747 | 0.9974 |
| 5 - 8 | 1.5 | -0.0063 | 0.0261 | 188.2462 | -0.2417 | 1.0000 |
| 5 - 9 | 1.5 | 0.0026 | 0.0261 | 188.2462 | 0.0982 | 1.0000 |
| 5 - 9b | 1.5 | 0.0097 | 0.0261 | 188.2462 | 0.3711 | 1.0000 |
| 6 - 7 | 1.5 | 0.0040 | 0.0261 | 188.2462 | 0.1547 | 1.0000 |
| 6 - 8 | 1.5 | 0.0179 | 0.0261 | 188.2462 | 0.6877 | 0.9989 |
| 6 - 9 | 1.5 | 0.0268 | 0.0261 | 188.2462 | 1.0275 | 0.9828 |
| 6 - 9b | 1.5 | 0.0339 | 0.0261 | 188.2462 | 1.3004 | 0.9302 |
| 7 - 8 | 1.5 | 0.0139 | 0.0261 | 188.2462 | 0.5330 | 0.9998 |
| 7 - 9 | 1.5 | 0.0228 | 0.0261 | 188.2462 | 0.8729 | 0.9941 |
| 7 - 9b | 1.5 | 0.0299 | 0.0261 | 188.2462 | 1.1458 | 0.9663 |
| 8 - 9 | 1.5 | 0.0089 | 0.0261 | 188.2462 | 0.3399 | 1.0000 |
| 8 - 9b | 1.5 | 0.0160 | 0.0261 | 188.2462 | 0.6128 | 0.9995 |
| 9 - 9b | 1.5 | 0.0071 | 0.0261 | 188.2462 | 0.2729 | 1.0000 |
| 1 - 10 | 2 | 0.0225 | 0.0261 | 188.2462 | 0.8626 | 0.9946 |
| 1 - 3 | 2 | 0.0177 | 0.0261 | 188.2462 | 0.6793 | 0.9990 |
| 1 - 5 | 2 | 0.0536 | 0.0261 | 188.2462 | 2.0536 | 0.5082 |
| 1 - 6 | 2 | 0.0291 | 0.0261 | 188.2462 | 1.1161 | 0.9712 |
| 1 - 7 | 2 | 0.0183 | 0.0261 | 188.2462 | 0.7012 | 0.9987 |
| 1 - 8 | 2 | 0.0443 | 0.0261 | 188.2462 | 1.6969 | 0.7478 |
| 1 - 9 | 2 | 0.0651 | 0.0261 | 188.2462 | 2.4942 | 0.2415 |
| 1 - 9b | 2 | 0.0718 | 0.0261 | 188.2462 | 2.7526 | 0.1370 |
| 10 - 3 | 2 | -0.0048 | 0.0261 | 188.2462 | -0.1833 | 1.0000 |
| 10 - 5 | 2 | 0.0311 | 0.0261 | 188.2462 | 1.1910 | 0.9576 |
| 10 - 6 | 2 | 0.0066 | 0.0261 | 188.2462 | 0.2535 | 1.0000 |
| 10 - 7 | 2 | -0.0042 | 0.0261 | 188.2462 | -0.1614 | 1.0000 |
| 10 - 8 | 2 | 0.0218 | 0.0261 | 188.2462 | 0.8343 | 0.9957 |
| 10 - 9 | 2 | 0.0426 | 0.0261 | 188.2462 | 1.6315 | 0.7863 |
| 10 - 9b | 2 | 0.0493 | 0.0261 | 188.2462 | 1.8900 | 0.6215 |
| 3 - 5 | 2 | 0.0359 | 0.0261 | 188.2462 | 1.3743 | 0.9062 |
| 3 - 6 | 2 | 0.0114 | 0.0261 | 188.2462 | 0.4368 | 1.0000 |
| 3 - 7 | 2 | 0.0006 | 0.0261 | 188.2462 | 0.0219 | 1.0000 |
| 3 - 8 | 2 | 0.0265 | 0.0261 | 188.2462 | 1.0176 | 0.9838 |
| 3 - 9 | 2 | 0.0474 | 0.0261 | 188.2462 | 1.8148 | 0.6723 |
| 3 - 9b | 2 | 0.0541 | 0.0261 | 188.2462 | 2.0732 | 0.4948 |
| 5 - 6 | 2 | -0.0245 | 0.0261 | 188.2462 | -0.9375 | 0.9905 |
| 5 - 7 | 2 | -0.0353 | 0.0261 | 188.2462 | -1.3524 | 0.9138 |
| 5 - 8 | 2 | -0.0093 | 0.0261 | 188.2462 | -0.3567 | 1.0000 |
| 5 - 9 | 2 | 0.0115 | 0.0261 | 188.2462 | 0.4406 | 1.0000 |
| 5 - 9b | 2 | 0.0182 | 0.0261 | 188.2462 | 0.6990 | 0.9988 |
| 6 - 7 | 2 | -0.0108 | 0.0261 | 188.2462 | -0.4149 | 1.0000 |
| 6 - 8 | 2 | 0.0152 | 0.0261 | 188.2462 | 0.5808 | 0.9997 |
| 6 - 9 | 2 | 0.0360 | 0.0261 | 188.2462 | 1.3781 | 0.9048 |
| 6 - 9b | 2 | 0.0427 | 0.0261 | 188.2462 | 1.6365 | 0.7835 |
| 7 - 8 | 2 | 0.0260 | 0.0261 | 188.2462 | 0.9957 | 0.9859 |
| 7 - 9 | 2 | 0.0468 | 0.0261 | 188.2462 | 1.7930 | 0.6868 |
| 7 - 9b | 2 | 0.0535 | 0.0261 | 188.2462 | 2.0514 | 0.5098 |
| 8 - 9 | 2 | 0.0208 | 0.0261 | 188.2462 | 0.7972 | 0.9968 |
| 8 - 9b | 2 | 0.0275 | 0.0261 | 188.2462 | 1.0556 | 0.9796 |
| 9 - 9b | 2 | 0.0067 | 0.0261 | 188.2462 | 0.2584 | 1.0000 |
| 1 - 10 | 2.5 | 0.0118 | 0.0261 | 188.2462 | 0.4509 | 1.0000 |
| 1 - 3 | 2.5 | 0.0248 | 0.0261 | 188.2462 | 0.9502 | 0.9896 |
| 1 - 5 | 2.5 | 0.0655 | 0.0261 | 188.2462 | 2.5123 | 0.2328 |
| 1 - 6 | 2.5 | 0.0339 | 0.0261 | 188.2462 | 1.2981 | 0.9309 |
| 1 - 7 | 2.5 | 0.0064 | 0.0261 | 188.2462 | 0.2466 | 1.0000 |
| 1 - 8 | 2.5 | 0.0479 | 0.0261 | 188.2462 | 1.8344 | 0.6593 |
| 1 - 9 | 2.5 | 0.0858 | 0.0261 | 188.2462 | 3.2887 | 0.0321 |
| 1 - 9b | 2.5 | 0.0784 | 0.0261 | 188.2462 | 3.0064 | 0.0721 |
| 10 - 3 | 2.5 | 0.0130 | 0.0261 | 188.2462 | 0.4993 | 0.9999 |
| 10 - 5 | 2.5 | 0.0538 | 0.0261 | 188.2462 | 2.0614 | 0.5029 |
| 10 - 6 | 2.5 | 0.0221 | 0.0261 | 188.2462 | 0.8471 | 0.9952 |
| 10 - 7 | 2.5 | -0.0053 | 0.0261 | 188.2462 | -0.2043 | 1.0000 |
| 10 - 8 | 2.5 | 0.0361 | 0.0261 | 188.2462 | 1.3835 | 0.9028 |
| 10 - 9 | 2.5 | 0.0740 | 0.0261 | 188.2462 | 2.8378 | 0.1115 |
| 10 - 9b | 2.5 | 0.0667 | 0.0261 | 188.2462 | 2.5554 | 0.2130 |
| 3 - 5 | 2.5 | 0.0408 | 0.0261 | 188.2462 | 1.5621 | 0.8239 |
| 3 - 6 | 2.5 | 0.0091 | 0.0261 | 188.2462 | 0.3478 | 1.0000 |
| 3 - 7 | 2.5 | -0.0184 | 0.0261 | 188.2462 | -0.7036 | 0.9987 |
| 3 - 8 | 2.5 | 0.0231 | 0.0261 | 188.2462 | 0.8842 | 0.9936 |
| 3 - 9 | 2.5 | 0.0610 | 0.0261 | 188.2462 | 2.3385 | 0.3247 |
| 3 - 9b | 2.5 | 0.0536 | 0.0261 | 188.2462 | 2.0561 | 0.5065 |
| 5 - 6 | 2.5 | -0.0317 | 0.0261 | 188.2462 | -1.2142 | 0.9525 |
| 5 - 7 | 2.5 | -0.0591 | 0.0261 | 188.2462 | -2.2657 | 0.3684 |
| 5 - 8 | 2.5 | -0.0177 | 0.0261 | 188.2462 | -0.6779 | 0.9990 |
| 5 - 9 | 2.5 | 0.0203 | 0.0261 | 188.2462 | 0.7764 | 0.9974 |
| 5 - 9b | 2.5 | 0.0129 | 0.0261 | 188.2462 | 0.4941 | 0.9999 |
| 6 - 7 | 2.5 | -0.0274 | 0.0261 | 188.2462 | -1.0515 | 0.9801 |
| 6 - 8 | 2.5 | 0.0140 | 0.0261 | 188.2462 | 0.5363 | 0.9998 |
| 6 - 9 | 2.5 | 0.0519 | 0.0261 | 188.2462 | 1.9906 | 0.5518 |
| 6 - 9b | 2.5 | 0.0446 | 0.0261 | 188.2462 | 1.7083 | 0.7408 |
| 7 - 8 | 2.5 | 0.0414 | 0.0261 | 188.2462 | 1.5878 | 0.8104 |
| 7 - 9 | 2.5 | 0.0794 | 0.0261 | 188.2462 | 3.0421 | 0.0654 |
| 7 - 9b | 2.5 | 0.0720 | 0.0261 | 188.2462 | 2.7598 | 0.1347 |
| 8 - 9 | 2.5 | 0.0379 | 0.0261 | 188.2462 | 1.4543 | 0.8748 |
| 8 - 9b | 2.5 | 0.0306 | 0.0261 | 188.2462 | 1.1720 | 0.9614 |
| 9 - 9b | 2.5 | -0.0074 | 0.0261 | 188.2462 | -0.2823 | 1.0000 |
| 1 - 10 | 3 | 0.0315 | 0.0269 | 210.5352 | 1.1696 | 0.9620 |
| 1 - 3 | 3 | 0.0381 | 0.0269 | 210.5343 | 1.4140 | 0.8914 |
| 1 - 5 | 3 | 0.0841 | 0.0272 | 219.1144 | 3.0883 | 0.0568 |
| 1 - 6 | 3 | 0.0399 | 0.0269 | 210.5343 | 1.4808 | 0.8633 |
| 1 - 7 | 3 | 0.0241 | 0.0269 | 210.5343 | 0.8934 | 0.9931 |
| 1 - 8 | 3 | 0.0643 | 0.0269 | 210.5343 | 2.3901 | 0.2949 |
| 1 - 9 | 3 | 0.1137 | 0.0269 | 210.5352 | 4.2235 | 0.0012 |
| 1 - 9b | 3 | 0.0892 | 0.0269 | 210.5343 | 3.3137 | 0.0294 |
| 10 - 3 | 3 | 0.0066 | 0.0266 | 202.0187 | 0.2473 | 1.0000 |
| 10 - 5 | 3 | 0.0526 | 0.0269 | 210.5352 | 1.9541 | 0.5771 |
| 10 - 6 | 3 | 0.0084 | 0.0266 | 202.0187 | 0.3149 | 1.0000 |
| 10 - 7 | 3 | -0.0074 | 0.0266 | 202.0187 | -0.2795 | 1.0000 |
| 10 - 8 | 3 | 0.0329 | 0.0266 | 202.0187 | 1.2348 | 0.9478 |
| 10 - 9 | 3 | 0.0822 | 0.0266 | 202.0196 | 3.0897 | 0.0570 |
| 10 - 9b | 3 | 0.0577 | 0.0266 | 202.0187 | 2.1692 | 0.4298 |
| 3 - 5 | 3 | 0.0460 | 0.0269 | 210.5343 | 1.7097 | 0.7400 |
| 3 - 6 | 3 | 0.0018 | 0.0266 | 202.0178 | 0.0676 | 1.0000 |
| 3 - 7 | 3 | -0.0140 | 0.0266 | 202.0178 | -0.5267 | 0.9998 |
| 3 - 8 | 3 | 0.0263 | 0.0266 | 202.0178 | 0.9875 | 0.9867 |
| 3 - 9 | 3 | 0.0756 | 0.0266 | 202.0187 | 2.8424 | 0.1097 |
| 3 - 9b | 3 | 0.0511 | 0.0266 | 202.0178 | 1.9220 | 0.5994 |
| 5 - 6 | 3 | -0.0442 | 0.0269 | 210.5343 | -1.6429 | 0.7799 |
| 5 - 7 | 3 | -0.0600 | 0.0269 | 210.5343 | -2.2303 | 0.3901 |
| 5 - 8 | 3 | -0.0198 | 0.0269 | 210.5343 | -0.7336 | 0.9982 |
| 5 - 9 | 3 | 0.0296 | 0.0269 | 210.5352 | 1.0998 | 0.9738 |
| 5 - 9b | 3 | 0.0051 | 0.0269 | 210.5343 | 0.1900 | 1.0000 |
| 6 - 7 | 3 | -0.0158 | 0.0266 | 202.0178 | -0.5943 | 0.9996 |
| 6 - 8 | 3 | 0.0245 | 0.0266 | 202.0178 | 0.9199 | 0.9916 |
| 6 - 9 | 3 | 0.0738 | 0.0266 | 202.0187 | 2.7748 | 0.1295 |
| 6 - 9b | 3 | 0.0493 | 0.0266 | 202.0178 | 1.8544 | 0.6458 |
| 7 - 8 | 3 | 0.0403 | 0.0266 | 202.0178 | 1.5142 | 0.8478 |
| 7 - 9 | 3 | 0.0896 | 0.0266 | 202.0187 | 3.3691 | 0.0249 |
| 7 - 9b | 3 | 0.0652 | 0.0266 | 202.0178 | 2.4487 | 0.2639 |
| 8 - 9 | 3 | 0.0494 | 0.0266 | 202.0187 | 1.8549 | 0.6454 |
| 8 - 9b | 3 | 0.0249 | 0.0266 | 202.0178 | 0.9345 | 0.9907 |
| 9 - 9b | 3 | -0.0245 | 0.0266 | 202.0187 | -0.9204 | 0.9916 |
| 1 - 10 | 3.5 | 0.0294 | 0.0261 | 188.2462 | 1.1269 | 0.9695 |
| 1 - 3 | 3.5 | 0.0388 | 0.0261 | 188.2462 | 1.4857 | 0.8610 |
| 1 - 5 | 3.5 | 0.0832 | 0.0261 | 188.2462 | 3.1886 | 0.0433 |
| 1 - 6 | 3.5 | 0.0315 | 0.0261 | 188.2462 | 1.2068 | 0.9542 |
| 1 - 7 | 3.5 | 0.0153 | 0.0261 | 188.2462 | 0.5869 | 0.9997 |
| 1 - 8 | 3.5 | 0.0544 | 0.0261 | 188.2462 | 2.0866 | 0.4856 |
| 1 - 9 | 3.5 | 0.1149 | 0.0261 | 188.2462 | 4.4022 | 0.0006 |
| 1 - 9b | 3.5 | 0.0731 | 0.0261 | 188.2462 | 2.8004 | 0.1222 |
| 10 - 3 | 3.5 | 0.0094 | 0.0261 | 188.2462 | 0.3588 | 1.0000 |
| 10 - 5 | 3.5 | 0.0538 | 0.0261 | 188.2462 | 2.0617 | 0.5027 |
| 10 - 6 | 3.5 | 0.0021 | 0.0261 | 188.2462 | 0.0799 | 1.0000 |
| 10 - 7 | 3.5 | -0.0141 | 0.0261 | 188.2462 | -0.5400 | 0.9998 |
| 10 - 8 | 3.5 | 0.0250 | 0.0261 | 188.2462 | 0.9597 | 0.9889 |
| 10 - 9 | 3.5 | 0.0855 | 0.0261 | 188.2462 | 3.2753 | 0.0335 |
| 10 - 9b | 3.5 | 0.0437 | 0.0261 | 188.2462 | 1.6735 | 0.7619 |
| 3 - 5 | 3.5 | 0.0444 | 0.0261 | 188.2462 | 1.7029 | 0.7441 |
| 3 - 6 | 3.5 | -0.0073 | 0.0261 | 188.2462 | -0.2789 | 1.0000 |
| 3 - 7 | 3.5 | -0.0234 | 0.0261 | 188.2462 | -0.8988 | 0.9928 |
| 3 - 8 | 3.5 | 0.0157 | 0.0261 | 188.2462 | 0.6009 | 0.9996 |
| 3 - 9 | 3.5 | 0.0761 | 0.0261 | 188.2462 | 2.9165 | 0.0913 |
| 3 - 9b | 3.5 | 0.0343 | 0.0261 | 188.2462 | 1.3147 | 0.9259 |
| 5 - 6 | 3.5 | -0.0517 | 0.0261 | 188.2462 | -1.9818 | 0.5580 |
| 5 - 7 | 3.5 | -0.0679 | 0.0261 | 188.2462 | -2.6016 | 0.1930 |
| 5 - 8 | 3.5 | -0.0287 | 0.0261 | 188.2462 | -1.1019 | 0.9734 |
| 5 - 9 | 3.5 | 0.0317 | 0.0261 | 188.2462 | 1.2136 | 0.9527 |
| 5 - 9b | 3.5 | -0.0101 | 0.0261 | 188.2462 | -0.3882 | 1.0000 |
| 6 - 7 | 3.5 | -0.0162 | 0.0261 | 188.2462 | -0.6199 | 0.9995 |
| 6 - 8 | 3.5 | 0.0230 | 0.0261 | 188.2462 | 0.8799 | 0.9938 |
| 6 - 9 | 3.5 | 0.0834 | 0.0261 | 188.2462 | 3.1954 | 0.0424 |
| 6 - 9b | 3.5 | 0.0416 | 0.0261 | 188.2462 | 1.5936 | 0.8073 |
| 7 - 8 | 3.5 | 0.0391 | 0.0261 | 188.2462 | 1.4997 | 0.8545 |
| 7 - 9 | 3.5 | 0.0995 | 0.0261 | 188.2462 | 3.8152 | 0.0057 |
| 7 - 9b | 3.5 | 0.0577 | 0.0261 | 188.2462 | 2.2134 | 0.4013 |
| 8 - 9 | 3.5 | 0.0604 | 0.0261 | 188.2462 | 2.3155 | 0.3382 |
| 8 - 9b | 3.5 | 0.0186 | 0.0261 | 188.2462 | 0.7137 | 0.9986 |
| 9 - 9b | 3.5 | -0.0418 | 0.0261 | 188.2462 | -1.6018 | 0.8028 |
| 1 - 10 | 4 | 0.0355 | 0.0261 | 188.2462 | 1.3610 | 0.9108 |
| 1 - 3 | 4 | 0.0417 | 0.0261 | 188.2462 | 1.5983 | 0.8047 |
| 1 - 5 | 4 | 0.0831 | 0.0261 | 188.2462 | 3.1839 | 0.0438 |
| 1 - 6 | 4 | 0.0322 | 0.0261 | 188.2462 | 1.2326 | 0.9483 |
| 1 - 7 | 4 | 0.0194 | 0.0261 | 188.2462 | 0.7440 | 0.9981 |
| 1 - 8 | 4 | 0.0562 | 0.0261 | 188.2462 | 2.1554 | 0.4392 |
| 1 - 9 | 4 | 0.1147 | 0.0261 | 188.2462 | 4.3945 | 0.0006 |
| 1 - 9b | 4 | 0.0664 | 0.0261 | 188.2462 | 2.5460 | 0.2172 |
| 10 - 3 | 4 | 0.0062 | 0.0261 | 188.2462 | 0.2373 | 1.0000 |
| 10 - 5 | 4 | 0.0476 | 0.0261 | 188.2462 | 1.8229 | 0.6670 |
| 10 - 6 | 4 | -0.0034 | 0.0261 | 188.2462 | -0.1285 | 1.0000 |
| 10 - 7 | 4 | -0.0161 | 0.0261 | 188.2462 | -0.6171 | 0.9995 |
| 10 - 8 | 4 | 0.0207 | 0.0261 | 188.2462 | 0.7944 | 0.9969 |
| 10 - 9 | 4 | 0.0791 | 0.0261 | 188.2462 | 3.0335 | 0.0670 |
| 10 - 9b | 4 | 0.0309 | 0.0261 | 188.2462 | 1.1849 | 0.9588 |
| 3 - 5 | 4 | 0.0414 | 0.0261 | 188.2462 | 1.5856 | 0.8116 |
| 3 - 6 | 4 | -0.0095 | 0.0261 | 188.2462 | -0.3657 | 1.0000 |
| 3 - 7 | 4 | -0.0223 | 0.0261 | 188.2462 | -0.8543 | 0.9949 |
| 3 - 8 | 4 | 0.0145 | 0.0261 | 188.2462 | 0.5571 | 0.9998 |
| 3 - 9 | 4 | 0.0730 | 0.0261 | 188.2462 | 2.7962 | 0.1234 |
| 3 - 9b | 4 | 0.0247 | 0.0261 | 188.2462 | 0.9476 | 0.9898 |
| 5 - 6 | 4 | -0.0509 | 0.0261 | 188.2462 | -1.9513 | 0.5791 |
| 5 - 7 | 4 | -0.0637 | 0.0261 | 188.2462 | -2.4399 | 0.2688 |
| 5 - 8 | 4 | -0.0268 | 0.0261 | 188.2462 | -1.0285 | 0.9827 |
| 5 - 9 | 4 | 0.0316 | 0.0261 | 188.2462 | 1.2106 | 0.9534 |
| 5 - 9b | 4 | -0.0166 | 0.0261 | 188.2462 | -0.6380 | 0.9994 |
| 6 - 7 | 4 | -0.0127 | 0.0261 | 188.2462 | -0.4886 | 0.9999 |
| 6 - 8 | 4 | 0.0241 | 0.0261 | 188.2462 | 0.9228 | 0.9914 |
| 6 - 9 | 4 | 0.0825 | 0.0261 | 188.2462 | 3.1619 | 0.0467 |
| 6 - 9b | 4 | 0.0343 | 0.0261 | 188.2462 | 1.3134 | 0.9263 |
| 7 - 8 | 4 | 0.0368 | 0.0261 | 188.2462 | 1.4114 | 0.8923 |
| 7 - 9 | 4 | 0.0952 | 0.0261 | 188.2462 | 3.6505 | 0.0100 |
| 7 - 9b | 4 | 0.0470 | 0.0261 | 188.2462 | 1.8020 | 0.6809 |
| 8 - 9 | 4 | 0.0584 | 0.0261 | 188.2462 | 2.2391 | 0.3850 |
| 8 - 9b | 4 | 0.0102 | 0.0261 | 188.2462 | 0.3905 | 1.0000 |
| 9 - 9b | 4 | -0.0482 | 0.0261 | 188.2462 | -1.8486 | 0.6497 |
11 Area Under the Curve (AUC)
AUC computed per individual via the trapezoid rule across all timepoints. metabolism_per_* is the primary metric matching the paper; corrected_fc and raw_fluorescence are retained for reference.
compute_auc <- function(df, value_col, group_vars) {
df %>%
filter(is.finite(time_hr), is.finite(.data[[value_col]])) %>%
group_by(across(all_of(group_vars))) %>%
summarise(
AUC = trapezoid_auc(time_hr, .data[[value_col]]),
n_timepoints = n(),
.groups = "drop"
) %>%
filter(is.finite(AUC))
}
# Only include grouping columns that are actually present in the data
individual_vars <- intersect(
c("trace_id", "family_id_group", "treatment_group"),
names(metabolism_df)
)
auc_metab_list <- list()
if (nrow(metabolism_df) > 0) {
for (mc in active_meas_cols) {
col <- paste0("metabolism_per_", mc)
if (col %in% names(metabolism_df)) {
auc_metab_list[[col]] <-
compute_auc(metabolism_df, col, individual_vars) %>%
mutate(metric = col)
}
}
}
auc_all <- bind_rows(auc_metab_list)
str(auc_all)tibble [99 × 6] (S3: tbl_df/tbl/data.frame)
$ trace_id : chr [1:99] "1" "10" "11" "12" ...
$ family_id_group: chr [1:99] "7" "8" "6" "7" ...
$ treatment_group: chr [1:99] NA NA NA NA ...
$ AUC : num [1:99] 0.694 0.651 0.741 0.532 0.554 ...
$ n_timepoints : int [1:99] 9 9 9 9 9 9 9 9 9 9 ...
$ metric : chr [1:99] "metabolism_per_area_mm2_measurement" "metabolism_per_area_mm2_measurement" "metabolism_per_area_mm2_measurement" "metabolism_per_area_mm2_measurement" ...
11.1 AUC summary tables
sum_vars <- intersect(
c("metric", "family_id_group", "treatment_group"),
names(auc_all)
)
auc_summary <- auc_all %>%
group_by(across(all_of(sum_vars))) %>%
summarise(
n = n(),
mean = mean(AUC, na.rm = TRUE),
sd = sd(AUC, na.rm = TRUE),
se = sd / sqrt(n),
median = median(AUC, na.rm = TRUE),
.groups = "drop"
)
print(auc_summary)# A tibble: 9 × 8
metric family_id_group treatment_group n mean sd se median
<chr> <chr> <chr> <int> <dbl> <dbl> <dbl> <dbl>
1 metabolism_pe… 1 <NA> 11 0.584 0.252 0.0760 0.554
2 metabolism_pe… 10 <NA> 11 0.508 0.239 0.0720 0.496
3 metabolism_pe… 3 <NA> 11 0.527 0.301 0.0908 0.487
4 metabolism_pe… 5 <NA> 11 0.409 0.131 0.0394 0.461
5 metabolism_pe… 6 <NA> 11 0.533 0.152 0.0458 0.518
6 metabolism_pe… 7 <NA> 11 0.552 0.193 0.0583 0.532
7 metabolism_pe… 8 <NA> 11 0.463 0.177 0.0534 0.431
8 metabolism_pe… 9 <NA> 11 0.356 0.159 0.0481 0.288
9 metabolism_pe… 9b <NA> 11 0.390 0.172 0.0520 0.367
12 Statistical Analysis
Each individual oyster (sample_id_group) is the observational unit. The model is built from whichever grouping factors are present: both family and treatment (with interaction) when both exist, or a one-way model when only one factor is available. Each plate maps to a unique family × treatment combination, so plate-level and group-level variance are confounded; interpret accordingly.
run_auc_stats <- function(auc_df) {
empty <- tibble()
has_family <- "family_id_group" %in% names(auc_df) &&
length(unique(na.omit(auc_df$family_id_group))) > 1
has_treatment <- "treatment_group" %in% names(auc_df) &&
length(unique(na.omit(auc_df$treatment_group))) > 1
if (!has_family && !has_treatment) {
return(list(model = NULL, anova = NULL,
pairs_full = empty, pairs_family = empty,
pairs_trt = empty,
has_family = FALSE, has_treatment = FALSE))
}
if (has_family) auc_df <- auc_df %>% mutate(family = factor(family_id_group))
if (has_treatment) auc_df <- auc_df %>% mutate(treatment = factor(treatment_group))
formula_str <- if (has_family && has_treatment) {
"AUC ~ family * treatment"
} else if (has_family) {
"AUC ~ family"
} else {
"AUC ~ treatment"
}
model <- lm(as.formula(formula_str), data = auc_df)
anova_res <- anova(model)
if (has_family && has_treatment) {
pairs_full <- as.data.frame(pairs(emmeans(model, ~ family * treatment),
adjust = "tukey"))
pairs_family <- as.data.frame(pairs(emmeans(model, ~ family),
adjust = "tukey"))
pairs_trt <- as.data.frame(pairs(emmeans(model, ~ treatment),
adjust = "tukey"))
} else if (has_family) {
pairs_family <- as.data.frame(pairs(emmeans(model, ~ family),
adjust = "tukey"))
pairs_full <- pairs_family
pairs_trt <- empty
} else {
pairs_trt <- as.data.frame(pairs(emmeans(model, ~ treatment),
adjust = "tukey"))
pairs_full <- pairs_trt
pairs_family <- empty
}
list(
model = model,
anova = anova_res,
pairs_full = pairs_full,
pairs_family = pairs_family,
pairs_trt = pairs_trt,
has_family = has_family,
has_treatment = has_treatment
)
}
metrics_to_test <- unique(auc_all$metric)
stats_results <- map(
set_names(metrics_to_test),
~ run_auc_stats(auc_all %>% filter(metric == .x))
)12.1 Results by metric
for (met in metrics_to_test) {
stats <- stats_results[[met]]
cat("\n\n### Metric:", met, "\n\n")
cat("**ANOVA:**\n\n")
cat(knitr::kable(as.data.frame(stats$anova), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
if (stats$has_family && stats$has_treatment) {
cat("**Pairwise: family × treatment (Tukey):**\n\n")
cat(knitr::kable(as.data.frame(stats$pairs_full), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
cat("**Pairwise: family main effect:**\n\n")
cat(knitr::kable(as.data.frame(stats$pairs_family), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
cat("**Pairwise: treatment main effect:**\n\n")
cat(knitr::kable(as.data.frame(stats$pairs_trt), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
} else if (stats$has_family) {
cat("**Pairwise: family (Tukey):**\n\n")
cat(knitr::kable(as.data.frame(stats$pairs_family), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
} else if (stats$has_treatment) {
cat("**Pairwise: treatment (Tukey):**\n\n")
cat(knitr::kable(as.data.frame(stats$pairs_trt), digits = 4, format = "pipe"), sep = "\n")
cat("\n")
}
}12.1.1 Metric: metabolism_per_area_mm2_measurement
ANOVA:
| Df | Sum Sq | Mean Sq | F value | Pr(>F) | |
|---|---|---|---|---|---|
| family | 8 | 0.5576 | 0.0697 | 1.6712 | 0.1163 |
| Residuals | 90 | 3.7536 | 0.0417 | NA | NA |
Pairwise: family (Tukey):
| contrast | estimate | SE | df | t.ratio | p.value |
|---|---|---|---|---|---|
| 1 - 10 | 0.0760 | 0.0871 | 90 | 0.8732 | 0.9938 |
| 1 - 3 | 0.0569 | 0.0871 | 90 | 0.6531 | 0.9992 |
| 1 - 5 | 0.1746 | 0.0871 | 90 | 2.0054 | 0.5438 |
| 1 - 6 | 0.0501 | 0.0871 | 90 | 0.5756 | 0.9997 |
| 1 - 7 | 0.0311 | 0.0871 | 90 | 0.3572 | 1.0000 |
| 1 - 8 | 0.1203 | 0.0871 | 90 | 1.3813 | 0.9020 |
| 1 - 9 | 0.2280 | 0.0871 | 90 | 2.6188 | 0.1936 |
| 1 - 9b | 0.1935 | 0.0871 | 90 | 2.2225 | 0.4005 |
| 10 - 3 | -0.0192 | 0.0871 | 90 | -0.2201 | 1.0000 |
| 10 - 5 | 0.0986 | 0.0871 | 90 | 1.1322 | 0.9677 |
| 10 - 6 | -0.0259 | 0.0871 | 90 | -0.2976 | 1.0000 |
| 10 - 7 | -0.0449 | 0.0871 | 90 | -0.5160 | 0.9999 |
| 10 - 8 | 0.0442 | 0.0871 | 90 | 0.5081 | 0.9999 |
| 10 - 9 | 0.1520 | 0.0871 | 90 | 1.7456 | 0.7168 |
| 10 - 9b | 0.1175 | 0.0871 | 90 | 1.3494 | 0.9132 |
| 3 - 5 | 0.1178 | 0.0871 | 90 | 1.3523 | 0.9122 |
| 3 - 6 | -0.0068 | 0.0871 | 90 | -0.0775 | 1.0000 |
| 3 - 7 | -0.0258 | 0.0871 | 90 | -0.2959 | 1.0000 |
| 3 - 8 | 0.0634 | 0.0871 | 90 | 0.7282 | 0.9982 |
| 3 - 9 | 0.1712 | 0.0871 | 90 | 1.9657 | 0.5709 |
| 3 - 9b | 0.1367 | 0.0871 | 90 | 1.5694 | 0.8186 |
| 5 - 6 | -0.1245 | 0.0871 | 90 | -1.4298 | 0.8833 |
| 5 - 7 | -0.1435 | 0.0871 | 90 | -1.6482 | 0.7755 |
| 5 - 8 | -0.0543 | 0.0871 | 90 | -0.6241 | 0.9994 |
| 5 - 9 | 0.0534 | 0.0871 | 90 | 0.6134 | 0.9995 |
| 5 - 9b | 0.0189 | 0.0871 | 90 | 0.2171 | 1.0000 |
| 6 - 7 | -0.0190 | 0.0871 | 90 | -0.2184 | 1.0000 |
| 6 - 8 | 0.0702 | 0.0871 | 90 | 0.8057 | 0.9964 |
| 6 - 9 | 0.1779 | 0.0871 | 90 | 2.0432 | 0.5181 |
| 6 - 9b | 0.1434 | 0.0871 | 90 | 1.6469 | 0.7762 |
| 7 - 8 | 0.0892 | 0.0871 | 90 | 1.0241 | 0.9825 |
| 7 - 9 | 0.1969 | 0.0871 | 90 | 2.2616 | 0.3763 |
| 7 - 9b | 0.1624 | 0.0871 | 90 | 1.8653 | 0.6389 |
| 8 - 9 | 0.1078 | 0.0871 | 90 | 1.2375 | 0.9458 |
| 8 - 9b | 0.0733 | 0.0871 | 90 | 0.8412 | 0.9952 |
| 9 - 9b | -0.0345 | 0.0871 | 90 | -0.3962 | 1.0000 |
13 AUC Box Plots with Statistical Annotations
Significance labels: *** p < 0.001, ** p < 0.01, * p < 0.05. Brackets are drawn only for significant pairs (p < 0.05). Plots are generated for whichever grouping factors are present: treatment-only, family-only, all-groups, within-family, and within-treatment.
sig_label <- function(p) {
case_when(p < 0.001 ~ "***", p < 0.01 ~ "**", p < 0.05 ~ "*",
TRUE ~ "ns")
}
# Add significance brackets to an existing ggplot.
# pairs_df : data frame with $contrast and $p.value columns
# group_levels: ordered character vector matching x-axis factor levels
# y_vals : numeric vector of AUC values used to set bracket heights
add_sig_brackets <- function(p, pairs_df, group_levels, y_vals) {
sig_pairs <- pairs_df %>%
mutate(label = sig_label(p.value)) %>%
filter(label != "ns")
if (nrow(sig_pairs) == 0) return(p)
y_max <- max(y_vals, na.rm = TRUE)
y_range <- diff(range(y_vals, na.rm = TRUE))
step <- y_range * 0.12
for (i in seq_len(nrow(sig_pairs))) {
parts <- str_split(as.character(sig_pairs$contrast[i]), " - ", 2)[[1]]
g1 <- trimws(parts[1])
g2 <- trimws(parts[2])
x1 <- match(g1, group_levels)
x2 <- match(g2, group_levels)
if (is.na(x1) || is.na(x2)) next
bar_y <- y_max + i * step
p <- p +
annotate("segment", x = x1, xend = x2,
y = bar_y, yend = bar_y,
colour = "black", linewidth = 0.6) +
annotate("segment", x = x1, xend = x1,
y = bar_y, yend = bar_y - step * 0.3,
colour = "black", linewidth = 0.6) +
annotate("segment", x = x2, xend = x2,
y = bar_y, yend = bar_y - step * 0.3,
colour = "black", linewidth = 0.6) +
annotate("text", x = (x1 + x2) / 2,
y = bar_y + step * 0.15,
label = sig_pairs$label[i], size = 4.5)
}
p
}for (met in metrics_to_test) {
df <- auc_all %>% filter(metric == met)
stats <- stats_results[[met]]
y_lab <- auc_y_label(met)
has_fam <- stats$has_family
has_trt <- stats$has_treatment
# ── Treatment main effect (x = treatment, tick = treatment name) ───────
if (has_trt) {
df_p <- df %>%
mutate(x = factor(treatment_group, levels = sort(unique(treatment_group))))
grps <- levels(df_p$x)
p <- ggplot(df_p, aes(x = x, y = AUC, fill = x)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
geom_jitter(width = 0.15, alpha = 0.4, size = 1.5) +
scale_fill_manual(values = trt_colours[grps], guide = "none") +
labs(x = "Treatment", y = y_lab) +
theme_classic(base_size = 13)
p <- add_sig_brackets(p, stats$pairs_trt, grps, df_p$AUC)
print(p)
ggsave(file.path(fig_dir, paste0("auc_treatment_", met, ".png")),
p, width = 5, height = 5)
}
# ── Family main effect (x = family, tick = family name) ───────────────
if (has_fam) {
df_p <- df %>%
mutate(x = factor(family_id_group,
levels = str_sort(unique(family_id_group), numeric = TRUE)))
grps <- levels(df_p$x)
p <- ggplot(df_p, aes(x = x, y = AUC, fill = x)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
geom_jitter(width = 0.15, alpha = 0.4, size = 1.5) +
scale_fill_manual(values = fam_colours[grps], guide = "none") +
labs(x = "Family", y = y_lab) +
theme_classic(base_size = 13)
p <- add_sig_brackets(p, stats$pairs_family, grps, df_p$AUC)
print(p)
ggsave(file.path(fig_dir, paste0("auc_family_", met, ".png")),
p, width = 5, height = 5)
}
# Remaining plots require both factors
if (!has_fam || !has_trt) next
# ── All family:treatment groups (x = family:treatment) ─────────────────
# emmeans contrasts use spaces; convert to colon to match tick labels
pairs_fc <- stats$pairs_full %>%
mutate(contrast = str_replace_all(
contrast,
"([a-z]+) ([a-z]+)( - )([a-z]+) ([a-z]+)",
"\\1:\\2\\3\\4:\\5"
))
df_p <- df %>%
mutate(x = factor(
paste(family_id_group, treatment_group, sep = ":"),
levels = str_sort(unique(paste(family_id_group, treatment_group, sep = ":")),
numeric = TRUE)
))
grps <- levels(df_p$x)
fill_map <- setNames(make_palette(length(grps)), grps)
p <- ggplot(df_p, aes(x = x, y = AUC, fill = x)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
geom_jitter(width = 0.15, alpha = 0.4, size = 1.5) +
scale_fill_manual(values = fill_map, guide = "none") +
labs(x = "Family : Treatment", y = y_lab) +
theme_classic(base_size = 13) +
theme(axis.text.x = element_text(angle = 20, hjust = 1))
p <- add_sig_brackets(p, pairs_fc, grps, df_p$AUC)
print(p)
ggsave(file.path(fig_dir, paste0("auc_all_groups_", met, ".png")),
p, width = 6, height = 5)
# ── Within each family: treatment comparison (x = family:treatment) ────
# Tick labels are family:treatment so these plots are visually distinct
# from the treatment main-effect plot above.
for (fam in str_sort(unique(df$family_id_group), numeric = TRUE)) {
df_p <- df %>%
filter(family_id_group == fam) %>%
mutate(x = factor(
paste(family_id_group, treatment_group, sep = ":"),
levels = str_sort(unique(paste(family_id_group, treatment_group, sep = ":")),
numeric = TRUE)
))
grps <- levels(df_p$x)
pairs_sub <- pairs_fc %>%
filter(str_count(contrast, paste0(fam, ":")) == 2)
p <- ggplot(df_p, aes(x = x, y = AUC, fill = x)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
geom_jitter(width = 0.15, alpha = 0.4, size = 1.5) +
scale_fill_manual(values = fill_map[grps], guide = "none") +
labs(x = "Family : Treatment", y = y_lab) +
theme_classic(base_size = 13)
p <- add_sig_brackets(p, pairs_sub, grps, df_p$AUC)
print(p)
ggsave(file.path(fig_dir, paste0("auc_", fam, "_trt_", met, ".png")),
p, width = 5, height = 5)
}
# ── Within each treatment: family comparison (x = family:treatment) ────
# Tick labels are family:treatment so these plots are visually distinct
# from the family main-effect plot above.
for (trt in sort(unique(df$treatment_group))) {
df_p <- df %>%
filter(treatment_group == trt) %>%
mutate(x = factor(
paste(family_id_group, treatment_group, sep = ":"),
levels = str_sort(unique(paste(family_id_group, treatment_group, sep = ":")),
numeric = TRUE)
))
grps <- levels(df_p$x)
pairs_sub <- pairs_fc %>%
filter(str_count(contrast, paste0(":", trt)) == 2)
p <- ggplot(df_p, aes(x = x, y = AUC, fill = x)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
geom_jitter(width = 0.15, alpha = 0.4, size = 1.5) +
scale_fill_manual(values = fill_map[grps], guide = "none") +
labs(x = "Family : Treatment", y = y_lab) +
theme_classic(base_size = 13)
p <- add_sig_brackets(p, pairs_sub, grps, df_p$AUC)
print(p)
ggsave(file.path(fig_dir, paste0("auc_", trt, "_fam_", met, ".png")),
p, width = 5, height = 5)
}
}
14 Save Output Data
write_csv(auc_all, file.path(out_dir, "auc_all_metrics.csv"))
write_csv(auc_summary, file.path(out_dir, "auc_summary.csv"))
if (nrow(metabolism_df) > 0)
write_csv(metabolism_df,
file.path(out_dir, "metabolism.csv"))
stats_compiled <- map_dfr(metrics_to_test, function(met) {
bind_rows(
stats_results[[met]]$pairs_full %>%
mutate(comparison = "family:treatment"),
stats_results[[met]]$pairs_family %>%
mutate(comparison = "family"),
stats_results[[met]]$pairs_trt %>%
mutate(comparison = "treatment")
) %>% mutate(metric = met)
})
write_csv(stats_compiled,
file.path(out_dir, "pairwise_stats.csv"))
message("Output files written to: ", out_dir)