1. Setup and Data Loading
Load libraries
library(tidyverse)
library(dplyr)
library(ggplot2)
library(ggtext)
library(ggfx)
library(ggpubr)
library(ggbeeswarm)
library(rstatix)
library(emmeans)
library(knitr)
Define aesthetic constants
pal <- c("gold", "purple4")
pheno_theme <- theme_classic2(base_size = 20) +
theme(
plot.title = element_markdown(hjust = 0.5, face = "bold"),
axis.title.x = element_blank(),
axis.text.x = element_text(
face = "bold",
color = "black",
size = 20,
angle = 45,
hjust = 1
),
strip.background = element_blank(),
strip.text = element_text(size = 25),
legend.position = "none"
)
pheno_theme2 <- theme_classic2(base_size = 20) +
theme(
plot.title = element_markdown(hjust = 0.5, face = "bold"),
axis.title.y = element_markdown(),
axis.title.x = element_blank(),
axis.text.x = element_text(size = 25, face = "bold", color = "black"),
strip.background = element_blank(),
strip.text = element_markdown(size = 20),
legend.position = "none"
)
pheno_theme3 <- theme_classic2(base_size = 20) +
theme(
plot.title = element_markdown(hjust = 0.5, face = "bold"),
axis.text.x = element_text(color = "black", size = 20),
strip.background = element_blank(),
strip.text = element_text(size = 20),
legend.position = "none"
)
Load spatially corrected data
sp_corrected_file <- "PSU2022_spatially_corrected_both_treatments.csv"
sp_corrected_combined <- read.csv(sp_corrected_file) %>%
mutate(
Treatment = factor(Treatment, levels = c("+P", "-P")),
genotype = factor(genotype, levels = c("CTRL", "Inv4m"))
)
cat("Spatially corrected data dimensions:", dim(sp_corrected_combined), "\n")
## Spatially corrected data dimensions: 127 39
cat("Treatment levels:", levels(sp_corrected_combined$Treatment), "\n")
## Treatment levels: +P -P
cat("Genotype levels:", levels(sp_corrected_combined$genotype), "\n")
## Genotype levels: CTRL Inv4m
Prepare plotting data
pheno <- sp_corrected_combined %>%
filter(genotype %in% c("CTRL", "Inv4m")) %>%
mutate(genotype = factor(genotype, levels = c("CTRL", "Inv4m"))) %>%
droplevels() %>%
rename(Genotype = genotype) %>%
mutate(
Genotype_label = case_when(
Genotype == "CTRL" ~ "CTRL",
Genotype == "Inv4m" ~ "<i>Inv4m</i>",
TRUE ~ as.character(Genotype)
),
Genotype_label = factor(Genotype_label, levels = c("CTRL", "<i>Inv4m</i>"))
)
cat("Analysis data dimensions:", dim(pheno), "\n")
## Analysis data dimensions: 64 40
2. Statistical Analysis
Identify phenotype variables
base_phenotypes <- c("PH", "DTA", "DTS", "STW40", "STW50", "STW60", "STWHV")
ear_phenotypes <- c("EW", "EL", "ED", "KRN", "CD", "TKW", "KW50", "TKN", "HI")
available_phenos <- intersect(
c(base_phenotypes, ear_phenotypes),
colnames(pheno)
)
cat("Available phenotypes for analysis:\n")
## Available phenotypes for analysis:
cat(paste(available_phenos, collapse = ", "), "\n")
## PH, DTA, DTS, STW40, STW50, STW60, STWHV, EW, EL, ED, KRN, CD, TKW, KW50, TKN, HI
vars <- available_phenos
Stage 1: Fit models and extract all effects with single FDR
# Fit models and extract ALL non-intercept coefficients
all_effects <- lapply(vars, function(x) {
n_obs <- sum(!is.na(pheno[[x]]))
if (n_obs < 20) {
message("Skipping ", x, " - insufficient data")
return(NULL)
}
model <- tryCatch(
lm(as.formula(paste(x, "~ Genotype * Treatment")), data = pheno),
error = function(e) {
message("Model failed for ", x)
return(NULL)
}
)
if (is.null(model)) return(NULL)
# Extract coefficients, excluding intercept
coef_summary <- coef(summary(model))
coef_summary <- coef_summary[!grepl("Intercept", rownames(coef_summary)), , drop = FALSE]
if (nrow(coef_summary) == 0) return(NULL)
out <- coef_summary %>%
as.data.frame() %>%
tibble::rownames_to_column("predictor")
colnames(out)[3] <- "Std.Error"
colnames(out)[5] <- "p.value"
out$response <- x
out %>% select(response, predictor, Estimate, Std.Error, p.value)
}) %>%
bind_rows()
# Apply single FDR correction to all effects
all_effects <- all_effects %>%
mutate(p.adjust = p.adjust(p.value, method = "fdr")) %>%
arrange(p.adjust)
cat("\n=== All Model Effects (non-intercept) ===\n")
##
## === All Model Effects (non-intercept) ===
cat("Total effects tested:", nrow(all_effects), "\n")
## Total effects tested: 48
print(all_effects %>% head(20))
## response predictor Estimate Std.Error p.value
## 1 STW50 Treatment-P -18.7588852 1.58289202 1.598759e-16
## 2 DTS Treatment-P 3.0621067 0.32199366 1.383332e-13
## 3 DTA Treatment-P 3.4328993 0.36759849 2.668769e-13
## 4 STW40 Treatment-P -6.5170078 1.00758227 2.137127e-08
## 5 STW60 Treatment-P -33.3434615 4.97340523 1.741324e-08
## 6 STWHV Treatment-P -22.4074887 4.70104209 1.299584e-05
## 7 DTA GenotypeInv4m -1.4777836 0.36759849 1.648877e-04
## 8 DTS GenotypeInv4m -1.2841867 0.32199366 1.833645e-04
## 9 PH GenotypeInv4m 5.5697807 1.48063541 3.853788e-04
## 10 KW50 Treatment-P -1.8013514 0.53628018 1.521219e-03
## 11 STW40 GenotypeInv4m -2.9345129 1.00758227 5.056342e-03
## 12 CD GenotypeInv4m:Treatment-P -2.6176674 0.97676410 1.000036e-02
## 13 HI GenotypeInv4m 0.1718492 0.06560131 1.168579e-02
## 14 STW50 GenotypeInv4m -4.1128444 1.63844890 1.515412e-02
## 15 STWHV GenotypeInv4m -11.6548319 4.70104209 1.609593e-02
## 16 EL GenotypeInv4m 0.8936462 0.38817860 2.561592e-02
## 17 TKW GenotypeInv4m 11.8709854 5.37554617 3.193065e-02
## 18 EL Treatment-P 0.8272244 0.38244294 3.544784e-02
## 19 STW50 GenotypeInv4m:Treatment-P 4.6198626 2.22224856 4.248080e-02
## 20 STW40 GenotypeInv4m:Treatment-P 2.8619497 1.41339838 4.741356e-02
## p.adjust
## 1 7.674042e-15
## 2 3.319998e-12
## 3 4.270031e-12
## 4 2.051642e-07
## 5 2.051642e-07
## 6 1.039667e-04
## 7 1.100187e-03
## 8 1.100187e-03
## 9 2.055354e-03
## 10 7.301852e-03
## 11 2.206404e-02
## 12 4.000144e-02
## 13 4.314752e-02
## 14 5.150698e-02
## 15 5.150698e-02
## 16 7.684777e-02
## 17 9.015714e-02
## 18 9.452758e-02
## 19 1.073199e-01
## 20 1.089860e-01
Identify significant effects and traits with interactions
# Significant effects at FDR < 0.05
significant_effects <- all_effects %>%
filter(p.adjust < 0.05)
cat("\n=== Significant Effects (FDR < 0.05) ===\n")
##
## === Significant Effects (FDR < 0.05) ===
print(significant_effects)
## response predictor Estimate Std.Error p.value
## 1 STW50 Treatment-P -18.7588852 1.58289202 1.598759e-16
## 2 DTS Treatment-P 3.0621067 0.32199366 1.383332e-13
## 3 DTA Treatment-P 3.4328993 0.36759849 2.668769e-13
## 4 STW40 Treatment-P -6.5170078 1.00758227 2.137127e-08
## 5 STW60 Treatment-P -33.3434615 4.97340523 1.741324e-08
## 6 STWHV Treatment-P -22.4074887 4.70104209 1.299584e-05
## 7 DTA GenotypeInv4m -1.4777836 0.36759849 1.648877e-04
## 8 DTS GenotypeInv4m -1.2841867 0.32199366 1.833645e-04
## 9 PH GenotypeInv4m 5.5697807 1.48063541 3.853788e-04
## 10 KW50 Treatment-P -1.8013514 0.53628018 1.521219e-03
## 11 STW40 GenotypeInv4m -2.9345129 1.00758227 5.056342e-03
## 12 CD GenotypeInv4m:Treatment-P -2.6176674 0.97676410 1.000036e-02
## 13 HI GenotypeInv4m 0.1718492 0.06560131 1.168579e-02
## p.adjust
## 1 7.674042e-15
## 2 3.319998e-12
## 3 4.270031e-12
## 4 2.051642e-07
## 5 2.051642e-07
## 6 1.039667e-04
## 7 1.100187e-03
## 8 1.100187e-03
## 9 2.055354e-03
## 10 7.301852e-03
## 11 2.206404e-02
## 12 4.000144e-02
## 13 4.314752e-02
# Identify traits with significant interactions
traits_with_interaction <- significant_effects %>%
filter(grepl(":", predictor)) %>%
pull(response) %>%
unique()
cat("\n=== Traits with Significant Interactions ===\n")
##
## === Traits with Significant Interactions ===
if (length(traits_with_interaction) > 0) {
cat(paste(traits_with_interaction, collapse = ", "), "\n")
} else {
cat("None\n")
}
## CD
# Get all traits with any significant effect
traits_with_effects <- significant_effects %>%
pull(response) %>%
unique()
cat("\n=== All Traits with Significant Effects ===\n")
##
## === All Traits with Significant Effects ===
cat(paste(traits_with_effects, collapse = ", "), "\n")
## STW50, DTS, DTA, STW40, STW60, STWHV, PH, KW50, CD, HI
cat("Total:", length(traits_with_effects), "traits\n")
## Total: 10 traits
Calculate percentage effects
# Calculate reference means for each trait
reference_means_list <- lapply(traits_with_effects, function(x) {
group_means <- pheno %>%
filter(!is.na(.data[[x]])) %>%
group_by(Genotype, Treatment) %>%
summarise(mean_val = mean(.data[[x]], na.rm = TRUE), .groups = "drop")
plus_p_marginal <- group_means %>%
filter(Treatment == "+P") %>%
summarise(mean = mean(mean_val)) %>%
pull(mean)
ctrl_marginal <- group_means %>%
filter(Genotype == "CTRL") %>%
summarise(mean = mean(mean_val)) %>%
pull(mean)
ctrl_minus_p <- group_means %>%
filter(Genotype == "CTRL", Treatment == "-P") %>%
pull(mean_val)
if (length(ctrl_minus_p) == 0) ctrl_minus_p <- NA_real_
data.frame(
response = x,
plus_p_marginal_mean = plus_p_marginal,
ctrl_marginal_mean = ctrl_marginal,
ctrl_minus_p_mean = ctrl_minus_p
)
}) %>%
bind_rows()
# Add percentages to contrasts
contrasts_with_pct <- contrasts_for_reporting %>%
left_join(reference_means_list, by = "response") %>%
mutate(
reference_mean = case_when(
predictor == "Treatment" ~ plus_p_marginal_mean,
predictor == "Genotype" ~ ctrl_marginal_mean,
grepl("Treatment\\|CTRL", predictor) ~ plus_p_marginal_mean,
grepl("Treatment\\|Inv4m", predictor) ~ plus_p_marginal_mean,
grepl("Genotype\\|\\+P", predictor) ~ ctrl_marginal_mean,
grepl("Genotype\\|-P", predictor) ~ ctrl_minus_p_mean,
TRUE ~ NA_real_
),
estimate_pct = (estimate / reference_mean) * 100
) %>% select(-starts_with("ctrl_"), -starts_with("plus_"))
cat("\n=== Effects with Percentages ===\n")
##
## === Effects with Percentages ===
print(contrasts_with_pct %>%
select(response, predictor, contrast_type, estimate, estimate_pct, SE, p.value))
## response predictor contrast_type estimate estimate_pct SE
## 1 STW50 Treatment marginal -16.41494589 -48.381951 1.14466632
## 2 STW50 Genotype marginal -1.60148084 -6.019516 1.14042941
## 3 DTS Treatment marginal 3.41646064 4.679297 0.23032280
## 4 DTS Genotype marginal -0.92983272 -1.236720 0.23032280
## 5 DTA Treatment marginal 3.60161149 5.045611 0.25869545
## 6 DTA Genotype marginal -1.30907138 -1.772935 0.25869545
## 7 STW40 Treatment marginal -5.06257438 -47.731076 0.72462888
## 8 STW40 Genotype marginal -1.48007949 -16.790072 0.72462888
## 9 STW60 Treatment marginal -27.79553850 -36.790821 3.51749524
## 10 STW60 Genotype marginal 2.87358466 4.763014 3.51024017
## 11 STWHV Treatment marginal -18.56909001 -18.355897 3.27431036
## 12 STWHV Genotype marginal -7.81643322 -8.160384 3.27431036
## 13 PH Treatment marginal 3.42625576 2.083557 1.04388584
## 14 PH Genotype marginal 6.40830361 3.932642 1.04388584
## 15 KW50 Treatment marginal -1.86372143 -17.689031 0.36979252
## 16 KW50 Genotype marginal 0.13864020 1.454181 0.36926537
## 17 CD Treatment|CTRL conditional 1.53892221 5.864469 0.51745913
## 18 CD Treatment|Inv4m conditional 1.53892221 5.864469 0.51745913
## 19 CD Genotype|+P conditional -1.23073512 -4.721431 0.51672148
## 20 CD Genotype|-P conditional -1.23073512 -4.738758 0.51672148
## 21 HI Treatment marginal 0.07999539 10.695997 0.04505377
## 22 HI Genotype marginal 0.12166268 16.750318 0.04498954
## p.value
## 1 4.662928e-20
## 2 1.659616e-01
## 3 6.674410e-22
## 4 1.534890e-04
## 5 1.339050e-20
## 6 4.113753e-06
## 7 2.638380e-09
## 8 4.549988e-02
## 9 2.067418e-10
## 10 4.168069e-01
## 11 4.511543e-07
## 12 2.020047e-02
## 13 1.706870e-03
## 14 6.819607e-08
## 15 6.475186e-06
## 16 7.089149e-01
## 17 4.515382e-03
## 18 4.515382e-03
## 19 2.107391e-02
## 20 2.107391e-02
## 21 8.189278e-02
## 22 9.332652e-03
Identify traits for visualization
# Traits with treatment effects (marginal or conditional)
treatment_traits <- contrasts_for_reporting %>%
filter(grepl("Treatment", predictor)) %>%
pull(response) %>%
unique()
cat("\n=== Traits for Visualization ===\n")
##
## === Traits for Visualization ===
cat(paste(treatment_traits, collapse = ", "), "\n")
## STW50, DTS, DTA, STW40, STW60, STWHV, PH, KW50, CD, HI
cat("Total:", length(treatment_traits), "traits\n")
## Total: 10 traits
3. Pairwise Comparisons for Visualization
to_t_test <- pheno %>%
select(plotId, Genotype, Treatment, all_of(vars)) %>%
pivot_longer(
cols = all_of(vars),
names_to = "trait",
values_to = "value"
) %>%
filter(trait %in% treatment_traits)
if (length(treatment_traits) > 0) {
treatment_pairwise <- to_t_test %>%
group_by(Genotype, trait) %>%
t_test(value ~ Treatment, ref.group = "+P") %>%
adjust_pvalue(method = "fdr") %>%
add_significance() %>%
add_x_position(x = "Treatment") %>%
add_y_position(scales = "free") %>%
mutate(
Genotype_label = factor(
case_when(
Genotype == "CTRL" ~ "CTRL",
Genotype == "Inv4m" ~ "<i>Inv4m</i>",
TRUE ~ as.character(Genotype)
),
levels = c("CTRL", "<i>Inv4m</i>")
)
)
cat("\n=== Pairwise t-test summary ===\n")
print(treatment_pairwise %>% select(trait, Genotype, statistic, p.adj,p.adj.signif), n=20)
} else {
treatment_pairwise <- NULL
cat("No traits with treatment effects for visualization\n")
}
##
## === Pairwise t-test summary ===
## # A tibble: 20 × 5
## trait Genotype statistic p.adj p.adj.signif
## <chr> <fct> <dbl> <dbl> <chr>
## 1 CD CTRL 0.187 8.55e- 1 ns
## 2 DTA CTRL -9.54 7 e-10 ****
## 3 DTS CTRL -10.4 2.79e-10 ****
## 4 HI CTRL -1.70 1.24e- 1 ns
## 5 KW50 CTRL 2.55 2.58e- 2 *
## 6 PH CTRL -2.06 5.66e- 2 ns
## 7 STW40 CTRL 5.93 8.67e- 6 ****
## 8 STW50 CTRL 10.2 1.08e- 7 ****
## 9 STW60 CTRL 7.18 2.40e- 6 ****
## 10 STWHV CTRL 4.73 1.81e- 4 ***
## 11 CD Inv4m 6.92 9.86e- 7 ****
## 12 DTA Inv4m -10.0 2.79e-10 ****
## 13 DTS Inv4m -10.9 1.72e-10 ****
## 14 HI Inv4m -0.585 5.95e- 1 ns
## 15 KW50 Inv4m 5.56 1.75e- 5 ****
## 16 PH Inv4m -2.54 2.19e- 2 *
## 17 STW40 Inv4m 4.01 7.42e- 4 ***
## 18 STW50 Inv4m 9.49 1.08e- 7 ****
## 19 STW60 Inv4m 3.97 2.17e- 3 **
## 20 STWHV Inv4m 3.25 5.27e- 3 **
4. Plotting Function
plot_treatment_effect <- function(trait_name,
trait_title,
trait_ylab,
y_limits = NULL,
y_breaks = NULL,
bracket_nudge = 0.05) {
t_test_data <- NULL
if (!is.null(treatment_pairwise)) {
t_test_data <- treatment_pairwise %>%
filter(trait == trait_name)
if (!is.null(t_test_data) && nrow(t_test_data) > 0) {
if (!is.null(y_limits)) {
y_range <- diff(y_limits)
y_max <- y_limits[2]
t_test_data <- t_test_data %>%
mutate(y.position = y_max - (bracket_nudge * y_range))
} else {
data_range <- pheno %>%
filter(!is.na(.data[[trait_name]])) %>%
pull(.data[[trait_name]]) %>%
range()
y_range <- diff(data_range)
y_max <- max(data_range)
t_test_data <- t_test_data %>%
mutate(y.position = y_max + (bracket_nudge * y_range))
}
}
}
p <- pheno %>%
ggplot(aes(x = Treatment, y = .data[[trait_name]], col = Treatment)) +
ggtitle(trait_title) +
ylab(trait_ylab) +
geom_boxplot(width = 0.25, linewidth = 1, alpha = 0) %>%
with_shadow(colour = "black", x_offset = 0, y_offset = 0, sigma = 1) +
geom_quasirandom(size = 2) %>%
with_shadow(colour = "black", x_offset = 0, y_offset = 0, sigma = 1) +
scale_color_manual(values = pal) +
facet_wrap(. ~ Genotype_label) +
pheno_theme2 +
theme(strip.text = element_markdown())
if (!is.null(t_test_data) && nrow(t_test_data) > 0) {
p <- p + stat_pvalue_manual(
t_test_data,
size = 10,
label = "p.adj.signif" ,
bracket.size = 0.8,
hide.ns = FALSE
)
}
if (!is.null(y_limits)) {
p <- p + coord_cartesian(ylim = y_limits)
}
if (!is.null(y_breaks)) {
p <- p + scale_y_continuous(breaks = y_breaks)
}
return(p)
}
5. Individual Trait Plots
Days to Anthesis
if ("DTA" %in% treatment_traits) {
dta_plot <- plot_treatment_effect(
trait_name = "DTA",
trait_title = "Anthesis",
trait_ylab = "Days",
y_limits = c(68, 80),
y_breaks = 68:80,
bracket_nudge = 0.08
)
print(dta_plot)
}

