Here we followed Varroa mites viability in glass vials, after 3 hours
exposure to Amitraz in differnet doses, and the solvent as control.
First, we tested two parameters important for the establishment of
the experimental protocol:
Following the preliminary experiments, we have concluded that there’s
no difference between the two physiological stages, and that the
solvent, Acetone confer no harm to the mite’s viability.
we therefore proceeded to test amitraz effect on the varroa mite,
asking:
Does the solvent (Acetone) affects the mites’ viability?
Phoretic mites’ state after 3 hours exposure to the solvent
(“Acetone”), compared to an empty vial (“Control”).
Only freshly prepared acetone vials were used for the analysis.
# Analysis 1: 4-category analysis for experiments 8-13 (with dancing) - Control and Acetone only
df_solvent <- df %>%
filter(Yard == "Shamir") %>%
filter(Physiological_stage == "Phoretic") %>%
filter(Vial_Freshness == "Fresh") %>%
filter(Tested_compound %in% c("Control", "Acetone")) %>%
filter(Date %in% experiments_to_keep) %>% # Filter by control viability
filter(Date %in% c("12-03-25", "24-03-25", "27-03-25", "17-03-25")) %>% # include experiments with both acetone and control
select(Date, Physiological_stage, Tested_compound, Viable, Paralyzed, Dead) %>%
pivot_longer(
cols = c(Viable, Paralyzed, Dead),
names_to = "State",
values_to = "Count"
) %>%
mutate(
State = factor(State, levels = c("Viable", "Paralyzed", "Dead")),
State = recode(State,
"Viable" = "Live",
"Paralyzed" = "Paralyzed",
"Dead" = "Dead"))
# Calculate summary df
if (nrow(df_solvent) > 0) {
df_summary_solvent <- df_solvent %>%
group_by(Tested_compound, State) %>%
summarise(
Total_Count = sum(Count, na.rm = TRUE),
.groups = 'drop'
) %>%
group_by(Tested_compound) %>%
mutate(
Percentage = Total_Count / sum(Total_Count) * 100,
Total_Count_Per_Condition = sum(Total_Count) # Add total count per condition
) %>%
ungroup()
} else {
df_summary_solvent <- tibble()
}
# Create the 4-category plot (experiments 1,3,4)
p1 <- ggplot(df_summary_solvent, aes(x = Tested_compound, y = Percentage, fill = State)) +
geom_bar(stat = "identity", position = "stack", alpha = 0.8, color = "black", linewidth = 0.5) +
#facet_wrap(~ Vial_Freshness, labeller = label_wrap_gen(width = 15), scales = "free_x") +
scale_fill_manual(values = c("Live" = "#FFFFFF",
"Paralyzed" = "#A0A0A0",
"Dead" = "#000000")) +
scale_x_discrete(labels = function(x) {
sapply(x, function(val) {
count <- df_summary_solvent$Total_Count_Per_Condition[df_summary_solvent$Tested_compound == val][1]
paste0(val, "\n(n = ", count, ")")
})
}) +
labs(
title = "Distribution of Varroa Mite state - Control vs Acetone",
subtitle = paste0("Experiments 1, 3 and 4 (Filtered: >= 80% control viability)"),
x = "Treatment",
y = "Percentage (%)",
fill = "State"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 12),
axis.text.x = element_text(angle = 0, hjust = 0.5),
axis.title = element_text(size = 11, face = "bold"),
legend.title = element_text(size = 11, face = "bold"),
strip.text = element_text(size = 10, face = "bold"),
panel.grid.major.x = element_blank(),
panel.grid.minor = element_blank()
)
print(p1)

cat("\n\n### Mean and Standard Error Plots\n")
##
##
## ### Mean and Standard Error Plots
cat("These plots show the mean percentage of mites per vial for each state, with standard error bars.\n")
## These plots show the mean percentage of mites per vial for each state, with standard error bars.
# --- Mean analysis for 4-category data ---
df_for_mean_solvent <- df %>%
filter(Physiological_stage == "Phoretic") %>%
filter(Vial_Freshness == "Fresh") %>%
filter(Tested_compound %in% c("Control", "Acetone")) %>%
filter(Date %in% experiments_to_keep) %>% # Filter by control viability
filter(Date %in% c("12-03-25", "23-03-25", "27-03-25"))
if (nrow(df_for_mean_solvent) > 0) {
# Calculate proportions per vial
df_proportions_solvent <- df_for_mean_solvent %>%
filter(Total > 0) %>%
mutate(
Live = Viable / Total,
Paralyzed = Paralyzed / Total,
Dancing = Dancing / Total,
Dead = Dead / Total
) %>%
select(Vial, Tested_compound, Vial_Freshness, Total, Live, Paralyzed, Dancing, Dead) %>%
pivot_longer(
cols = c(Live, Paralyzed, Dancing, Dead),
names_to = "State",
values_to = "Proportion"
)
# Calculate summary stats (mean, se)
summary_mean_solvent <- df_proportions_solvent %>%
group_by(Tested_compound, State) %>%
summarise(
Mean_Percentage = mean(Proportion, na.rm = TRUE) * 100,
SE = (sd(Proportion, na.rm = TRUE) / sqrt(n())) * 100,
.groups = 'drop'
)
# Get total counts (vials and mites)
totals_solvent <- df_for_mean_solvent %>%
group_by(Tested_compound) %>%
summarise(
n_vials = n_distinct(paste(Date, Vial)),
n_mites = sum(Total, na.rm = TRUE),
.groups = 'drop'
) %>%
mutate(x_label = paste0(Tested_compound, "\nvials: ", n_vials, "\nmites: ", n_mites))
# Combine for plotting
plot_data_mean_solvent <- summary_mean_solvent %>%
left_join(totals_solvent, by = "Tested_compound") %>%
mutate(State = factor(State, levels = c("Live", "Paralyzed", "Dead"))) %>%
na.omit()
# Create the plot
p3 <- ggplot(plot_data_mean_solvent, aes(x = x_label, y = Mean_Percentage, fill = State)) +
geom_bar(stat = "identity", position = position_dodge(), color = "black") +
geom_errorbar(
aes(ymin = Mean_Percentage - SE, ymax = pmin(100, Mean_Percentage + SE)),
position = position_dodge(0.9),
width = 0.25
) +
scale_fill_manual(values = c("Live" = "#FFFFFF", "Paralyzed" = "#A0A0A0", "Dead" = "#000000")) +
labs(
title = "Mean Mite States per Vial - Control vs Acetone",
subtitle = paste0("Experiments 1, 3 and 4(Filtered: >= 80% control viability)"),
x = "Treatment",
y = "Mean Percentage per Vial"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 12),
axis.text.x = element_text(angle = 0, hjust = 0.5),
axis.title = element_text(size = 11, face = "bold"),
legend.title = element_text(size = 11, face = "bold"),
strip.text = element_text(size = 10, face = "bold")
)
print(p3)
}

cat("\n\n### Statistical Analysis: Control vs. Acetone\n")
##
##
## ### Statistical Analysis: Control vs. Acetone
# --- Chi-squared Test of Independence ---
# This test determines if there is a significant association between the treatment
# (Control/Acetone) and the mite's state (Live/Paralyzed/Dead).
# First, create a contingency table of counts
contingency_table_solvent <- df_summary_solvent %>%
select(Tested_compound, State, Total_Count) %>%
pivot_wider(names_from = State, values_from = Total_Count, values_fill = 0) %>%
column_to_rownames(var = "Tested_compound")
# Perform the Chi-squared test
chi_test_result <- chisq.test(contingency_table_solvent)
cat("\n**1. Chi-squared Test Results:**\n")
##
## **1. Chi-squared Test Results:**
cat("This test compares the overall distribution of mite states between the Control and Acetone groups.\n\n")
## This test compares the overall distribution of mite states between the Control and Acetone groups.
# Print the results in a readable format
print(chi_test_result)
##
## Pearson's Chi-squared test
##
## data: contingency_table_solvent
## X-squared = 7.2345, df = 2, p-value = 0.02686
cat(paste0("\n**Interpretation:**\n",
"The p-value is ", round(chi_test_result$p.value, 4), ". ",
"If this value is less than your significance level (e.g., 0.05), you can conclude that there is a statistically significant association between the treatment and the mites' state distribution. In other words, the distribution of Live, Paralyzed, and Dead mites is different for the Acetone group compared to the Control group.\n"))
##
## **Interpretation:**
## The p-value is 0.0269. If this value is less than your significance level (e.g., 0.05), you can conclude that there is a statistically significant association between the treatment and the mites' state distribution. In other words, the distribution of Live, Paralyzed, and Dead mites is different for the Acetone group compared to the Control group.
# --- T-test on Arcsin-Transformed Proportions of Live Mites ---
# This test compares the mean proportion of LIVE mites per vial between the two groups.
# Arcsin transformation is used to stabilize the variance of proportion data.
cat("\n**2. T-test on the Proportion of Live Mites:**\n")
##
## **2. T-test on the Proportion of Live Mites:**
cat("This test specifically checks if the mean percentage of *Live* mites per vial is different between the Control and Acetone groups.\n\n")
## This test specifically checks if the mean percentage of *Live* mites per vial is different between the Control and Acetone groups.
if (nrow(df_for_mean_solvent) > 0) {
# Calculate proportion of live mites per vial
proportions_live <- df_for_mean_solvent %>%
filter(Total > 0) %>%
mutate(Proportion_Live = Viable / Total)
# Apply arcsin square root transformation
proportions_live$Transformed_Live <- asin(sqrt(proportions_live$Proportion_Live))
# Perform Welch's t-test (does not assume equal variances)
t_test_result <- t.test(Transformed_Live ~ Tested_compound, data = proportions_live)
print(t_test_result)
cat(paste0("\n**Interpretation:**\n",
"The p-value is ", round(t_test_result$p.value, 4), ". ",
"If this value is less than your significance level, it indicates a significant difference in the mean proportion of live mites per vial between the Control and Acetone treatments.\n"))
} else {
cat("Not enough data to perform the t-test.\n")
}
##
## Welch Two Sample t-test
##
## data: Transformed_Live by Tested_compound
## t = 0.81636, df = 5.318, p-value = 0.4493
## alternative hypothesis: true difference in means between group Control and group Acetone is not equal to 0
## 95 percent confidence interval:
## -0.2426092 0.4744330
## sample estimates:
## mean in group Control mean in group Acetone
## 1.490359 1.374447
##
##
## **Interpretation:**
## The p-value is 0.4493. If this value is less than your significance level, it indicates a significant difference in the mean proportion of live mites per vial between the Control and Acetone treatments.
Is there a dose dependent effect of Amitraz on varroa mite
viability?
To answer this question, we’ll use only phoretic mites from fresh
vials. The analysis was done on Abbott -corrected values.
we tested mite’s state after three hours exposure to different doses
of amitraz, in glass vials.
Experiment was conducted in threee yards, in a few hives in each
yard
# --- Data Filtering and Preparation ---
df_dose <- df %>%
filter(Date %in% experiments_to_keep) %>%
filter(Physiological_stage == "Phoretic") %>%
# Include Acetone (dose 0) for Abbott correction; we will exclude dose 0 in modeling later
filter(Tested_compound %in% c("Acetone", "Amitraz")) %>%
filter(Vial_Freshness == "Fresh") %>%
mutate(
Dose_numeric = ifelse(Tested_compound == "Acetone", 0, as.numeric(Dose)))
# --- Abbott correction using same-experiment controls (Dose = 0) ---
# For each Date and State, compute the mean control proportion from dose 0 vials
df_control_means <- df_dose %>%
filter(Total > 0) %>%
filter(Dose_numeric == 0) %>%
mutate(
Abnormal_behaviour = (ifelse(is.na(Paralyzed), 0, Paralyzed) + ifelse(is.na(Dancing), 0, Dancing)) / Total,
Live = Viable / Total,
Dead = Dead / Total
) %>%
select(Date, Yard, Live, Abnormal_behaviour, Dead) %>%
pivot_longer(cols = c(Live, Abnormal_behaviour, Dead), names_to = "State", values_to = "Control_Prop") %>%
group_by(Date, Yard, State) %>%
summarise(Control_Proportion = mean(Control_Prop, na.rm = TRUE), .groups = 'drop')
# Build base proportions table once (do not overwrite later)
df_proportions_dose_base <- df_dose %>%
filter(Total > 0) %>%
mutate(
Abnormal_behaviour = (ifelse(is.na(Paralyzed), 0, Paralyzed) + ifelse(is.na(Dancing), 0, Dancing)) / Total,
Live = Viable / Total,
Dead = Dead / Total
) %>%
select(Date, Yard, Hive, Vial, Dose_numeric, Live, Abnormal_behaviour, Dead) %>%
pivot_longer(
cols = c(Live, Abnormal_behaviour, Dead),
names_to = "State",
values_to = "Proportion")
# Add control means and Abbott-corrected proportions to a new table
df_proportions_dose <- df_proportions_dose_base %>%
left_join(df_control_means, by = c("Date", "Yard", "State")) %>%
mutate(
Abbott_corrected = case_when(
# Robust Abbott for Live: correct mortality then convert back to viability
State == "Live" & !is.na(Control_Proportion) ~ {
mort_test <- pmin(pmax(1 - Proportion, 0), 1)
mort_ctrl <- pmin(pmax(1 - Control_Proportion, 0), 1)
ifelse((1 - mort_ctrl) > 0,
pmin(pmax(1 - ((mort_test - mort_ctrl) / (1 - mort_ctrl)), 0), 1),
NA_real_)
},
# Default Abbott for other states (e.g., Dead, Abnormal)
!is.na(Control_Proportion) & (1 - Control_Proportion) > 0 ~
pmin(pmax((Proportion - Control_Proportion) / (1 - Control_Proportion), 0), 1),
TRUE ~ NA_real_
)
)
# Prepare a reusable table for Live-state Abbott-corrected viability with per-vial totals
df_viability_corrected <- df_proportions_dose %>%
filter(State == "Live") %>%
select(Date, Yard, Hive, Vial, Dose_numeric, Abbott_corrected) %>%
left_join(
df_dose %>%
group_by(Date, Yard, Hive, Vial) %>%
summarise(Total = sum(Total, na.rm = TRUE), .groups = 'drop'),
by = c("Date", "Yard", "Hive", "Vial")
) %>%
mutate(Corrected_Viability = pmin(pmax(Abbott_corrected, 0), 1)) %>%
filter(!is.na(Corrected_Viability))
## (Removed global logistic regression section to focus on yard/hive-specific analyses)
# Build per-vial table of corrected viability for the Live state and attach weights (Total mites)
live_corrected <- df_proportions_dose %>%
filter(State == "Live") %>%
select(Date, Yard, Hive, Vial, Dose_numeric, Proportion, Control_Proportion, Abbott_corrected) %>%
left_join(
df_dose %>%
group_by(Date, Yard, Hive, Vial) %>%
summarise(Total = sum(Total, na.rm = TRUE), .groups = 'drop'),
by = c("Date", "Yard", "Hive", "Vial")
) %>%
mutate(
Corrected_Viability = pmin(pmax(Abbott_corrected, 0), 1)
) %>%
filter(!is.na(Corrected_Viability))
# Fit GLM on corrected viability with binomial family and weights = Total, excluding dose 0
if (nrow(live_corrected %>% filter(Dose_numeric > 0)) > 2) {
dose_response_model <- glm(
Corrected_Viability ~ log10(Dose_numeric),
data = live_corrected %>% filter(Dose_numeric > 0),
weights = Total,
family = binomial(link = "logit")
)
# suppress printing of the full model summary in the HTML output
# Helper to compute LDx given target viability proportion v (0<v<1)
compute_LD_from_viability <- function(v, coef_vec) {
a <- coef_vec[1]
b <- coef_vec[2]
if (is.na(a) || is.na(b) || b == 0) return(NA_real_)
log10_d <- (qlogis(v) - a) / b
10^log10_d
}
coefs <- coef(dose_response_model)
# LDx where viability equals 5%, 10%, 50%
ld5 <- compute_LD_from_viability(0.05, coefs)
ld10 <- compute_LD_from_viability(0.10, coefs)
ld50 <- compute_LD_from_viability(0.50, coefs)
# suppress printing raw LDs here (reported later per yard/hive)
# Prepare per‑yard prediction lines for plotting
pred_lines_all <- live_corrected %>%
filter(Dose_numeric > 0) %>% # Exclude dose 0 for GLM fitting
group_by(Yard) %>%
group_modify(~{
df_h <- .x
if (nrow(df_h) < 3 || length(unique(df_h$Dose_numeric)) < 2) return(tibble(Dose_numeric = numeric(0), Predicted_Viability = numeric(0), Yard = df_h$Yard[1]))
m <- tryCatch(glm(Corrected_Viability ~ log10(Dose_numeric), data = df_h, weights = Total, family = binomial(link = "logit")), error=function(e) NULL)
if (is.null(m)) return(tibble(Dose_numeric = numeric(0), Predicted_Viability = numeric(0), Yard = df_h$Yard[1]))
dmin <- min(df_h$Dose_numeric); dmax <- max(df_h$Dose_numeric)
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
tibble(Dose_numeric = dseq, Predicted_Viability = predict(m, newdata = data.frame(Dose_numeric = dseq), type = "response"), Yard = df_h$Yard[1])
})
# Calculate per-yard LDs
yard_ld_calculations <- live_corrected %>%
filter(Dose_numeric > 0) %>%
group_by(Yard) %>%
group_modify(~{
df_h <- .x
if (nrow(df_h) < 3 || length(unique(df_h$Dose_numeric)) < 2) {
return(tibble(LD5 = NA_real_, LD10 = NA_real_, LD50 = NA_real_, n_points = nrow(df_h), dose_range = paste(range(df_h$Dose_numeric), collapse = " to ")))
}
m <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric), data = df_h, weights = Total, family = binomial(link = "logit")),
error = function(e) NULL
)
if (is.null(m)) {
return(tibble(LD5 = NA_real_, LD10 = NA_real_, LD50 = NA_real_, n_points = nrow(df_h), dose_range = paste(range(df_h$Dose_numeric), collapse = " to ")))
}
coefs <- coef(m)
a <- coefs[1]; b <- coefs[2]
if (is.na(a) || is.na(b) || b == 0) {
return(tibble(LD5 = NA_real_, LD10 = NA_real_, LD50 = NA_real_, n_points = nrow(df_h), dose_range = paste(range(df_h$Dose_numeric), collapse = " to ")))
}
ld5 <- 10^((qlogis(0.05) - a) / b)
ld10 <- 10^((qlogis(0.10) - a) / b)
ld50 <- 10^((qlogis(0.50) - a) / b)
tibble(LD5 = ld5, LD10 = ld10, LD50 = ld50, n_points = nrow(df_h), dose_range = paste(range(df_h$Dose_numeric), collapse = " to "))
}) %>%
ungroup()
# Prepare mean ± SE data for all yards
all_yards_summary <- live_corrected %>%
filter(Dose_numeric > 0) %>%
group_by(Yard, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Yard, Dose_numeric)
p_fit <- ggplot() +
geom_point(
data = all_yards_summary,
aes(x = Dose_numeric, y = mean_viability, color = Yard),
size = 3, shape = 16
) +
geom_errorbar(
data = all_yards_summary,
aes(x = Dose_numeric, ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability), color = Yard),
width = 0.1, linewidth = 0.8
) +
geom_line(
data = pred_lines_all,
aes(x = Dose_numeric, y = Predicted_Viability, color = Yard),
linewidth = 1.2
) +
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10(breaks = c(0.01, 0.1, 1, 10, 100, 1000), labels = c("0.01", "0.1", "1", "10", "100", "1000")) +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = "Amitraz Dose–Response on Abbott-corrected Viability (Live)",
x = "Dose (µg/vial, log scale)",
y = "Abbott-corrected Viability",
color = "Yard"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10)
)
print(p_fit)
cat("\n**Figure 1.** Dose-response curves for Abbott-corrected viability of *Varroa destructor* mites exposed to Amitraz for 3 hours. Data points represent mean ± standard error of viability at each dose tested. Solid lines show fitted logistic regression models for each apiary. Horizontal dashed lines indicate reference viability levels (5%, 10%, 50%). Zur_Suheita (blue) shows highest sensitivity with rapid viability decline, Raabane_Ein_Kinya (red) exhibits intermediate sensitivity, and Shamir (green) demonstrates lowest sensitivity (highest resistance) to Amitraz treatment.\n")
# Create a nicely formatted LD summary table
cat("\n**Viable Dose Summary by Yard:**\n")
cat("VD values represent doses (µg/vial) where VIABILITY equals 5%, 10%, and 50% (VD5=dose at 5% viability, etc.)\n\n")
ld_summary_table <- yard_ld_calculations %>%
mutate(
across(c(LD5, LD10, LD50), ~round(., 2)),
dose_range = paste0("(", dose_range, ")")
) %>%
select(Yard, LD5, LD10, LD50, n_points, dose_range) %>%
rename(VD5 = LD5, VD10 = LD10, VD50 = LD50, n = n_points, `Dose range` = dose_range)
# Render as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
ld_summary_table,
format = "html",
col.names = c("Yard", "VD5", "VD10", "VD50", "n", "Dose range"),
align = c('l','r','r','r','r','l')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(ld_summary_table)
}
} else {
cat("\nNot enough corrected data points to fit the logistic regression model.\n")
}