Days to Silking
if ("DTS" %in% treatment_traits) {
dts_plot <- plot_treatment_effect(
trait_name = "DTS",
trait_title = "Silking",
trait_ylab = "Days",
y_limits = c(68, 80),
y_breaks = 68:80,
bracket_nudge = 0
)
print(dts_plot)
}

Plant Height
if ("PH" %in% treatment_traits) {
ph_plot <- plot_treatment_effect(
trait_name = "PH",
trait_title = "Plant Height",
trait_ylab = "cm",
y_limits = c(140, 180),
y_breaks = seq(140, 180, by = 10),
bracket_nudge = 0.08
)
print(ph_plot)
}

50 Kernel Weight
if ("KW50" %in% treatment_traits) {
kw50_plot <- plot_treatment_effect(
trait_name = "KW50",
trait_title = "50 Kernel Weight",
trait_ylab = "g",
y_limits = c(5, 17),
y_breaks = 5:17,
bracket_nudge = 0.08
)
print(kw50_plot)
}

Cob Diameter
if ("CD" %in% treatment_traits) {
cd_plot <- plot_treatment_effect(
trait_name = "CD",
trait_title = "Cob Diameter",
trait_ylab = "cm",
y_limits = c(21, 31),
y_breaks = 21:31,
bracket_nudge = 0.08
)
print(cd_plot)
}