##
## **Figure 1.** Dose-response curves for Abbott-corrected viability of *Varroa destructor* mites exposed to Amitraz for 3 hours. Data points represent mean ± standard error of viability at each dose tested. Solid lines show fitted logistic regression models for each apiary. Horizontal dashed lines indicate reference viability levels (5%, 10%, 50%). Zur_Suheita (blue) shows highest sensitivity with rapid viability decline, Raabane_Ein_Kinya (red) exhibits intermediate sensitivity, and Shamir (green) demonstrates lowest sensitivity (highest resistance) to Amitraz treatment.
##
## **Viable Dose Summary by Yard:**
## VD values represent doses (µg/vial) where VIABILITY equals 5%, 10%, and 50% (VD5=dose at 5% viability, etc.)
|
Yard
|
VD5
|
VD10
|
VD50
|
n
|
Dose range
|
|
Dan_Zaatar
|
NA
|
NA
|
NA
|
3
|
(10 to 10)
|
|
Raabane_Ein_Kinya
|
8.92
|
1.40
|
0.01
|
28
|
(0.01 to 5)
|
|
Shamir
|
55147.21
|
6254.96
|
10.39
|
60
|
(0.01 to 2000)
|
|
Zur_Suheita
|
40.16
|
4.85
|
0.01
|
50
|
(0.01 to 10)
|
Shamir yard: dose–response and LDs per hive
# Focus on Shamir yard; reuse Abbott-corrected viability prepared earlier
# Build per-vial corrected viability for Live state in Shamir and attach per-vial totals
live_corrected_shamir <- df_viability_corrected %>%
filter(Yard == "Shamir", Dose_numeric > 0, Total > 0, Hive != "3_4") # Exclude mixed-hive experiments
# Fit GLM per hive
fit_per_hive <- live_corrected_shamir %>%
group_by(Hive) %>%
group_modify(~{
df_h <- .x
if (nrow(df_h) < 3 || length(unique(df_h$Dose_numeric)) < 2) return(tibble(model = list(NULL)))
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric), data = df_h, weights = Total, family = binomial(link = "logit")),
error = function(e) NULL
)
tibble(model = list(model))
})
# Compute LDs per hive
compute_LD_v <- function(model, v) {
if (is.null(model)) return(NA_real_)
coefs <- coef(model)
a <- coefs[1]; b <- coefs[2]
if (is.na(a) || is.na(b) || b == 0) return(NA_real_)
10^((qlogis(v) - a) / b)
}
# Compute Viable Doses (with reasonable extrapolation)
compute_VD_extrapolated <- function(model, v, dmin, dmax) {
if (is.null(model) || is.na(dmin) || is.na(dmax) || dmin <= 0 || dmax <= 0 || dmin >= dmax) return(NA_real_)
# First try within observed range
f <- function(logd) {
predict(model, newdata = data.frame(Dose_numeric = 10^logd), type = "response") - v
}
# Check if target is achievable within observed range
f_lo <- tryCatch(f(log10(dmin)), error = function(e) NA_real_)
f_hi <- tryCatch(f(log10(dmax)), error = function(e) NA_real_)
if (any(is.na(c(f_lo, f_hi)))) return(NA_real_)
# If target is within range, use uniroot
if (sign(f_lo) != sign(f_hi)) {
root <- tryCatch(uniroot(f, interval = c(log10(dmin), log10(dmax)))$root, error = function(e) NA_real_)
if (!is.na(root)) return(10^root)
}
# If not achievable within range, try reasonable extrapolation
# Extend range by 2 log units in both directions for more aggressive extrapolation
extended_min <- max(0.0001, dmin / 100) # Don't go below 0.0001
extended_max <- min(100000, dmax * 100) # Don't go above 100000
# Check if target is achievable in extended range
f_ext_lo <- tryCatch(f(log10(extended_min)), error = function(e) NA_real_)
f_ext_hi <- tryCatch(f(log10(extended_max)), error = function(e) NA_real_)
if (any(is.na(c(f_ext_lo, f_ext_hi)))) return(NA_real_)
if (sign(f_ext_lo) != sign(f_ext_hi)) {
root <- tryCatch(uniroot(f, interval = c(log10(extended_min), log10(extended_max)))$root, error = function(e) NA_real_)
if (!is.na(root)) return(10^root)
}
# If still not achievable, try even more aggressive extrapolation
# This handles cases where the model asymptote is above the target viability
very_extended_min <- max(0.00001, dmin / 1000) # Very low doses
very_extended_max <- min(1000000, dmax * 1000) # Very high doses
f_very_lo <- tryCatch(f(log10(very_extended_min)), error = function(e) NA_real_)
f_very_hi <- tryCatch(f(log10(very_extended_max)), error = function(e) NA_real_)
if (any(is.na(c(f_very_lo, f_very_hi)))) return(NA_real_)
if (sign(f_very_lo) != sign(f_very_hi)) {
root <- tryCatch(uniroot(f, interval = c(log10(very_extended_min), log10(very_extended_max)))$root, error = function(e) NA_real_)
if (!is.na(root)) return(10^root)
}
# If still not achievable, try the simple formula as last resort
# This handles cases where the model is well-behaved but extrapolation fails
coefs <- coef(model)
if (length(coefs) >= 2 && !is.na(coefs[2]) && coefs[2] != 0) {
simple_result <- 10^((qlogis(v) - coefs[1]) / coefs[2])
# Only use if result is reasonable (between 0.00001 and 1000000)
if (!is.na(simple_result) && simple_result > 0.00001 && simple_result < 1000000) {
return(simple_result)
}
}
# If all methods fail, return NA
return(NA_real_)
}
# Join dose ranges per hive
shamir_ranges <- live_corrected_shamir %>% group_by(Hive) %>% summarise(dmin = min(Dose_numeric), dmax = max(Dose_numeric), .groups = 'drop')
# Debug: Check Hive 3 data specifically
cat("\n**Debug: Hive 3 Shamir data:**\n")
##
## **Debug: Hive 3 Shamir data:**
hive3_data <- live_corrected_shamir %>% filter(Hive == "3")
hive3_model <- fit_per_hive %>% filter(Hive == "3") %>% pull(model) %>% .[[1]]
hive3_range <- shamir_ranges %>% filter(Hive == "3")
cat("Dose range:", hive3_range$dmin, "to", hive3_range$dmax, "\n")
## Dose range: 0.01 to 2000
cat("Number of data points:", nrow(hive3_data), "\n")
## Number of data points: 22
cat("Model exists:", !is.null(hive3_model), "\n")
## Model exists: TRUE
if (!is.null(hive3_model)) {
# Check viability range
cat("Viability range:", round(min(hive3_data$Corrected_Viability), 3), "to", round(max(hive3_data$Corrected_Viability), 3), "\n")
# Test predictions at dose range
pred_min <- predict(hive3_model, newdata = data.frame(Dose_numeric = hive3_range$dmin), type = "response")
pred_max <- predict(hive3_model, newdata = data.frame(Dose_numeric = hive3_range$dmax), type = "response")
cat("Predicted viability at min dose:", round(pred_min, 3), "\n")
cat("Predicted viability at max dose:", round(pred_max, 3), "\n")
# Test VD calculations manually
vd5_manual <- compute_VD_extrapolated(hive3_model, 0.05, hive3_range$dmin, hive3_range$dmax)
vd10_manual <- compute_VD_extrapolated(hive3_model, 0.10, hive3_range$dmin, hive3_range$dmax)
cat("Manual VD5:", vd5_manual, "\n")
cat("Manual VD10:", vd10_manual, "\n")
}
## Viability range: 0 to 1
## Predicted viability at min dose: 0.864
## Predicted viability at max dose: 0.151
## Manual VD5: 129753
## Manual VD10: 10078.94
ld_table <- fit_per_hive %>%
left_join(shamir_ranges, by = "Hive") %>%
mutate(
VD5 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.05, ..2, ..3)),
VD10 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.10, ..2, ..3)),
VD50 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.50, ..2, ..3))
) %>%
select(Hive, VD5, VD10, VD50)
cat("\n**VD estimates (viability = 5%, 10%, 50%) by hive (Shamir):**\n")
##
## **VD estimates (viability = 5%, 10%, 50%) by hive (Shamir):**
ld_table_html <- ld_table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(ld_table_html, format = "html", align = c('l','r','r','r'),
col.names = c("Hive","VD5","VD10","VD50")) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(ld_table_html)
}
|
Hive
|
VD5
|
VD10
|
VD50
|
|
1
|
991532.9093
|
125748.9670
|
289.993684
|
|
3
|
129753.0356
|
10078.9433
|
5.499867
|
|
4
|
525.1695
|
288.8162
|
49.768309
|
|
6
|
2556.1228
|
625.5240
|
9.965903
|
# Summary statistics per hive (compute then render)
shamir_summary <- live_corrected_shamir %>%
group_by(Hive) %>%
summarise(
n_vials = n(),
total_mites = sum(Total, na.rm = TRUE),
mean_mites_per_vial = round(mean(Total, na.rm = TRUE), 1),
dose_range = paste0("(", round(min(Dose_numeric), 3), " - ", round(max(Dose_numeric), 1), ")"),
.groups = 'drop'
)
cat("\n**Shamir yard summary statistics per hive:**\n")
##
## **Shamir yard summary statistics per hive:**
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(shamir_summary, format = "html", align = c('l','r','r','r','l')) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(shamir_summary)
}
|
Hive
|
n_vials
|
total_mites
|
mean_mites_per_vial
|
dose_range
|
|
1
|
10
|
100
|
10
|
(1 - 2000)
|
|
3
|
22
|
220
|
10
|
(0.01 - 2000)
|
|
4
|
6
|
60
|
10
|
(1 - 2000)
|
|
6
|
18
|
180
|
10
|
(1 - 2000)
|
# Dose-level summary for Shamir yard (compute then render)
shamir_dose_summary <- live_corrected_shamir %>%
group_by(Dose_numeric) %>%
summarise(
n_vials = n(),
total_mites = sum(Total, na.rm = TRUE),
mean_viability = round(mean(Corrected_Viability, na.rm = TRUE) * 100, 1),
se_viability = round((sd(Corrected_Viability, na.rm = TRUE) / sqrt(n())) * 100, 1),
.groups = 'drop'
) %>%
arrange(Dose_numeric)
cat("\n**Shamir yard dose-level summary:**\n")
##
## **Shamir yard dose-level summary:**
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(shamir_dose_summary, format = "html", align = c('r','r','r','r')) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(shamir_dose_summary)
}
|
Dose_numeric
|
n_vials
|
total_mites
|
mean_viability
|
se_viability
|
|
1e-02
|
1
|
10
|
40.0
|
NA
|
|
1e-01
|
2
|
20
|
90.0
|
10.0
|
|
1e+00
|
10
|
101
|
78.4
|
8.6
|
|
1e+01
|
14
|
139
|
55.2
|
8.9
|
|
5e+01
|
7
|
70
|
45.1
|
9.8
|
|
1e+02
|
8
|
80
|
28.3
|
7.1
|
|
1e+03
|
7
|
70
|
11.2
|
8.5
|
|
2e+03
|
7
|
70
|
14.3
|
14.3
|
# Prepare LD lines (long format) for plotting
ld_lines_shamir <- ld_table %>%
tidyr::pivot_longer(cols = c(VD5, VD10, VD50), names_to = "LD", values_to = "dose") %>%
filter(!is.na(dose))
# Create prediction grid per hive for plotting
pred_lines <- live_corrected_shamir %>%
group_by(Hive) %>%
summarise(dose_min = min(Dose_numeric), dose_max = max(Dose_numeric), .groups = 'drop') %>%
inner_join(fit_per_hive, by = "Hive") %>%
mutate(grid = pmap(list(dose_min, dose_max, model), function(dmin, dmax, m) {
if (is.null(m)) return(tibble(Dose_numeric = numeric(0), Pred = numeric(0)))
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
tibble(Dose_numeric = dseq, Pred = predict(m, newdata = data.frame(Dose_numeric = dseq), type = "response"))
})) %>%
select(Hive, grid) %>%
unnest(grid)
# Prepare mean ± SE data for Shamir hives
shamir_hives_summary <- live_corrected_shamir %>%
group_by(Hive, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Hive, Dose_numeric)
# Plot per-hive regression lines and observations
p_shamir <- ggplot() +
geom_point(
data = shamir_hives_summary,
aes(x = Dose_numeric, y = mean_viability, color = Hive),
size = 3, shape = 16
) +
geom_errorbar(
data = shamir_hives_summary,
aes(x = Dose_numeric, ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability), color = Hive),
width = 0.1, linewidth = 0.8
) +
geom_line(
data = pred_lines,
aes(x = Dose_numeric, y = Pred, color = Hive),
linewidth = 1.2
) +
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10(breaks = c(0.01, 0.1, 1, 10, 100, 1000), labels = c("0.01", "0.1", "1", "10", "100", "1000")) +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = "Shamir yard: Abbott-corrected viability by hive",
x = "Dose (µg/vial, log scale)",
y = "Abbott-corrected Viability",
color = "Hive"
) +
theme_minimal() +
theme(plot.title = element_text(size = 14, face = "bold"))
print(p_shamir)

# Publication-style dose-response plot for all hives in Shamir yard with separate regression lines
cat("\n**Publication-style dose-response plot for all hives in Shamir yard:**\n")
##
## **Publication-style dose-response plot for all hives in Shamir yard:**
# Prepare mean ± SE data for all hives in Shamir
shamir_hives_summary <- live_corrected_shamir %>%
group_by(Hive, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Hive, Dose_numeric)
# Create publication-style plot for all hives in Shamir
p_shamir_hives_pub <- ggplot(shamir_hives_summary, aes(x = log10(Dose_numeric), y = mean_viability, color = Hive)) +
geom_point(size = 3, shape = 16) +
geom_errorbar(
aes(ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability)),
width = 0.1,
linewidth = 0.8
) +
geom_line(
data = pred_lines %>%
mutate(log_dose = log10(Dose_numeric)),
aes(x = log_dose, y = Pred, color = Hive),
linewidth = 1.2
) +
scale_x_continuous(
breaks = seq(-1, 3, 1),
labels = c("0.1", "1", "10", "100", "1000"),
limits = c(-1, 3)
) +
scale_y_continuous(
limits = c(0, 1),
labels = c("0", "0.25", "0.5", "0.75", "1.0")
) +
labs(
title = "Shamir - All Hives Dose-Response",
x = "log Concentration (µg/vial)",
y = "Viability (proportion)",
color = "Hive"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10),
panel.grid.minor = element_blank(),
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
legend.position = "right"
)
print(p_shamir_hives_pub)

# Individual hive plots for Shamir arranged in one row
cat("\n**Individual hive plots for Shamir yard:**\n")
##
## **Individual hive plots for Shamir yard:**
# Get unique hives in Shamir
shamir_hives <- unique(live_corrected_shamir$Hive)
# Create individual plots for each hive
shamir_individual_plots <- list()
for (hive_id in shamir_hives) {
hive_data <- live_corrected_shamir %>% filter(Hive == hive_id)
hive_model <- fit_per_hive %>% filter(Hive == hive_id) %>% pull(model) %>% .[[1]]
if (!is.null(hive_model)) {
# Create prediction line for this hive
dmin <- min(hive_data$Dose_numeric)
dmax <- max(hive_data$Dose_numeric)
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
pred_hive <- data.frame(
Dose_numeric = dseq,
Predicted_Viability = predict(hive_model, newdata = data.frame(Dose_numeric = dseq), type = "response")
)
# Create individual hive plot
p_hive <- ggplot() +
geom_point(
data = hive_data,
aes(x = Dose_numeric, y = Corrected_Viability),
color = "blue", alpha = 0.7, shape = 16, size = 2
) +
geom_line(
data = pred_hive,
aes(x = Dose_numeric, y = Predicted_Viability),
color = "blue", linewidth = 1.0
) +
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10() +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = paste0("Hive ", hive_id),
x = "Dose (µg/vial)",
y = "Viability"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 12, face = "bold"),
axis.title = element_text(size = 10),
axis.text = element_text(size = 8),
plot.margin = margin(5, 5, 5, 5)
)
shamir_individual_plots[[hive_id]] <- p_hive
}
}
# Arrange individual plots in one row
if (length(shamir_individual_plots) > 0) {
combined_shamir_plots <- do.call(gridExtra::grid.arrange, c(shamir_individual_plots, ncol = length(shamir_individual_plots)))
print(combined_shamir_plots)
}