Stover Weight at 40 DAP
if ("STW40" %in% treatment_traits) {
stw40_plot <- plot_treatment_effect(
trait_name = "STW40",
trait_title = "Stover Weight <br> at 40 DAP",
trait_ylab = "g",
bracket_nudge = 0.08
)
print(stw40_plot)
}

Stover Weight at 50 DAP
if ("STW50" %in% treatment_traits) {
stw50_plot <- plot_treatment_effect(
trait_name = "STW50",
trait_title = "Stover Weight <br> at 50 DAP",
trait_ylab = "g",
bracket_nudge = 0.08
)
print(stw50_plot)
}

Stover Weight at 60 DAP
if ("STW60" %in% treatment_traits) {
stw60_plot <- plot_treatment_effect(
trait_name = "STW60",
trait_title = "Stover Weight <br> at 60 DAP",
trait_ylab = "g",
bracket_nudge = 0.08
)
print(stw60_plot)
}

Stover Weight at Harvest
if ("STWHV" %in% treatment_traits) {
stwhv_plot <- plot_treatment_effect(
trait_name = "STWHV",
trait_title = "Stover Dry Weight <br> at Harvest",
trait_ylab = "g",
y_limits = c(50, 175),
bracket_nudge = 0.08
)
print(stwhv_plot)
}

6. Stover Dry Weight Time Course
Prepare time-series data
dw_raw <- pheno %>%
mutate(STW121 = STWHV) %>%
select(
plotId,
Genotype,
Genotype_label,
Treatment,
starts_with("STW")
) %>%
pivot_longer(
cols = c("STW40", "STW50", "STW60", "STW121"),
names_to = "DAP_string",
values_to = "STW"
) %>%
mutate(
DAP = as.integer(gsub("STW", "", DAP_string)),
Genotype = factor(Genotype, levels = c("CTRL", "Inv4m")),
Genotype_label = factor(Genotype_label, levels = c("CTRL", "<i>Inv4m</i>"))
) %>%
filter(!is.na(STW)) %>%
droplevels()
Time-point specific t-tests
treatment_timeseries_test <- dw_raw %>%
ungroup() %>%
filter(DAP > 1) %>%
mutate(DAP = as.factor(DAP)) %>%
group_by(Genotype, DAP) %>%
t_test(STW ~ Treatment, ref.group = "+P") %>%
adjust_pvalue(method = "fdr") %>%
add_significance() %>%
add_y_position(scales = "free_y") %>%
add_x_position(x = "DAP", group = NULL, dodge = 1) %>%
mutate(
Genotype_label = factor(
case_when(
Genotype == "CTRL" ~ "CTRL",
Genotype == "Inv4m" ~ "<i>Inv4m</i>",
TRUE ~ as.character(Genotype)
),
levels = c("CTRL", "<i>Inv4m</i>")
)
)
data_range <- range(dw_raw$STW[dw_raw$DAP > 1], na.rm = TRUE)
y_range <- diff(data_range)
treatment_timeseries_test <- treatment_timeseries_test %>%
mutate(y.position = y.position + (0.08 * y_range))
cat("\n=== Time-series t-test summary ===\n")
##
## === Time-series t-test summary ===
print(treatment_timeseries_test %>%
select(Genotype, DAP, statistic, p, p.adj, p.adj.signif))
## # A tibble: 8 × 6
## Genotype DAP statistic p p.adj p.adj.signif
## <fct> <fct> <dbl> <dbl> <dbl> <chr>
## 1 CTRL 40 5.93 0.0000039 0.0000078 ****
## 2 CTRL 50 10.2 0.0000000324 0.000000130 ****
## 3 CTRL 60 7.18 0.000000961 0.00000256 ****
## 4 CTRL 121 4.73 0.0000995 0.000159 ***
## 5 Inv4m 40 4.01 0.000445 0.000593 ***
## 6 Inv4m 50 9.49 0.0000000271 0.000000130 ****
## 7 Inv4m 60 3.97 0.00141 0.00161 **
## 8 Inv4m 121 3.25 0.00369 0.00369 **
Visualize stover weight trajectory
pd <- position_dodge(1)
p_dw <- dw_raw %>%
ungroup() %>%
filter(DAP > 1) %>%
ggplot(aes(x = as.factor(DAP), y = STW, col = Treatment)) +
ggtitle("Stover Dry Weight") +
ylab("g") +
xlab("Days After Planting") +
geom_boxplot(
width = 0.25,
linewidth = 1,
alpha = 0,
position = pd
) %>%
with_shadow(colour = "black", x_offset = 0, y_offset = 0, sigma = 1) +
geom_quasirandom(
size = 2,
width = 0.25,
dodge.width = 1
) %>%
with_shadow(colour = "black", x_offset = 0, y_offset = 0, sigma = 1) +
facet_wrap(. ~ Genotype_label) +
stat_pvalue_manual(
treatment_timeseries_test,
size = 10,
bracket.size = 0.8,
tip.length = 0.01,
hide.ns = TRUE
) +
scale_color_manual(values = pal) +
scale_y_continuous(expand = expansion(mult = c(0.05, 0.1))) +
pheno_theme3 +
theme(
legend.position = c(0.12, 0.8),
strip.text = element_markdown()
)
print(p_dw)