## TableGrob (1 x 4) "arrange": 4 grobs
## z cells name grob
## 3 1 (1-1,1-1) arrange gtable[layout]
## 4 2 (1-1,2-2) arrange gtable[layout]
## 6 3 (1-1,3-3) arrange gtable[layout]
## 1 4 (1-1,4-4) arrange gtable[layout]
# Publication-style dose-response plot for hive 4 in Shamir yard by date with separate regression lines
cat("\n**Publication-style dose-response plot for Shamir - Hive 4 by Date:**\n")
##
## **Publication-style dose-response plot for Shamir - Hive 4 by Date:**
# Prepare mean ± SE data for hive 4 by date
hive4_by_date_summary <- live_corrected_shamir %>%
filter(Hive == "4") %>%
group_by(Date, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Date, Dose_numeric)
# Fit GLM per date for hive 4
fit_per_date_hive4 <- live_corrected_shamir %>%
filter(Hive == "4") %>%
group_by(Date) %>%
group_modify(~{
df_d <- .x
if (nrow(df_d) < 3 || length(unique(df_d$Dose_numeric)) < 2) return(tibble(model = list(NULL)))
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric), data = df_d, weights = Total, family = binomial(link = "logit")),
error = function(e) NULL
)
tibble(model = list(model))
})
# Create prediction lines for each date
pred_lines_hive4_by_date <- live_corrected_shamir %>%
filter(Hive == "4") %>%
group_by(Date) %>%
summarise(dose_min = min(Dose_numeric), dose_max = max(Dose_numeric), .groups = 'drop') %>%
inner_join(fit_per_date_hive4, by = "Date") %>%
mutate(grid = pmap(list(dose_min, dose_max, model), function(dmin, dmax, m) {
if (is.null(m)) return(tibble(Dose_numeric = numeric(0), Pred = numeric(0)))
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
tibble(Dose_numeric = dseq, Pred = predict(m, newdata = data.frame(Dose_numeric = dseq), type = "response"))
})) %>%
select(Date, grid) %>%
unnest(grid)
# Create publication-style plot for hive 4 by date
p_hive4_by_date_pub <- ggplot(hive4_by_date_summary, aes(x = log10(Dose_numeric), y = mean_viability, color = Date)) +
geom_point(size = 3, shape = 16) +
geom_errorbar(
aes(ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability)),
width = 0.1,
linewidth = 0.8
) +
geom_line(
data = pred_lines_hive4_by_date %>%
mutate(log_dose = log10(Dose_numeric)),
aes(x = log_dose, y = Pred, color = Date),
linewidth = 1.2
) +
scale_x_continuous(
breaks = seq(-1, 3, 1),
labels = c("0.1", "1", "10", "100", "1000"),
limits = c(-1, 3)
) +
scale_y_continuous(
limits = c(0, 1),
labels = c("0", "0.25", "0.5", "0.75", "1.0")
) +
labs(
title = "Shamir - Hive 4 Dose-Response by Date",
x = "log Concentration (µg/vial)",
y = "Viability (proportion)",
color = "Date"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10),
panel.grid.minor = element_blank(),
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
legend.position = "right"
)
print(p_hive4_by_date_pub)

# Publication-style dose-response plot for hive 6 in Shamir yard by date with separate regression lines
cat("\n**Publication-style dose-response plot for Shamir - Hive 6 by Date:**\n")
##
## **Publication-style dose-response plot for Shamir - Hive 6 by Date:**
# Prepare mean ± SE data for hive 6 by date
hive6_by_date_summary <- live_corrected_shamir %>%
filter(Hive == "6") %>%
group_by(Date, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Date, Dose_numeric)
# Fit GLM per date for hive 6
fit_per_date_hive6 <- live_corrected_shamir %>%
filter(Hive == "6") %>%
group_by(Date) %>%
group_modify(~{
df_d <- .x
if (nrow(df_d) < 3 || length(unique(df_d$Dose_numeric)) < 2) return(tibble(model = list(NULL)))
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric), data = df_d, weights = Total, family = binomial(link = "logit")),
error = function(e) NULL
)
tibble(model = list(model))
})
# Create prediction lines for each date
pred_lines_hive6_by_date <- live_corrected_shamir %>%
filter(Hive == "6") %>%
group_by(Date) %>%
summarise(dose_min = min(Dose_numeric), dose_max = max(Dose_numeric), .groups = 'drop') %>%
inner_join(fit_per_date_hive6, by = "Date") %>%
mutate(grid = pmap(list(dose_min, dose_max, model), function(dmin, dmax, m) {
if (is.null(m)) return(tibble(Dose_numeric = numeric(0), Pred = numeric(0)))
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
tibble(Dose_numeric = dseq, Pred = predict(m, newdata = data.frame(Dose_numeric = dseq), type = "response"))
})) %>%
select(Date, grid) %>%
unnest(grid)
# Create publication-style plot for hive 6 by date
p_hive6_by_date_pub <- ggplot(hive6_by_date_summary, aes(x = log10(Dose_numeric), y = mean_viability, color = Date)) +
geom_point(size = 3, shape = 16) +
geom_errorbar(
aes(ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability)),
width = 0.1,
linewidth = 0.8
) +
geom_line(
data = pred_lines_hive6_by_date %>%
mutate(log_dose = log10(Dose_numeric)),
aes(x = log_dose, y = Pred, color = Date),
linewidth = 1.2
) +
scale_x_continuous(
breaks = seq(-1, 3, 1),
labels = c("0.1", "1", "10", "100", "1000"),
limits = c(-1, 3)
) +
scale_y_continuous(
limits = c(0, 1),
labels = c("0", "0.25", "0.5", "0.75", "1.0")
) +
labs(
title = "Shamir - Hive 6 Dose-Response by Date",
x = "log Concentration (µg/vial)",
y = "Viability (proportion)",
color = "Date"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10),
panel.grid.minor = element_blank(),
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
legend.position = "right"
)
print(p_hive6_by_date_pub)

Raabane_Ein_Kinya yard: dose–response and LDs per hive
# Focus on Raabane_Ein_Kinya yard; reuse Abbott-corrected viability prepared earlier
# Build per-vial corrected viability for Live state in Raabane_Ein_Kinya and attach per-vial totals
live_corrected_raabane <- df_viability_corrected %>%
filter(Yard == "Raabane_Ein_Kinya", Dose_numeric > 0, Total > 0)
# Fit GLM per hive
fit_per_hive_raabane <- live_corrected_raabane %>%
group_by(Hive) %>%
group_modify(~{
df_h <- .x
if (nrow(df_h) < 3 || length(unique(df_h$Dose_numeric)) < 2) return(tibble(model = list(NULL)))
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric), data = df_h, weights = Total, family = binomial(link = "logit")),
error = function(e) NULL
)
tibble(model = list(model))
})
# Compute LDs per hive
ld_table_raabane <- fit_per_hive_raabane %>%
mutate(
LD5 = map_dbl(model, ~compute_LD_v(.x, 0.05)),
LD10 = map_dbl(model, ~compute_LD_v(.x, 0.10)),
LD50 = map_dbl(model, ~compute_LD_v(.x, 0.50))
) %>%
select(Hive, LD5, LD10, LD50)
cat("\n**VD estimates (viability = 5%, 10%, 50%) by hive (Raabane_Ein_Kinya):**\n")
##
## **VD estimates (viability = 5%, 10%, 50%) by hive (Raabane_Ein_Kinya):**
ld_table_raabane_html <- ld_table_raabane
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(ld_table_raabane_html, format = "html", align = c('l','r','r','r')) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(ld_table_raabane_html)
}
|
Hive
|
LD5
|
LD10
|
LD50
|
|
2
|
40.173334
|
2.8910395
|
0.0012599
|
|
3
|
3.454109
|
0.8518582
|
0.0138868
|
# Summary statistics per hive (compute then render)
raabane_summary <- live_corrected_raabane %>%
group_by(Hive) %>%
summarise(
n_vials = n(),
total_mites = sum(Total, na.rm = TRUE),
mean_mites_per_vial = round(mean(Total, na.rm = TRUE), 1),
dose_range = paste0("(", round(min(Dose_numeric), 3), " - ", round(max(Dose_numeric), 1), ")"),
.groups = 'drop'
)
cat("\n**Raabane_Ein_Kinya yard summary statistics per hive:**\n")
##
## **Raabane_Ein_Kinya yard summary statistics per hive:**
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(raabane_summary, format = "html", align = c('l','r','r','r','l')) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(raabane_summary)
}
|
Hive
|
n_vials
|
total_mites
|
mean_mites_per_vial
|
dose_range
|
|
2
|
15
|
149
|
9.9
|
(0.01 - 5)
|
|
3
|
13
|
130
|
10.0
|
(0.01 - 5)
|
# Dose-level summary for Raabane_Ein_Kinya yard (compute then render)
raabane_dose_summary <- live_corrected_raabane %>%
group_by(Dose_numeric) %>%
summarise(
n_vials = n(),
total_mites = sum(Total, na.rm = TRUE),
mean_viability = round(mean(Corrected_Viability, na.rm = TRUE) * 100, 1),
se_viability = round((sd(Corrected_Viability, na.rm = TRUE) / sqrt(n())) * 100, 1),
.groups = 'drop'
) %>%
arrange(Dose_numeric)
cat("\n**Raabane_Ein_Kinya yard dose-level summary:**\n")
##
## **Raabane_Ein_Kinya yard dose-level summary:**
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(raabane_dose_summary, format = "html", align = c('r','r','r','r')) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(raabane_dose_summary)
}
|
Dose_numeric
|
n_vials
|
total_mites
|
mean_viability
|
se_viability
|
|
0.01
|
6
|
60
|
48.3
|
7.9
|
|
0.10
|
6
|
60
|
18.3
|
5.4
|
|
0.50
|
2
|
20
|
10.0
|
0.0
|
|
1.00
|
4
|
40
|
20.0
|
9.1
|
|
2.50
|
4
|
40
|
2.5
|
2.5
|
|
5.00
|
6
|
59
|
8.3
|
4.0
|
# Create prediction grid per hive for plotting
pred_lines_raabane <- live_corrected_raabane %>%
group_by(Hive) %>%
summarise(dose_min = min(Dose_numeric), dose_max = max(Dose_numeric), .groups = 'drop') %>%
inner_join(fit_per_hive_raabane, by = "Hive") %>%
mutate(grid = pmap(list(dose_min, dose_max, model), function(dmin, dmax, m) {
if (is.null(m)) return(tibble(Dose_numeric = numeric(0), Pred = numeric(0)))
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
tibble(Dose_numeric = dseq, Pred = predict(m, newdata = data.frame(Dose_numeric = dseq), type = "response"))
})) %>%
select(Hive, grid) %>%
unnest(grid)
# Prepare mean ± SE data for Raabane hives
raabane_hives_summary <- live_corrected_raabane %>%
group_by(Hive, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Hive, Dose_numeric)
# Plot per-hive regression lines and observations
p_raabane <- ggplot() +
geom_point(
data = raabane_hives_summary,
aes(x = Dose_numeric, y = mean_viability, color = Hive),
size = 3, shape = 16
) +
geom_errorbar(
data = raabane_hives_summary,
aes(x = Dose_numeric, ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability), color = Hive),
width = 0.1, linewidth = 0.8
) +
geom_line(
data = pred_lines_raabane,
aes(x = Dose_numeric, y = Pred, color = Hive),
linewidth = 1.2
) +
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10(breaks = c(0.01, 0.1, 1, 10, 100, 1000), labels = c("0.01", "0.1", "1", "10", "100", "1000")) +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = "Raabane_Ein_Kinya yard: Abbott-corrected viability by hive",
x = "Dose (µg/vial, log scale)",
y = "Abbott-corrected Viability",
color = "Hive"
) +
theme_minimal() +
theme(plot.title = element_text(size = 14, face = "bold"))
print(p_raabane)