7. Biomass Trajectory Analysis
Fit regressions
lm(estimate_pct ~ predictor*DAP_numeric , data = stover_effects %>%
filter(predictor %in% c("Genotype","Treatment"))
) %>% summary()
##
## Call:
## lm(formula = estimate_pct ~ predictor * DAP_numeric, data = stover_effects %>%
## filter(predictor %in% c("Genotype", "Treatment")))
##
## Residuals:
## 1 2 3 4 5 6 7 8
## -9.5320 0.4777 0.9840 -3.9187 11.5120 3.9269 -2.9640 -0.4858
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -8.27610 9.62994 -0.859 0.4386
## predictorTreatment -54.91468 13.61879 -4.032 0.0157 *
## DAP_numeric 0.02545 0.12886 0.198 0.8531
## predictorTreatment:DAP_numeric 0.34910 0.18223 1.916 0.1279
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 8.13 on 4 degrees of freedom
## Multiple R-squared: 0.9049, Adjusted R-squared: 0.8336
## F-statistic: 12.69 on 3 and 4 DF, p-value: 0.01641
minus_p_data <- stover_effects %>%
filter(predictor == "Treatment")
lm_minus_p <- lm(estimate_pct ~ DAP_numeric, data = minus_p_data)
r_squared_minus_p <- summary(lm_minus_p)$r.squared
p_value_minus_p <- summary(lm_minus_p)$coefficients[2, 4]
inv4m_data <- stover_effects %>%
filter(predictor == "Genotype") %>%
mutate(DAP_numeric = as.numeric(as.character(DAP)))
lm_inv4m <- lm(estimate_pct ~ DAP_numeric, data = inv4m_data)
r_squared_inv4m <- summary(lm_inv4m)$r.squared
p_value_inv4m <- summary(lm_inv4m)$coefficients[2, 4]
regression_label_minus_p <- sprintf(
"italic(R)^2 == %.3f~~italic(p) == %.3f",
r_squared_minus_p,
p_value_minus_p
)
regression_label_inv4m <- sprintf(
"italic(R)^2 == %.3f~~italic(p) == %.3f",
r_squared_inv4m,
p_value_inv4m
)
cat("\n=== Regression Statistics ===\n")
##
## === Regression Statistics ===
cat("Treatment effect:\n")
## Treatment effect:
cat(" R² =", round(r_squared_minus_p, 4), "\n")
## R² = 0.947
cat(" p-value =", format.pval(p_value_minus_p, digits = 3), "\n")
## p-value = 0.0268
cat(" Slope =", round(coef(lm_minus_p)[2], 4), "% per day\n\n")
## Slope = 0.3746 % per day
cat("Genotype effect:\n")
## Genotype effect:
cat(" R² =", round(r_squared_inv4m, 4), "\n")
## R² = 0.0109
cat(" p-value =", format.pval(p_value_inv4m, digits = 3), "\n")
## p-value = 0.895
cat(" Slope =", round(coef(lm_inv4m)[2], 4), "% per day\n")
## Slope = 0.0255 % per day
Plot biomass trajectory
p_biomass_comparison <- stover_effects %>%
mutate(DAP_numeric = as.numeric(as.character(DAP))) %>%
ggplot(aes(x = DAP_numeric, y = -estimate_pct, group = effect_type)) +
geom_hline(yintercept = 0, linetype = "solid", color = "gray50", linewidth = 0.8) +
geom_smooth(
data = minus_p_data,
method = "lm",
se = FALSE,
linewidth = 1,
linetype = "dashed",
color = "black"
) +
geom_smooth(
data = inv4m_data,
method = "lm",
se = FALSE,
linewidth = 1,
linetype = "dotted",
color = "black"
) +
geom_pointrange(
aes(ymin = -pct_ci_lower, ymax = -pct_ci_upper, shape = effect_type),
color = "black",
size = 1,
linewidth = 1,
fatten = 5,
position = position_dodge(width = 3)
) +
annotate(
"text",
x = 80,
y = 40,
label = regression_label_minus_p,
parse = TRUE,
size = 6,
hjust = 0
) +
annotate(
"text",
x = 80,
y = 10,
label = regression_label_inv4m,
parse = TRUE,
size = 6,
hjust = 0
) +
scale_x_continuous(
breaks = c(40, 50, 60, 121),
labels = c("40", "50", "60", "121")
) +
scale_shape_manual(
values = c(16, 18),
labels = c("<i>Inv4m</i>", "-P")
) +
guides(shape = guide_legend(reverse = TRUE)) +
labs(
title = "Stover Biomass Reduction ",
x = "Days After Planting",
y = "% reference mean",
shape = "Predictor"
) +
theme_classic2(base_size = 20) +
theme(
plot.title = element_markdown(hjust = 0.5, face = "bold"),
axis.text.x = element_text(color = "black", size = 18),
legend.position = c(0.8, 0.9),
legend.text = element_markdown(size = 20)
)
#the odd value at DAP60 is real
summary(pairs(emmeans(lm(data=pheno, STW60 ~ Genotype * Treatment), ~ Genotype), reverse = TRUE))
## contrast estimate SE df t.ratio p.value
## Inv4m - CTRL 2.5 3.47 50 0.720 0.4750
##
## Results are averaged over the levels of: Treatment
print(p_biomass_comparison)