# Individual hive plots for Raabane arranged in one row
cat("\n**Individual hive plots for Raabane_Ein_Kinya yard:**\n")
##
## **Individual hive plots for Raabane_Ein_Kinya yard:**
# Get unique hives in Raabane
raabane_hives <- unique(live_corrected_raabane$Hive)
# Create individual plots for each hive
raabane_individual_plots <- list()
for (hive_id in raabane_hives) {
hive_data <- live_corrected_raabane %>% filter(Hive == hive_id)
hive_model <- fit_per_hive_raabane %>% filter(Hive == hive_id) %>% pull(model) %>% .[[1]]
if (!is.null(hive_model)) {
# Create prediction line for this hive
dmin <- min(hive_data$Dose_numeric)
dmax <- max(hive_data$Dose_numeric)
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
pred_hive <- data.frame(
Dose_numeric = dseq,
Predicted_Viability = predict(hive_model, newdata = data.frame(Dose_numeric = dseq), type = "response")
)
# Create individual hive plot
p_hive <- ggplot() +
geom_point(
data = hive_data,
aes(x = Dose_numeric, y = Corrected_Viability),
color = "red", alpha = 0.7, shape = 16, size = 2
) +
geom_line(
data = pred_hive,
aes(x = Dose_numeric, y = Predicted_Viability),
color = "red", linewidth = 1.0
) +
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10() +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = paste0("Hive ", hive_id),
x = "Dose (µg/vial)",
y = "Viability"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 12, face = "bold"),
axis.title = element_text(size = 10),
axis.text = element_text(size = 8),
plot.margin = margin(5, 5, 5, 5)
)
raabane_individual_plots[[hive_id]] <- p_hive
}
}
# Arrange individual plots in one row
if (length(raabane_individual_plots) > 0) {
combined_raabane_plots <- do.call(gridExtra::grid.arrange, c(raabane_individual_plots, ncol = length(raabane_individual_plots)))
print(combined_raabane_plots)
}

## TableGrob (1 x 2) "arrange": 2 grobs
## z cells name grob
## 2 1 (1-1,1-1) arrange gtable[layout]
## 3 2 (1-1,2-2) arrange gtable[layout]
# Compute VD bounded for Raabane
raabane_ranges <- live_corrected_raabane %>% group_by(Hive) %>% summarise(dmin = min(Dose_numeric), dmax = max(Dose_numeric), .groups = 'drop')
ld_table_raabane <- fit_per_hive_raabane %>%
left_join(raabane_ranges, by = "Hive") %>%
mutate(
VD5 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.05, ..2, ..3)),
VD10 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.10, ..2, ..3)),
VD50 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.50, ..2, ..3))
) %>%
select(Hive, VD5, VD10, VD50)
# (Removed premature Zur VD computation; handled in Zur chunk below)
Zur_Suheita yard: dose–response and LDs per hive
# Focus on Zur_Suheita yard; reuse Abbott-corrected viability prepared earlier
# Build per-vial corrected viability for Live state in Zur_Suheita and attach per-vial totals
live_corrected_zur <- df_viability_corrected %>%
filter(Yard == "Zur_Suheita", Dose_numeric > 0, Total > 0)# %>%
# filter(Hive == "3") #%>% # keep only hive 3, as hives 7 and 9 were tested 24 hours post collection, and the mites were not in good conditon
# filter(Date == "05-08-25") # keep only the date of collection. for the same reason
# Fit GLM per hive
fit_per_hive_zur <- live_corrected_zur %>%
group_by(Hive) %>%
group_modify(~{
df_h <- .x
if (nrow(df_h) < 3 || length(unique(df_h$Dose_numeric)) < 2) return(tibble(model = list(NULL)))
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric), data = df_h, weights = Total, family = binomial(link = "logit")),
error = function(e) NULL
)
tibble(model = list(model))
})
# Compute bounded VD per hive (within observed dose range)
zur_ranges <- live_corrected_zur %>% group_by(Hive) %>% summarise(dmin = min(Dose_numeric), dmax = max(Dose_numeric), .groups = 'drop')
ld_table_zur <- fit_per_hive_zur %>%
left_join(zur_ranges, by = "Hive") %>%
mutate(
VD5 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.05, ..2, ..3)),
VD10 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.10, ..2, ..3)),
VD50 = pmap_dbl(list(model, dmin, dmax), ~compute_VD_extrapolated(..1, 0.50, ..2, ..3))
) %>%
select(Hive, VD5, VD10, VD50)
cat("\n**VD estimates (viability = 5%, 10%, 50%) by hive (Zur_Suheita):**\n")
##
## **VD estimates (viability = 5%, 10%, 50%) by hive (Zur_Suheita):**
ld_table_zur_html <- ld_table_zur
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(ld_table_zur_html, format = "html", align = c('l','r','r','r')) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(ld_table_zur_html)
}
|
Hive
|
VD5
|
VD10
|
VD50
|
|
3
|
70.3834129
|
9.6730175
|
0.0282546
|
|
5
|
NA
|
NA
|
NA
|
|
7
|
0.0148820
|
0.0126596
|
0.0078677
|
|
9
|
0.0163165
|
0.0145443
|
0.0103696
|
# Summary statistics per hive (compute then render)
zur_summary <- live_corrected_zur %>%
group_by(Hive) %>%
summarise(
n_vials = n(),
total_mites = sum(Total, na.rm = TRUE),
mean_mites_per_vial = round(mean(Total, na.rm = TRUE), 1),
"dose_range_ug/vial" = paste0("(", round(min(Dose_numeric), 3), " - ", round(max(Dose_numeric), 1), ")"),
.groups = 'drop'
)
cat("\n**Zur_Suheita yard summary statistics per hive:**\n")
##
## **Zur_Suheita yard summary statistics per hive:**
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(zur_summary, format = "html", align = c('l','r','r','r','l')) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(zur_summary)
}
|
Hive
|
n_vials
|
total_mites
|
mean_mites_per_vial
|
dose_range_ug/vial
|
|
3
|
37
|
370
|
10
|
(0.01 - 10)
|
|
5
|
2
|
20
|
10
|
(0.01 - 10)
|
|
7
|
7
|
70
|
10
|
(0.01 - 5)
|
|
9
|
4
|
40
|
10
|
(0.01 - 10)
|
# Dose-level summary for Zur_Suheita yard (compute then render)
zur_dose_summary <- live_corrected_zur %>%
group_by(Dose_numeric) %>%
summarise(
n_vials = n(),
total_mites = sum(Total, na.rm = TRUE),
mean_viability = round(mean(Corrected_Viability, na.rm = TRUE) * 100, 1),
se_viability = round((sd(Corrected_Viability, na.rm = TRUE) / sqrt(n())) * 100, 1),
.groups = 'drop'
) %>%
arrange(Dose_numeric)
cat("\n**Zur_Suheita yard dose-level summary:**\n")
##
## **Zur_Suheita yard dose-level summary:**
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(zur_dose_summary, format = "html", align = c('r','r','r','r')) %>%
kableExtra::kable_styling(full_width = FALSE, bootstrap_options = c("striped","hover","condensed")) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(zur_dose_summary)
}
|
Dose_numeric
|
n_vials
|
total_mites
|
mean_viability
|
se_viability
|
|
0.01
|
13
|
130
|
45.2
|
11.4
|
|
0.10
|
8
|
80
|
51.7
|
18.3
|
|
0.50
|
6
|
60
|
0.0
|
0.0
|
|
1.00
|
5
|
50
|
27.0
|
8.7
|
|
2.50
|
6
|
60
|
0.0
|
0.0
|
|
5.00
|
6
|
60
|
0.0
|
0.0
|
|
10.00
|
6
|
60
|
22.5
|
10.8
|
# Create prediction grid per hive for plotting
pred_lines_zur <- live_corrected_zur %>%
group_by(Hive) %>%
summarise(dose_min = min(Dose_numeric), dose_max = max(Dose_numeric), .groups = 'drop') %>%
inner_join(fit_per_hive_zur, by = "Hive") %>%
mutate(grid = pmap(list(dose_min, dose_max, model), function(dmin, dmax, m) {
if (is.null(m)) return(tibble(Dose_numeric = numeric(0), Pred = numeric(0)))
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
tibble(Dose_numeric = dseq, Pred = predict(m, newdata = data.frame(Dose_numeric = dseq), type = "response"))
})) %>%
select(Hive, grid) %>%
unnest(grid)
# Prepare mean ± SE data for Zur hives
zur_hives_summary <- live_corrected_zur %>%
group_by(Hive, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Hive, Dose_numeric)
# Plot per-hive regression lines and observations
p_zur <- ggplot() +
geom_point(
data = zur_hives_summary,
aes(x = Dose_numeric, y = mean_viability, color = Hive),
size = 3, shape = 16
) +
geom_errorbar(
data = zur_hives_summary,
aes(x = Dose_numeric, ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability), color = Hive),
width = 0.1, linewidth = 0.8
) +
geom_line(
data = pred_lines_zur,
aes(x = Dose_numeric, y = Pred, color = Hive),
linewidth = 1.2
) +
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10(breaks = c(0.01, 0.1, 1, 10, 100, 1000), labels = c("0.01", "0.1", "1", "10", "100", "1000")) +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = "Zur_Suheita yard: Abbott-corrected viability by hive",
x = "Dose (µg/vial, log scale)",
y = "Abbott-corrected Viability",
color = "Hive"
) +
theme_minimal() +
theme(plot.title = element_text(size = 14, face = "bold"))
print(p_zur)

# Individual hive plots for Zur_Suheita arranged in one row
cat("\n**Individual hive plots for Zur_Suheita yard:**\n")
##
## **Individual hive plots for Zur_Suheita yard:**
# Get unique hives in Zur_Suheita
zur_hives <- unique(live_corrected_zur$Hive)
# Create individual plots for each hive
zur_individual_plots <- list()
for (hive_id in zur_hives) {
hive_data <- live_corrected_zur %>% filter(Hive == hive_id)
hive_model <- fit_per_hive_zur %>% filter(Hive == hive_id) %>% pull(model) %>% .[[1]]
if (!is.null(hive_model)) {
# Create prediction line for this hive
dmin <- min(hive_data$Dose_numeric)
dmax <- max(hive_data$Dose_numeric)
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
pred_hive <- data.frame(
Dose_numeric = dseq,
Predicted_Viability = predict(hive_model, newdata = data.frame(Dose_numeric = dseq), type = "response")
)
# Color coding: hive 3 by date, others in blue
if (hive_id == "3") {
# Hive 3: color by date
p_hive <- ggplot() +
geom_point(
data = hive_data,
aes(x = Dose_numeric, y = Corrected_Viability, color = Date),
alpha = 0.7, shape = 16, size = 2
) +
geom_line(
data = pred_hive,
aes(x = Dose_numeric, y = Predicted_Viability),
color = "blue", linewidth = 1.0
) +
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10() +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = paste0("Hive ", hive_id),
x = "Dose (µg/vial)",
y = "Viability",
color = "Date"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 12, face = "bold"),
axis.title = element_text(size = 10),
axis.text = element_text(size = 8),
plot.margin = margin(5, 5, 5, 5),
legend.position = "none" # Remove legend for cleaner individual plots
)
} else {
# Other hives: blue color
p_hive <- ggplot() +
geom_point(
data = hive_data,
aes(x = Dose_numeric, y = Corrected_Viability),
color = "blue", alpha = 0.7, shape = 16, size = 2
) +
geom_line(
data = pred_hive,
aes(x = Dose_numeric, y = Predicted_Viability),
color = "blue", linewidth = 1.0
) +
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10() +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = paste0("Hive ", hive_id),
x = "Dose (µg/vial)",
y = "Viability"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 12, face = "bold"),
axis.title = element_text(size = 10),
axis.text = element_text(size = 8),
plot.margin = margin(5, 5, 5, 5)
)
}
zur_individual_plots[[hive_id]] <- p_hive
}
}
# Arrange individual plots in one row
if (length(zur_individual_plots) > 0) {
combined_zur_plots <- do.call(gridExtra::grid.arrange, c(zur_individual_plots, ncol = length(zur_individual_plots)))
print(combined_zur_plots)
}

## TableGrob (1 x 3) "arrange": 3 grobs
## z cells name grob
## 3 1 (1-1,1-1) arrange gtable[layout]
## 9 2 (1-1,2-2) arrange gtable[layout]
## 7 3 (1-1,3-3) arrange gtable[layout]
# Publication-style dose-response plot for hive 3 (Zur yard) with mean ± SE
cat("\n**Publication-style dose-response plot for Zur_Suheita - Hive 3:**\n")
##
## **Publication-style dose-response plot for Zur_Suheita - Hive 3:**
# Prepare mean ± SE data for hive 3
hive3_summary <- live_corrected_zur %>%
filter(Hive == "3") %>%
group_by(Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Dose_numeric)
# Create publication-style plot
p_hive3_pub <- ggplot(hive3_summary, aes(x = log10(Dose_numeric), y = mean_viability)) +
geom_point(size = 3, color = "blue", shape = 16) +
geom_errorbar(
aes(ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability)),
width = 0.1,
color = "blue",
linewidth = 0.8
) +
geom_line(
data = pred_lines_zur %>%
filter(Hive == "3") %>%
mutate(log_dose = log10(Dose_numeric)),
aes(x = log_dose, y = Pred),
color = "blue",
linewidth = 1.2
) +
scale_x_continuous(
breaks = seq(-3, 1, 1),
labels = c("0.001", "0.01", "0.1", "1", "10"),
limits = c(-3, 1)
) +
scale_y_continuous(
limits = c(0, 1),
labels = c("0", "0.25", "0.5", "0.75", "1.0")
) +
labs(
title = "Zur_Suheita - Hive 3 Dose-Response",
x = "log Concentration (µg/vial)",
y = "Viability (proportion)"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10),
panel.grid.minor = element_blank(),
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5)
)
print(p_hive3_pub)

# Publication-style dose-response plot for hive 3 in Zur yard by date with separate regression lines
cat("\n**Publication-style dose-response plot for Zur_Suheita - Hive 3 by Date:**\n")
##
## **Publication-style dose-response plot for Zur_Suheita - Hive 3 by Date:**
# Prepare mean ± SE data for hive 3 by date
hive3_by_date_summary <- live_corrected_zur %>%
filter(Hive == "3") %>%
group_by(Date, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
) %>%
arrange(Date, Dose_numeric)
# Fit GLM per date for hive 3
fit_per_date_hive3 <- live_corrected_zur %>%
filter(Hive == "3") %>%
group_by(Date) %>%
group_modify(~{
df_d <- .x
if (nrow(df_d) < 3 || length(unique(df_d$Dose_numeric)) < 2) return(tibble(model = list(NULL)))
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric), data = df_d, weights = Total, family = binomial(link = "logit")),
error = function(e) NULL
)
tibble(model = list(model))
})
# Create prediction lines for each date
pred_lines_hive3_by_date <- live_corrected_zur %>%
filter(Hive == "3") %>%
group_by(Date) %>%
summarise(dose_min = min(Dose_numeric), dose_max = max(Dose_numeric), .groups = 'drop') %>%
inner_join(fit_per_date_hive3, by = "Date") %>%
mutate(grid = pmap(list(dose_min, dose_max, model), function(dmin, dmax, m) {
if (is.null(m)) return(tibble(Dose_numeric = numeric(0), Pred = numeric(0)))
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 200)
tibble(Dose_numeric = dseq, Pred = predict(m, newdata = data.frame(Dose_numeric = dseq), type = "response"))
})) %>%
select(Date, grid) %>%
unnest(grid)
# Create publication-style plot for hive 3 by date
p_hive3_by_date_pub <- ggplot(hive3_by_date_summary, aes(x = log10(Dose_numeric), y = mean_viability, color = Date)) +
geom_point(size = 3, shape = 16) +
geom_errorbar(
aes(ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability)),
width = 0.1,
linewidth = 0.8
) +
geom_line(
data = pred_lines_hive3_by_date %>%
mutate(log_dose = log10(Dose_numeric)),
aes(x = log_dose, y = Pred, color = Date),
linewidth = 1.2
) +
scale_x_continuous(
breaks = seq(-3, 1, 1),
labels = c("0.001", "0.01", "0.1", "1", "10"),
limits = c(-3, 1)
) +
scale_y_continuous(
limits = c(0, 1),
labels = c("0", "0.25", "0.5", "0.75", "1.0")
) +
labs(
title = "Zur_Suheita - Hive 3 Dose-Response by Date",
x = "log Concentration (µg/vial)",
y = "Viability (proportion)",
color = "Date"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10),
panel.grid.minor = element_blank(),
panel.border = element_rect(color = "black", fill = NA, linewidth = 0.5),
legend.position = "right"
)
print(p_hive3_by_date_pub)

Hive Classification Analysis
# Analysis to classify hives into sensitive, benign, and resistant categories
# Based on their overall dose-response characteristics
cat("## Hive Classification Analysis\n")
## ## Hive Classification Analysis
cat("**Objective:** Classify all hives into sensitivity categories based on dose-response patterns\n")
## **Objective:** Classify all hives into sensitivity categories based on dose-response patterns
cat("**Categories:** Sensitive, Benign, Resistant\n\n")
## **Categories:** Sensitive, Benign, Resistant
# 1. Calculate summary statistics for each hive
cat("**1. Hive-level summary statistics:**\n")
## **1. Hive-level summary statistics:**
hive_summary <- df_viability_corrected %>%
filter(Dose_numeric > 0) %>%
group_by(Yard, Hive) %>%
summarise(
n_vials = n(),
n_doses = n_distinct(Dose_numeric),
dose_range = paste0(round(min(Dose_numeric), 3), " - ", round(max(Dose_numeric), 1)),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
median_viability = median(Corrected_Viability, na.rm = TRUE),
min_viability = min(Corrected_Viability, na.rm = TRUE),
max_viability = max(Corrected_Viability, na.rm = TRUE),
sd_viability = sd(Corrected_Viability, na.rm = TRUE),
.groups = 'drop'
) %>%
mutate(
mean_viability_pct = round(mean_viability * 100, 1),
median_viability_pct = round(median_viability * 100, 1),
min_viability_pct = round(min_viability * 100, 1),
max_viability_pct = round(max_viability * 100, 1),
cv_viability = round((sd_viability / mean_viability) * 100, 1)
) %>%
arrange(Yard, Hive)
# Render hive summary as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
hive_summary,
format = "html",
align = c('l','l','r','r','l','r','r','r','r','r','r','r','r','r','r')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(hive_summary)
}
|
Yard
|
Hive
|
n_vials
|
n_doses
|
dose_range
|
mean_viability
|
median_viability
|
min_viability
|
max_viability
|
sd_viability
|
mean_viability_pct
|
median_viability_pct
|
min_viability_pct
|
max_viability_pct
|
cv_viability
|
|
Dan_Zaatar
|
21
|
1
|
1
|
10 - 10
|
0.0000000
|
0.0000000
|
0
|
0.0000000
|
NA
|
0.0
|
0.0
|
0
|
0.0
|
NA
|
|
Dan_Zaatar
|
29
|
2
|
1
|
10 - 10
|
0.0000000
|
0.0000000
|
0
|
0.0000000
|
0.0000000
|
0.0
|
0.0
|
0
|
0.0
|
NaN
|
|
Raabane_Ein_Kinya
|
2
|
15
|
6
|
0.01 - 5
|
0.1866667
|
0.1000000
|
0
|
0.5000000
|
0.1552264
|
18.7
|
10.0
|
0
|
50.0
|
83.2
|
|
Raabane_Ein_Kinya
|
3
|
13
|
5
|
0.01 - 5
|
0.2153846
|
0.1000000
|
0
|
0.7000000
|
0.2577019
|
21.5
|
10.0
|
0
|
70.0
|
119.6
|
|
Shamir
|
1
|
10
|
6
|
1 - 2000
|
0.6272727
|
0.7272727
|
0
|
1.0000000
|
0.3722465
|
62.7
|
72.7
|
0
|
100.0
|
59.3
|
|
Shamir
|
3
|
22
|
8
|
0.01 - 2000
|
0.4634298
|
0.4000000
|
0
|
1.0000000
|
0.3372881
|
46.3
|
40.0
|
0
|
100.0
|
72.8
|
|
Shamir
|
3_4
|
4
|
2
|
1 - 10
|
0.2894737
|
0.2105263
|
0
|
0.7368421
|
0.3258627
|
28.9
|
21.1
|
0
|
73.7
|
112.6
|
|
Shamir
|
4
|
6
|
6
|
1 - 2000
|
0.4500000
|
0.4500000
|
0
|
1.0000000
|
0.4183300
|
45.0
|
45.0
|
0
|
100.0
|
93.0
|
|
Shamir
|
6
|
18
|
6
|
1 - 2000
|
0.3222222
|
0.2500000
|
0
|
1.0000000
|
0.3606458
|
32.2
|
25.0
|
0
|
100.0
|
111.9
|
|
Zur_Suheita
|
3
|
37
|
7
|
0.01 - 10
|
0.3082310
|
0.1363636
|
0
|
1.0000000
|
0.3907326
|
30.8
|
13.6
|
0
|
100.0
|
126.8
|
|
Zur_Suheita
|
5
|
2
|
2
|
0.01 - 10
|
0.0000000
|
0.0000000
|
0
|
0.0000000
|
0.0000000
|
0.0
|
0.0
|
0
|
0.0
|
NaN
|
|
Zur_Suheita
|
7
|
7
|
4
|
0.01 - 5
|
0.1064039
|
0.0000000
|
0
|
0.7448276
|
0.2815184
|
10.6
|
0.0
|
0
|
74.5
|
264.6
|
|
Zur_Suheita
|
9
|
4
|
4
|
0.01 - 10
|
0.1396552
|
0.0000000
|
0
|
0.5586207
|
0.2793103
|
14.0
|
0.0
|
0
|
55.9
|
200.0
|
# 2. Calculate LD50 for each hive (if possible)
cat("\n**2. LD50 calculations for each hive:**\n")
##
## **2. LD50 calculations for each hive:**
# Function to calculate LD50 for a hive
calculate_hive_ld50 <- function(hive_data) {
if (nrow(hive_data) < 3 || length(unique(hive_data$Dose_numeric)) < 2) {
return(NA)
}
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric),
data = hive_data,
weights = Total,
family = binomial(link = "logit")),
error = function(e) NULL
)
if (is.null(model)) return(NA)
# Calculate LD50
coefs <- coef(model)
if (length(coefs) >= 2 && !is.na(coefs[2]) && coefs[2] != 0) {
# logit(0.5) = 0, so the formula simplifies
ld50 <- 10^(-coefs[1] / coefs[2])
return(ld50)
} else {
return(NA)
}
}
# Calculate LD50 for each hive
hive_ld50 <- df_viability_corrected %>%
filter(Dose_numeric > 0) %>%
group_by(Yard, Hive) %>%
group_modify(~ {
ld50 <- calculate_hive_ld50(.x)
tibble(LD50 = ld50)
}) %>%
ungroup()
# Combine with hive summary
hive_classification <- hive_summary %>%
left_join(hive_ld50, by = c("Yard", "Hive")) %>%
mutate(
LD50_log = ifelse(!is.na(LD50), round(log10(LD50), 2), NA),
LD50_display = ifelse(!is.na(LD50), round(LD50, 3), "N/A")
)
# Render hive classification as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
hive_classification,
format = "html",
align = c('l','l','r','r','l','r','r','r','r','r','r','r','r','r','r','r','l','l')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(hive_classification)
}
|
Yard
|
Hive
|
n_vials
|
n_doses
|
dose_range
|
mean_viability
|
median_viability
|
min_viability
|
max_viability
|
sd_viability
|
mean_viability_pct
|
median_viability_pct
|
min_viability_pct
|
max_viability_pct
|
cv_viability
|
LD50
|
LD50_log
|
LD50_display
|
|
Dan_Zaatar
|
21
|
1
|
1
|
10 - 10
|
0.0000000
|
0.0000000
|
0
|
0.0000000
|
NA
|
0.0
|
0.0
|
0
|
0.0
|
NA
|
NA
|
NA
|
N/A
|
|
Dan_Zaatar
|
29
|
2
|
1
|
10 - 10
|
0.0000000
|
0.0000000
|
0
|
0.0000000
|
0.0000000
|
0.0
|
0.0
|
0
|
0.0
|
NaN
|
NA
|
NA
|
N/A
|
|
Raabane_Ein_Kinya
|
2
|
15
|
6
|
0.01 - 5
|
0.1866667
|
0.1000000
|
0
|
0.5000000
|
0.1552264
|
18.7
|
10.0
|
0
|
50.0
|
83.2
|
0.0012599
|
-2.90
|
0.001
|
|
Raabane_Ein_Kinya
|
3
|
13
|
5
|
0.01 - 5
|
0.2153846
|
0.1000000
|
0
|
0.7000000
|
0.2577019
|
21.5
|
10.0
|
0
|
70.0
|
119.6
|
0.0138868
|
-1.86
|
0.014
|
|
Shamir
|
1
|
10
|
6
|
1 - 2000
|
0.6272727
|
0.7272727
|
0
|
1.0000000
|
0.3722465
|
62.7
|
72.7
|
0
|
100.0
|
59.3
|
289.9987998
|
2.46
|
289.999
|
|
Shamir
|
3
|
22
|
8
|
0.01 - 2000
|
0.4634298
|
0.4000000
|
0
|
1.0000000
|
0.3372881
|
46.3
|
40.0
|
0
|
100.0
|
72.8
|
5.4998710
|
0.74
|
5.5
|
|
Shamir
|
3_4
|
4
|
2
|
1 - 10
|
0.2894737
|
0.2105263
|
0
|
0.7368421
|
0.3258627
|
28.9
|
21.1
|
0
|
73.7
|
112.6
|
1.0843518
|
0.04
|
1.084
|
|
Shamir
|
4
|
6
|
6
|
1 - 2000
|
0.4500000
|
0.4500000
|
0
|
1.0000000
|
0.4183300
|
45.0
|
45.0
|
0
|
100.0
|
93.0
|
49.7662056
|
1.70
|
49.766
|
|
Shamir
|
6
|
18
|
6
|
1 - 2000
|
0.3222222
|
0.2500000
|
0
|
1.0000000
|
0.3606458
|
32.2
|
25.0
|
0
|
100.0
|
111.9
|
9.9659035
|
1.00
|
9.966
|
|
Zur_Suheita
|
3
|
37
|
7
|
0.01 - 10
|
0.3082310
|
0.1363636
|
0
|
1.0000000
|
0.3907326
|
30.8
|
13.6
|
0
|
100.0
|
126.8
|
0.0282545
|
-1.55
|
0.028
|
|
Zur_Suheita
|
5
|
2
|
2
|
0.01 - 10
|
0.0000000
|
0.0000000
|
0
|
0.0000000
|
0.0000000
|
0.0
|
0.0
|
0
|
0.0
|
NaN
|
NA
|
NA
|
N/A
|
|
Zur_Suheita
|
7
|
7
|
4
|
0.01 - 5
|
0.1064039
|
0.0000000
|
0
|
0.7448276
|
0.2815184
|
10.6
|
0.0
|
0
|
74.5
|
264.6
|
0.0078678
|
-2.10
|
0.008
|
|
Zur_Suheita
|
9
|
4
|
4
|
0.01 - 10
|
0.1396552
|
0.0000000
|
0
|
0.5586207
|
0.2793103
|
14.0
|
0.0
|
0
|
55.9
|
200.0
|
0.0103693
|
-1.98
|
0.01
|
# 3. Classify hives based on multiple criteria
cat("\n**3. Hive classification based on dose-response characteristics:**\n")
##
## **3. Hive classification based on dose-response characteristics:**
# Define classification criteria
hive_classification <- hive_classification %>%
mutate(
# Classification based on mean viability and LD50
sensitivity_class = case_when(
# Resistant: High mean viability (>30%) OR high LD50 (>10 µg/vial)
mean_viability > 0.30 | (!is.na(LD50) & LD50 > 10) ~ "Resistant",
# Sensitive: Low mean viability (<15%) AND low LD50 (<1 µg/vial)
mean_viability < 0.15 & (!is.na(LD50) & LD50 < 1) ~ "Sensitive",
# Benign: Everything else (intermediate response)
TRUE ~ "Benign"
),
# Additional classification based on viability range
viability_class = case_when(
max_viability < 0.20 ~ "Low variability",
max_viability > 0.60 ~ "High variability",
TRUE ~ "Moderate variability"
)
)
# Display classification results
classification_summary <- hive_classification %>%
select(Yard, Hive, mean_viability_pct, LD50_display, sensitivity_class, viability_class) %>%
arrange(sensitivity_class, Yard, Hive)
# Render classification summary as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
classification_summary,
format = "html",
align = c('l','l','r','l','l','l')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(classification_summary)
}
|
Yard
|
Hive
|
mean_viability_pct
|
LD50_display
|
sensitivity_class
|
viability_class
|
|
Dan_Zaatar
|
21
|
0.0
|
N/A
|
Benign
|
Low variability
|
|
Dan_Zaatar
|
29
|
0.0
|
N/A
|
Benign
|
Low variability
|
|
Raabane_Ein_Kinya
|
2
|
18.7
|
0.001
|
Benign
|
Moderate variability
|
|
Raabane_Ein_Kinya
|
3
|
21.5
|
0.014
|
Benign
|
High variability
|
|
Shamir
|
3_4
|
28.9
|
1.084
|
Benign
|
High variability
|
|
Zur_Suheita
|
5
|
0.0
|
N/A
|
Benign
|
Low variability
|
|
Shamir
|
1
|
62.7
|
289.999
|
Resistant
|
High variability
|
|
Shamir
|
3
|
46.3
|
5.5
|
Resistant
|
High variability
|
|
Shamir
|
4
|
45.0
|
49.766
|
Resistant
|
High variability
|
|
Shamir
|
6
|
32.2
|
9.966
|
Resistant
|
High variability
|
|
Zur_Suheita
|
3
|
30.8
|
0.028
|
Resistant
|
High variability
|
|
Zur_Suheita
|
7
|
10.6
|
0.008
|
Sensitive
|
High variability
|
|
Zur_Suheita
|
9
|
14.0
|
0.01
|
Sensitive
|
Moderate variability
|
# 4. Summary statistics by classification
cat("\n**4. Summary by sensitivity classification:**\n")
##
## **4. Summary by sensitivity classification:**
class_summary <- hive_classification %>%
group_by(sensitivity_class) %>%
summarise(
n_hives = n(),
mean_viability_pct = round(mean(mean_viability_pct, na.rm = TRUE), 1),
median_viability_pct = round(median(mean_viability_pct, na.rm = TRUE), 1),
mean_ld50 = round(mean(LD50, na.rm = TRUE), 3),
median_ld50 = round(median(LD50, na.rm = TRUE), 3),
.groups = 'drop'
)
# Render class summary as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
class_summary,
format = "html",
align = c('l','r','r','r','r','r')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(class_summary)
}
|
sensitivity_class
|
n_hives
|
mean_viability_pct
|
median_viability_pct
|
mean_ld50
|
median_ld50
|
|
Benign
|
6
|
11.5
|
11.5
|
0.366
|
0.014
|
|
Resistant
|
5
|
43.4
|
43.4
|
71.052
|
9.966
|
|
Sensitive
|
2
|
12.3
|
12.3
|
0.009
|
0.009
|
# 5. Visualization of hive classification
cat("\n**5. Hive classification visualization:**\n")
##
## **5. Hive classification visualization:**
# Create scatter plot of mean viability vs LD50
p_classification <- ggplot(hive_classification, aes(x = LD50_log, y = mean_viability_pct, color = sensitivity_class)) +
geom_point(size = 4, alpha = 0.8) +
geom_text(aes(label = paste(Yard, Hive, sep = "-")),
hjust = -0.2, vjust = 0.5, size = 3) +
scale_color_manual(values = c("Sensitive" = "blue", "Benign" = "green", "Resistant" = "red")) +
scale_x_continuous(breaks = seq(-2, 2, 1), labels = c("0.01", "0.1", "1", "10", "100")) +
labs(
title = "Hive Classification by Sensitivity",
subtitle = "Based on mean viability and LD50 values",
x = "LD50 (µg/vial, log scale)",
y = "Mean Viability (%)",
color = "Sensitivity Class"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 12),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10),
legend.position = "bottom"
)
print(p_classification)