8. Combined Panels
Main phenotype panel
plot_list <- list()
if (exists("dta_plot")) plot_list$dta <- dta_plot
if (exists("dts_plot")) plot_list$dts <- dts_plot
# if (exists("ph_plot")) plot_list$ph <- ph_plot
if (exists("kw50_plot")) plot_list$kw50 <- kw50_plot
if (exists("cd_plot")) plot_list$cd <- cd_plot
if (length(plot_list) >= 4) {
main_plots <- plot_list[1:min(8, length(plot_list))]
combined_plot <- ggarrange(
plotlist = main_plots,
ncol = 4,
nrow = 1,
labels = LETTERS[1:length(main_plots)],
font.label = list(size = 30)
)
print(combined_plot)
} else if (length(plot_list) > 0) {
combined_plot <- ggarrange(
plotlist = plot_list,
ncol = 2,
nrow = ceiling(length(plot_list) / 2),
labels = LETTERS[1:length(plot_list)],
font.label = list(size = 30)
)
print(combined_plot)
}

Combined stover weight panel
p_stw <- ggarrange(
p_dw,
p_biomass_comparison,
nrow = 1,
ncol = 2,
labels = c("E", "F"),
font.label = list(size = 30)
)
print(p_stw)
### Combined figure with time series
# Create comprehensive figure including time series
if (exists("combined_plot") && exists("p_stw")) {
full_figure <- ggarrange(
combined_plot,
p_stw,
ncol = 1,
nrow = 2,
# heights = c(1, 1.2),
#labels = c("", "E"),
font.label = list(size = 30)
)
print(full_figure)
}