# 6. Detailed breakdown by yard
cat("\n**6. Classification breakdown by yard:**\n")
##
## **6. Classification breakdown by yard:**
yard_classification <- hive_classification %>%
group_by(Yard, sensitivity_class) %>%
summarise(
n_hives = n(),
hives = paste(Hive, collapse = ", "),
.groups = 'drop'
) %>%
pivot_wider(names_from = sensitivity_class, values_from = c(n_hives, hives),
names_sep = "_", values_fill = list(n_hives = 0, hives = ""))
# Render yard classification as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
yard_classification,
format = "html",
align = c('l','r','l','r','l','r','l')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(yard_classification)
}
|
Yard
|
n_hives_Benign
|
n_hives_Resistant
|
n_hives_Sensitive
|
hives_Benign
|
hives_Resistant
|
hives_Sensitive
|
|
Dan_Zaatar
|
2
|
0
|
0
|
21, 29
|
|
|
|
Raabane_Ein_Kinya
|
2
|
0
|
0
|
2, 3
|
|
|
|
Shamir
|
1
|
4
|
0
|
3_4
|
1, 3, 4, 6
|
|
|
Zur_Suheita
|
1
|
1
|
2
|
5
|
3
|
7, 9
|
cat("\n**Analysis complete.**\n")
##
## **Analysis complete.**
# 7. Diagnostic dose analysis based on sensitive hives
cat("\n**7. Diagnostic dose analysis for sensitive hives:**\n")
##
## **7. Diagnostic dose analysis for sensitive hives:**
# Get sensitive hives
sensitive_hives <- hive_classification %>%
filter(sensitivity_class == "Sensitive") %>%
select(Yard, Hive)
if (nrow(sensitive_hives) > 0) {
cat(paste0("Found ", nrow(sensitive_hives), " sensitive hives:\n"))
print(sensitive_hives)
# Get data for sensitive hives only
sensitive_data <- df_viability_corrected %>%
filter(Dose_numeric > 0) %>%
inner_join(sensitive_hives, by = c("Yard", "Hive"))
# Analyze dose-response for sensitive hives
cat("\n**Dose-response analysis for sensitive hives:**\n")
sensitive_dose_analysis <- sensitive_data %>%
group_by(Dose_numeric) %>%
summarise(
n_hives = n_distinct(paste(Yard, Hive)),
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
min_viability = min(Corrected_Viability, na.rm = TRUE),
max_viability = max(Corrected_Viability, na.rm = TRUE),
.groups = 'drop'
) %>%
mutate(
mean_viability_pct = round(mean_viability * 100, 1),
se_viability_pct = round(se_viability * 100, 1),
min_viability_pct = round(min_viability * 100, 1),
max_viability_pct = round(max_viability * 100, 1)
) %>%
arrange(Dose_numeric)
# Render sensitive dose analysis as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
sensitive_dose_analysis,
format = "html",
align = c('r','r','r','r','r','r','r','r','r','r')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(sensitive_dose_analysis)
}
# Find doses that give 0.01-0.1 viability (1-10%) in sensitive hives
cat("\n**Doses giving 0.01-0.1 viability (1-10%) in sensitive hives:**\n")
diagnostic_candidates <- sensitive_dose_analysis %>%
filter(mean_viability >= 0.01 & mean_viability <= 0.1) %>%
arrange(desc(n_hives), desc(n_vials))
if (nrow(diagnostic_candidates) > 0) {
# Render diagnostic candidates as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
diagnostic_candidates,
format = "html",
align = c('r','r','r','r','r','r','r','r','r','r')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(diagnostic_candidates)
}
# Select the best diagnostic dose
best_diagnostic <- diagnostic_candidates[1, ]
cat("\n**Recommended diagnostic dose based on sensitive hives:**\n")
cat(paste0("Dose: ", best_diagnostic$Dose_numeric, " µg/vial\n"))
cat(paste0("Mean viability in sensitive hives: ", round(best_diagnostic$mean_viability, 3), " (SE: ±", round(best_diagnostic$se_viability, 3), ")\n"))
cat(paste0("Range: ", round(best_diagnostic$min_viability, 3), " - ", round(best_diagnostic$max_viability, 3), "\n"))
cat(paste0("Tested in: ", best_diagnostic$n_hives, " sensitive hives, ", best_diagnostic$n_vials, " vials\n"))
# Create visualization for diagnostic dose
cat("\n**Diagnostic dose performance in sensitive hives:**\n")
diagnostic_dose_data <- sensitive_data %>%
filter(Dose_numeric == best_diagnostic$Dose_numeric) %>%
mutate(
Yard_Hive = paste(Yard, Hive, sep = "-")
)
p_diagnostic_sensitive <- ggplot(diagnostic_dose_data, aes(x = Yard_Hive, y = Corrected_Viability, fill = Yard)) +
geom_boxplot(alpha = 0.7) +
geom_jitter(width = 0.2, alpha = 0.6, size = 3) +
geom_hline(yintercept = c(0.01, 0.1), linetype = "dashed", color = "red", linewidth = 1) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = 0.1, ymax = Inf),
fill = "red", alpha = 0.1, inherit.aes = FALSE) +
annotate("text", x = length(unique(diagnostic_dose_data$Yard_Hive))/2, y = 0.15,
label = "RESISTANCE ZONE\n(>0.1 viability)",
color = "red", fontface = "bold", size = 3) +
scale_y_continuous(limits = c(0, 0.4), breaks = seq(0, 0.4, 0.1),
labels = c("0", "0.1", "0.2", "0.3", "0.4")) +
labs(
title = paste0("Diagnostic Dose Performance in Sensitive Hives: ", best_diagnostic$Dose_numeric, " µg/vial"),
subtitle = paste0("Mean viability: ", round(best_diagnostic$mean_viability, 3), " (n = ", best_diagnostic$n_vials, " vials)"),
x = "Sensitive Hives",
y = "Viability (proportion)",
fill = "Yard"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
plot.subtitle = element_text(size = 12),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "bottom"
)
print(p_diagnostic_sensitive)
# Protocol recommendation
cat("\n**Recommended protocol based on sensitive hive analysis:**\n")
cat("1. Use dose of", best_diagnostic$Dose_numeric, "µg/vial for screening\n")
cat("2. Expected viability in sensitive populations: 0.01-0.1 (1-10%)\n")
cat("3. If viability > 0.1, population shows potential resistance\n")
cat("4. This dose is validated on", best_diagnostic$n_hives, "sensitive hives\n")
} else {
cat("No doses found that give 0.01-0.1 viability in sensitive hives.\n")
cat("Expanding search to 0.02-0.3 range:\n")
diagnostic_candidates_expanded <- sensitive_dose_analysis %>%
filter(mean_viability >= 0.02 & mean_viability <= 0.3) %>%
arrange(desc(n_hives), desc(n_vials))
if (nrow(diagnostic_candidates_expanded) > 0) {
print(diagnostic_candidates_expanded)
best_diagnostic <- diagnostic_candidates_expanded[1, ]
cat("\n**Best alternative diagnostic dose:**\n")
cat(paste0("Dose: ", best_diagnostic$Dose_numeric, " µg/vial\n"))
cat(paste0("Mean viability: ", round(best_diagnostic$mean_viability, 3), "\n"))
} else {
cat("No suitable diagnostic dose found in sensitive hives.\n")
}
}
# 8. Model-based diagnostic dose estimation
cat("\n**8. Model-based diagnostic dose estimation for 1-10% viability:**\n")
# Function to calculate dose for specific viability level
calculate_dose_for_viability <- function(hive_data, target_viability) {
if (nrow(hive_data) < 3 || length(unique(hive_data$Dose_numeric)) < 2) {
return(NA)
}
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric),
data = hive_data,
weights = Total,
family = binomial(link = "logit")),
error = function(e) NULL
)
if (is.null(model)) return(NA)
# Calculate dose for target viability
coefs <- coef(model)
if (length(coefs) >= 2 && !is.na(coefs[2]) && coefs[2] != 0) {
# logit(target_viability) = coefs[1] + coefs[2] * log10(dose)
# log10(dose) = (logit(target_viability) - coefs[1]) / coefs[2]
# dose = 10^((logit(target_viability) - coefs[1]) / coefs[2])
logit_target <- log(target_viability / (1 - target_viability))
dose <- 10^((logit_target - coefs[1]) / coefs[2])
return(dose)
} else {
return(NA)
}
}
# Calculate doses for 1%, 5%, and 10% viability for each sensitive hive
model_based_doses <- sensitive_data %>%
group_by(Yard, Hive) %>%
group_modify(~ {
hive_data <- .x
dose_1pct <- calculate_dose_for_viability(hive_data, 0.01)
dose_5pct <- calculate_dose_for_viability(hive_data, 0.05)
dose_10pct <- calculate_dose_for_viability(hive_data, 0.10)
tibble(
dose_1pct = dose_1pct,
dose_5pct = dose_5pct,
dose_10pct = dose_10pct
)
}) %>%
ungroup() %>%
filter(!is.na(dose_5pct)) # Only include hives where we can calculate doses
if (nrow(model_based_doses) > 0) {
cat("Model-based dose estimates for 1%, 5%, and 10% viability:\n")
# Render model-based doses as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
model_based_doses,
format = "html",
align = c('l','l','r','r','r')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(model_based_doses)
}
# Calculate summary statistics for recommended diagnostic doses
cat("\n**Summary of model-based diagnostic doses:**\n")
dose_summary <- model_based_doses %>%
summarise(
n_hives = n(),
mean_dose_1pct = round(mean(dose_1pct, na.rm = TRUE), 3),
mean_dose_5pct = round(mean(dose_5pct, na.rm = TRUE), 3),
mean_dose_10pct = round(mean(dose_10pct, na.rm = TRUE), 3),
median_dose_5pct = round(median(dose_5pct, na.rm = TRUE), 3),
range_dose_5pct = paste0(round(min(dose_5pct, na.rm = TRUE), 3), " - ", round(max(dose_5pct, na.rm = TRUE), 3)),
.groups = 'drop'
)
# Render dose summary as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
dose_summary,
format = "html",
align = c('r','r','r','r','r','l')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(dose_summary)
}
# Recommend the median dose for 5% viability as diagnostic dose
recommended_dose <- dose_summary$median_dose_5pct
cat("\n**Recommended model-based diagnostic dose:**\n")
cat(paste0("Dose: ", recommended_dose, " µg/vial (median dose for 5% viability)\n"))
cat(paste0("This dose should give approximately 5% viability in sensitive populations\n"))
cat(paste0("Based on models from ", dose_summary$n_hives, " sensitive hives\n"))
cat(paste0("Dose range for 5% viability: ", dose_summary$range_dose_5pct, " µg/vial\n"))
} else {
cat("Unable to calculate model-based doses for sensitive hives.\n")
}
} else {
cat("No sensitive hives found in the dataset.\n")
cat("Cannot determine diagnostic dose based on sensitive population.\n")
}
## Found 2 sensitive hives:
## # A tibble: 2 × 2
## Yard Hive
## <chr> <chr>
## 1 Zur_Suheita 7
## 2 Zur_Suheita 9
##
## **Dose-response analysis for sensitive hives:**
##
## **Doses giving 0.01-0.1 viability (1-10%) in sensitive hives:**
## No doses found that give 0.01-0.1 viability in sensitive hives.
## Expanding search to 0.02-0.3 range:
## No suitable diagnostic dose found in sensitive hives.
##
## **8. Model-based diagnostic dose estimation for 1-10% viability:**
## Model-based dose estimates for 1%, 5%, and 10% viability:
##
## **Summary of model-based diagnostic doses:**
##
## **Recommended model-based diagnostic dose:**
## Dose: 0.016 µg/vial (median dose for 5% viability)
## This dose should give approximately 5% viability in sensitive populations
## Based on models from 2 sensitive hives
## Dose range for 5% viability: 0.015 - 0.016 µg/vial
Focused comparison plots: selected hives with LD lines
# Hives: 7, 9, 3 from Zur_Suheita; 3 from Shamir
# Helper to build per-hive summaries with mean ± SE
summarise_hive_means <- function(data) {
data %>%
group_by(Yard, Hive, Dose_numeric) %>%
summarise(
n_vials = n(),
mean_viability = mean(Corrected_Viability, na.rm = TRUE),
se_viability = sd(Corrected_Viability, na.rm = TRUE) / sqrt(n()),
.groups = 'drop'
)
}
# Helper to fit GLM and compute LDs, returning prediction lines and LDs
fit_hive_glm <- function(df_hive) {
if (nrow(df_hive) < 3 || length(unique(df_hive$Dose_numeric)) < 2) {
return(list(pred = tibble(Dose_numeric = numeric(0), Pred = numeric(0)),
LD5 = NA_real_, LD10 = NA_real_, LD50 = NA_real_))
}
model <- tryCatch(
glm(Corrected_Viability ~ log10(Dose_numeric), data = df_hive, weights = Total,
family = binomial(link = "logit")),
error = function(e) NULL
)
if (is.null(model)) {
return(list(pred = tibble(Dose_numeric = numeric(0), Pred = numeric(0)),
LD5 = NA_real_, LD10 = NA_real_, LD50 = NA_real_))
}
# prediction grid within observed range
dmin <- min(df_hive$Dose_numeric); dmax <- max(df_hive$Dose_numeric)
dseq <- 10^seq(log10(dmin), log10(dmax), length.out = 400)
pred <- tibble(
Dose_numeric = dseq,
Pred = predict(model, newdata = data.frame(Dose_numeric = dseq), type = "response")
)
co <- coef(model)
inv_ld <- function(v) 10^((log(v/(1-v)) - co[1]) / co[2])
out <- list(pred = pred,
LD5 = tryCatch(inv_ld(0.05), error = function(e) NA_real_),
LD10 = tryCatch(inv_ld(0.10), error = function(e) NA_real_),
LD50 = tryCatch(inv_ld(0.50), error = function(e) NA_real_))
out
}
# Select data
selected <- df_viability_corrected %>%
filter((Yard == "Zur_Suheita" & Hive %in% c("7", "9", "3")) |
(Yard == "Shamir" & Hive %in% c("3"))) %>%
filter(Dose_numeric > 0, Total > 0)
# Per-hive means for error bars
selected_means <- summarise_hive_means(selected)
# Fit per-hive models for prediction lines and LDs
model_outputs <- selected %>%
group_by(Yard, Hive) %>%
group_modify(~{
res <- fit_hive_glm(.x %>% filter(Dose_numeric > 0, Total > 0))
tibble(LD5 = res$LD5, LD10 = res$LD10, LD50 = res$LD50,
pred_list = list(res$pred))
}) %>%
ungroup()
pred_lines_sel <- model_outputs %>%
select(Yard, Hive, pred_list) %>%
tidyr::unnest(pred_list)
lds_sel <- model_outputs %>% select(Yard, Hive, LD5, LD10, LD50) %>%
mutate(
LD5 = ifelse(is.infinite(LD5) | LD5 <= 0, NA, LD5),
LD10 = ifelse(is.infinite(LD10) | LD10 <= 0, NA, LD10),
LD50 = ifelse(is.infinite(LD50) | LD50 <= 0, NA, LD50)
)
# Plot
p_selected <- ggplot() +
geom_point(
data = selected_means,
aes(x = Dose_numeric, y = mean_viability, color = interaction(Yard, Hive)),
size = 3, shape = 16
) +
geom_errorbar(
data = selected_means,
aes(x = Dose_numeric, ymin = pmax(0, mean_viability - se_viability),
ymax = pmin(1, mean_viability + se_viability), color = interaction(Yard, Hive)),
width = 0.1, linewidth = 0.8
) +
geom_line(
data = pred_lines_sel,
aes(x = Dose_numeric, y = Pred, color = interaction(Yard, Hive)),
linewidth = 1.2
) +
# Horizontal reference lines
geom_hline(yintercept = c(0.05, 0.10, 0.50), linetype = "dashed", color = "grey50") +
scale_x_log10(breaks = c(0.01, 0.1, 1, 10, 100, 1000), labels = c("0.01", "0.1", "1", "10", "100", "1000")) +
scale_y_continuous(limits = c(0, 1), labels = scales::percent_format(accuracy = 1)) +
labs(
title = "Selected hives dose–response with reference lines",
subtitle = "Zur_Suheita: hives 7, 9, 3; Shamir: hive 3",
x = "Dose (µg/vial, log scale)",
y = "Abbott-corrected Viability",
color = "Yard-Hive"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 14, face = "bold"),
axis.title = element_text(size = 12, face = "bold"),
axis.text = element_text(size = 10),
legend.position = "right"
)
print(p_selected)

# Build summary table: vial count, mite count, and LDs per hive
summary_sel <- selected %>%
group_by(Yard, Hive) %>%
summarise(
n_vials = n(),
total_mites = sum(Total, na.rm = TRUE),
.groups = 'drop'
) %>%
left_join(lds_sel, by = c("Yard", "Hive")) %>%
mutate(
LD5 = round(LD5, 4),
LD10 = round(LD10, 4),
LD50 = round(LD50, 4)
) %>%
arrange(Yard, Hive)
cat("\n**Summary for selected hives (vials, mites, LDs):**\n")
##
## **Summary for selected hives (vials, mites, LDs):**
# Render selected hives summary as styled HTML table
if (knitr::is_html_output()) {
html_tbl <- knitr::kable(
summary_sel,
format = "html",
align = c('l','l','r','r','r','r','r')
) %>%
kableExtra::kable_styling(
full_width = FALSE,
bootstrap_options = c("striped", "hover", "condensed")
) %>%
kableExtra::row_spec(0, bold = TRUE)
knitr::asis_output(html_tbl)
} else {
print(summary_sel)
}
|
Yard
|
Hive
|
n_vials
|
total_mites
|
LD5
|
LD10
|
LD50
|
|
Shamir
|
3
|
22
|
220
|
129744.6783
|
10078.9189
|
5.4999
|
|
Zur_Suheita
|
3
|
37
|
370
|
70.3827
|
9.6730
|
0.0283
|
|
Zur_Suheita
|
7
|
7
|
70
|
0.0149
|
0.0127
|
0.0079
|
|
Zur_Suheita
|
9
|
4
|
40
|
0.0163
|
0.0145
|
0.0104
|