Updates on genomic selection
Day 3: Genomic selection models enriched with environmental data
Data Science for Plant Breeding Lab
Piracicaba, September 3rd
GEI from the environmental perspective
An omics approach that uses the full spectrum of environmental variations to understand and predict G × E interaction — Fritsche-Neto et al. (2025)
Data extrinsic to the tested genotypes
An index vector derived from environmental covariates (ECs) which reflects the potential expression of a phenotype in a given space and time — Marcatti & Resende (2026)
Marker engineering
A new paradigm
Plant breeding triangle
Proposed by Jarquin et al. (2014) to surpass dimensionality issues
\[ \bar{\boldsymbol y} = \boldsymbol 1 \mu + \boldsymbol Z_1 \boldsymbol e + \boldsymbol Z_2 \boldsymbol g + \boldsymbol \varepsilon \]
\[ \boldsymbol{\Omega} = \frac{\boldsymbol{V}\boldsymbol{V}^\prime}{q} \]
\[ \bar{\boldsymbol y} = \boldsymbol 1 \mu + \boldsymbol Z_1 \boldsymbol \omega + \boldsymbol Z_2 \boldsymbol g + \boldsymbol \varepsilon \]
\[ \boldsymbol \omega \sim \mathcal N\left(\boldsymbol 0, \boldsymbol \Omega \sigma^2_e\right) \]
“The ECs may not fully describe differences across environments, perhaps because some relevant ECs were not measured or because of model miss-specification (e.g., non-linear effects of ECs on the trait of interest). Similarly, because of imperfect linkage disequilibrium (LD) between markers and genes at causal loci or because of model misspecification (e.g., interactions between alleles that are unaccounted for), the regression on markers may not fully describe genetic differences among lines.” — Jarquin et al. (2014)
\[ \bar{\boldsymbol y} = \boldsymbol 1 \mu + \boldsymbol Z_1 \boldsymbol \omega + \boldsymbol Z_2 \boldsymbol e + \boldsymbol Z_3 \boldsymbol g + \boldsymbol \varepsilon \]
\[ \boldsymbol e \sim \mathcal{N} \left(\boldsymbol 0, \boldsymbol I \sigma^2_e\right) \]
Genotype-by-environmental covariate interaction (first order multiplicative interaction)
\[ gw_{ik} = g_i \times \omega_k \]
\[ \operatorname{Cov} \left(gw_{ik}, gw_{i^\prime k^\prime}\right) = G_{ii^\prime} \times \Omega_{kk^\prime} \]
\[ \bar{\boldsymbol y} = \boldsymbol 1 \mu + \boldsymbol Z_1 \boldsymbol \omega + \boldsymbol Z_2 \boldsymbol e + \boldsymbol Z_3 \boldsymbol g + \boldsymbol Z_3 \boldsymbol {g\omega} + \boldsymbol \varepsilon \]
\[ \boldsymbol {g\omega} \sim \mathcal{N} \left[\boldsymbol 0, \left(\boldsymbol Z_3 \boldsymbol G \boldsymbol Z_3^\prime\right) \odot \left(\boldsymbol Z_1 \boldsymbol \Omega \boldsymbol Z_1^\prime\right) \sigma^2_{g\omega}\right] \]
Genotype-by-environment interaction
\[ \bar{\boldsymbol y} = \boldsymbol 1 \mu + \boldsymbol Z_1 \boldsymbol \omega + \boldsymbol Z_2 \boldsymbol e + \boldsymbol Z_3 \boldsymbol g + \boldsymbol Z_3 \boldsymbol {g\omega} + \boldsymbol Z_4 \boldsymbol {ge} + \boldsymbol \varepsilon \]
\[ \boldsymbol {ge} \sim \mathcal{N} \left[\boldsymbol 0, \left(\boldsymbol Z_3 \boldsymbol G \boldsymbol Z_3^\prime\right) \odot \left(\boldsymbol Z_2 \boldsymbol Z_2^\prime\right) \sigma^2_{ge}\right] \]
CV0: predict the performance of tested genotypes in untested environments
CV00: predict the performance of untested genotypes in untested environments (most challenging scenario of them all!)
'data.frame': 85949 obs. of 7 variables:
$ gen : chr "B37/C103" "B37/H95" "B73/MO17" "B73/PHG35" ...
$ year : int 2014 2014 2014 2014 2014 2014 2014 2014 2014 2014 ...
$ loc : chr "GAH1" "GAH1" "GAH1" "GAH1" ...
$ env : chr "GAH1_2014" "GAH1_2014" "GAH1_2014" "GAH1_2014" ...
$ blue : num 4.69 5.98 6.29 9.73 8.05 ...
$ std.error: num 0.828 0.828 0.828 0.828 0.828 ...
$ wt : num 1.91 1.91 1.91 1.91 1.91 ...
int [1:5899, 1:2105] 1 0 0 1 1 1 1 0 0 0 ...
- attr(*, "dimnames")=List of 2
..$ : chr [1:5899] "01CSI6/LH287" "01DIB2/LH287" "01DIB2/PHP02" "2369/DK3IIH6" ...
..$ : chr [1:2105] "S1_1007742" "S1_1020677" "S1_2018002" "S1_2275970" ...
[1] FALSE
dat = droplevels(dat[which(
dat$year > 2021 &
dat$loc %in% c("GAH1", "IAH1", "IAH2", "ILH1", "INH1", "MNH1", "MOH1", "MOH2")
), ])
dat = dat[which(dat$gen %in% rownames(geno.code)),]
set.seed(7)
dat = dat[which(dat$gen %in% sample(unique(dat$gen),150)),]
geno.code = geno.code[which(rownames(geno.code) %in% dat$gen),]
all(unique(dat$gen) %in% rownames(geno.code))[1] TRUE
Soil data: collected in loco
'data.frame': 186 obs. of 36 variables:
$ Year : int 2015 2015 2015 2015 2015 2015 2015 2015 2015 2015 ...
$ Env : chr "IAH1_2015" "IAH3_2015" "IAH4_2015" "INH1_2015" ...
$ LabID : chr "UW Soil & Plant Analysis Lab" "UW Soil & Plant Analysis Lab" "Soil & Forage Analysis Lab" "UW Soil & Plant Analysis Lab" ...
$ Date.Received : chr "" "" "" "" ...
$ Date.Reported : chr "5/27/2015" "5/27/2015" "12/7/2015" "6/2/2015" ...
$ E.Depth : int NA NA NA NA NA NA NA NA NA NA ...
$ X1.1.Soil.pH : num 5.3 6.5 6.4 6.1 6.5 6.2 6.4 7.1 5.8 6.4 ...
$ WDRF.Buffer.pH : num NA NA NA NA NA NA NA NA NA NA ...
$ X1.1.S.Salts.mmho.cm : num NA NA NA NA NA NA NA NA NA NA ...
$ Texture.No : int NA NA NA NA NA NA NA NA NA NA ...
$ Organic.Matter.LOI.. : num 3.2 3.4 4.4 2.9 2.1 4.2 2.3 1.8 1.6 2 ...
$ Nitrate.N.ppm.N : num NA NA NA NA NA NA NA NA NA NA ...
$ lbs.N.A : num NA NA NA NA NA NA NA NA NA NA ...
$ Potassium.ppm.K : num 118 114 95 86 145 109 104 93.5 330 455 ...
$ Sulfate.S.ppm.S : num NA NA NA NA NA NA NA NA NA NA ...
$ Calcium.ppm.Ca : num NA NA NA NA NA NA NA NA NA NA ...
$ Magnesium.ppm.Mg : num NA NA NA NA NA NA NA NA NA NA ...
$ Sodium.ppm.Na : num NA NA NA NA NA NA NA NA NA NA ...
$ CEC.Sum.of.Cations.me.100g: num NA NA NA NA NA NA NA NA NA NA ...
$ X.H.Sat : num NA NA NA NA NA NA NA NA NA NA ...
$ X.K.Sat : num NA NA NA NA NA NA NA NA NA NA ...
$ X.Ca.Sat : num NA NA NA NA NA NA NA NA NA NA ...
$ X.Mg.Sat : num NA NA NA NA NA NA NA NA NA NA ...
$ X.Na.Sat : num NA NA NA NA NA NA NA NA NA NA ...
$ Mehlich.P.III.ppm.P : num 34 38 42 14 11 19 18 38.5 40 27 ...
$ X..Sand : num NA NA NA NA NA NA NA NA NA NA ...
$ X..Silt : num NA NA NA NA NA NA NA NA NA NA ...
$ X..Clay : num NA NA NA NA NA NA NA NA NA NA ...
$ Texture : chr "" "" "" "" ...
$ BpH : num 6 6.5 6.4 6.5 6.7 6.5 6.6 NA 6.4 6.9 ...
$ Zinc.ppm.Zn : num NA NA NA NA NA NA NA NA NA NA ...
$ Iron.ppm.Fe : num NA NA NA NA NA NA NA NA NA NA ...
$ Manganese.ppm.Mn : num NA NA NA NA NA NA NA NA NA NA ...
$ Copper.ppm.Cu : num NA NA NA NA NA NA NA NA NA NA ...
$ Boron.ppm.B : num NA NA NA NA NA NA NA NA NA NA ...
$ Comments : chr "" "" "" "" ...
More filtering
[1] 12
[1] 12 36
Environmental profile of each environment
ggplot(data = soil |> pivot_longer(colnames(soil[-c(1, 2)])),
aes(x = Env, y = value)) +
facet_wrap(. ~ name, scales = "free_y") +
geom_col(color = "black", fill = '#b2bc63') +
theme_bw() +
theme(axis.text.x = element_text(
angle = 90,
vjust = .5,
hjust = 1
), axis.title.y = element_blank()) +
labs(x = "Environments")Weather data: collected from nearby weather stations
'data.frame': 51098 obs. of 18 variables:
$ Env : chr "DEH1_2015" "DEH1_2015" "DEH1_2015" "DEH1_2015" ...
$ Date : int 20150415 20150416 20150417 20150418 20150419 20150420 20150421 20150422 20150423 20150424 ...
$ RH2M : num 69.1 75 88.9 79.8 84.4 ...
$ T2M_MAX : num 16 19.1 22.1 23.4 14.3 ...
$ ALLSKY_SFC_SW_DWN : num 15.9 25.1 16.4 24.6 18.1 ...
$ T2MWET : num 9.57 10.77 15.49 16.05 11.32 ...
$ GWETTOP : num 0.88 0.87 0.87 0.87 0.87 0.91 0.88 0.88 0.87 0.87 ...
$ QV2M : num 5.98 6.77 10.25 9.95 7.57 ...
$ GWETPROF : num 0.88 0.88 0.87 0.86 0.86 0.89 0.89 0.88 0.87 0.86 ...
$ T2M_MIN : num 9.23 7.72 11.17 12.42 10.57 ...
$ T2MDEW : num 6.57 8.44 14.51 14.12 9.98 ...
$ PS : num 102 103 102 101 102 ...
$ T2M : num 12.6 13.1 16.5 18 12.6 ...
$ GWETROOT : num 0.91 0.91 0.91 0.9 0.89 0.93 0.93 0.91 0.91 0.9 ...
$ ALLSKY_SFC_PAR_TOT: num 83.1 126.4 87.1 126.2 94.4 ...
$ WS2M : num 2.7 2.54 3.07 1.41 5.13 5.52 3.9 4.32 4.09 4.37 ...
$ ALLSKY_SFC_SW_DNI : num 6.85 27.66 14 28.05 8.14 ...
$ PRECTOTCORR : num 0.28 0.01 3.04 0.36 8.92 ...
[1] 2218 18
Environmental profile of environment “GAH1_2022”
weather$Date = lubridate::ymd(weather$Date)
ggplot(data = weather |> pivot_longer(colnames(weather[-c(1, 2)])) |>
filter(Env == "GAH1_2022"),
aes(x = Date, y = value)) +
facet_wrap(name ~ ., scales = "free_y") +
geom_point(color = '#10342d', alpha = .75) +
geom_line(aes(group = 1), color = '#10342d', alpha = .8) +
theme_bw() +
theme(text = element_text(size = 15), axis.title.y = element_blank())Extracting environmental markers
# apply(weather[,-c(1,2)], 2, function(x) range(x))
envdata = list()
for (i in colnames(weather)[-c(1:2, 18)]) {
temp = do.call(rbind, tapply(weather[,i], weather$Env, summary))
colnames(temp) = c("min", "1qrt", "median", "mean", "3qrt", 'max')
colnames(temp) = paste(i, colnames(temp), sep = "_")
envdata[[i]] = temp
rm(temp)
}
envdata = do.call(cbind, envdata)
envdata = cbind(envdata,
PRECTOT_sum = tapply(weather$PRECTOTCORR, weather$Env, sum),
PRECTOT_mean = tapply(weather$PRECTOTCORR, weather$Env, mean),
soil[match(rownames(envdata), soil$Env),-c(1,2)])
dim(envdata)[1] 12 111
Overview of the matrix of environmental markers
library(ComplexHeatmap)
library(circlize)
col_fun = colorRamp2(c(min(envdata), 0, max(envdata)), c("blue4", "white", "red4"))
Heatmap(matrix = envdata,
show_row_dend = FALSE, show_column_dend = FALSE,
column_order = colnames(envdata),
column_split = ifelse(grepl("\\.", colnames(envdata)), "SOIL", gsub("_.*", "", colnames(envdata))),
show_column_names = FALSE,
column_title_rot = 45,
border = TRUE,
border_gp = gpar(col = "black", lty = 2),
col = col_fun,
show_heatmap_legend = FALSE)Environmental linear kernel
\[ \boldsymbol \Omega = \frac{\boldsymbol V \boldsymbol V^\prime}{q} \]
Environmental relationship matrix
Environmental relationship matrix
library(plotly)
pc = prcomp(Emat)
pc.df = rbind(
as.data.frame(pc$rotation) |>
rownames_to_column("ID") |>
mutate(comp = "load")
)
plot_ly(
pc.df,
x = ~ PC1,
y = ~ PC2,
z = ~ PC3
) |> add_markers(
text = ~ paste0("Environment: ", ID),
hoverinfo = "text",
marker = list(color = '#b2bc63', opacity = .62)
) |> layout(scene = list(
xaxis = list(
title = paste0("PC1 (", round(pc$sdev[1]^2/sum(pc$sdev^2)*100,2), "%)"),
showgrid = TRUE,
range = c(min(c(
range(pc.df$PC1), range(pc.df$PC2), range(pc.df$PC3)
)), max(c(
range(pc.df$PC1), range(pc.df$PC2), range(pc.df$PC3)
)))
),
yaxis = list(
title = paste0("PC2 (", round(pc$sdev[2]^2/sum(pc$sdev^2)*100,2), "%)"),
showgrid = TRUE,
range = c(min(c(
range(pc.df$PC1), range(pc.df$PC2), range(pc.df$PC3)
)), max(c(
range(pc.df$PC1), range(pc.df$PC2), range(pc.df$PC3)
)))
),
zaxis = list(
title = paste0("PC3 (", round(pc$sdev[3]^2/sum(pc$sdev^2)*100,2), "%)"),
showgrid = TRUE,
range = c(min(c(
range(pc.df$PC1), range(pc.df$PC2), range(pc.df$PC3)
)), max(c(
range(pc.df$PC1), range(pc.df$PC2), range(pc.df$PC3)
)))
)
))\[ \boldsymbol{G} = \frac{\boldsymbol{\dot{W}}\boldsymbol{\dot{W}}^\prime}{2\sum_m p_m \left(1-p_m\right)} \]
Initial data:
Number of Individuals: 150
Number of Markers: 2105
Missing data check:
Total SNPs: 2105
0 SNPs dropped due to missing data threshold of 0.5
Total of: 2105 SNPs
MAF check:
No SNPs with MAF below 0
Heterozigosity data check:
No SNPs with heterozygosity, missing threshold of = 0
Summary check:
Initial: 2105 SNPs
Final: 2105 SNPs ( 0 SNPs removed)
Completed! Time = 0.36 seconds
Reaction norm model:
\[ \bar{\boldsymbol y} = \boldsymbol 1 \mu + \boldsymbol Z_1 \boldsymbol \omega + \boldsymbol Z_2 \boldsymbol g + \boldsymbol Z_3 \boldsymbol {g\omega} + \boldsymbol \varepsilon \]
\[ \color{red}{\bar{\boldsymbol y} = \boldsymbol 1 \mu + \boldsymbol Z_1 \boldsymbol \omega + \boldsymbol Z_2 \boldsymbol g + \boldsymbol Z_3 \boldsymbol {g\omega}+ \boldsymbol Z_4 \boldsymbol {ge} + \boldsymbol \varepsilon} \]
Expanding the data frame
[1] 1640
[1] 1800
Expanding the data frame
ggplot(data = dat, aes(
x = reorder(env, blue),
y = reorder(gen, blue),
fill = blue
)) +
geom_tile() +
theme_minimal() +
theme(axis.text.y = element_blank(), axis.text.x = element_text(angle = 90),
legend.position = "top", legend.title = element_blank(),
text = element_text(size = 18)) +
scale_fill_viridis_c(option = "inferno", na.value = "lightgrey") +
labs(x = paste("Environments: ", length(unique(dat$env))),
y = paste("Hybrid: ", length(unique(dat$gen))))Incidence matrices
\[ \boldsymbol K_g = \boldsymbol Z_g \boldsymbol G \boldsymbol Z_g^\prime \]
\[ \boldsymbol K_e = \boldsymbol Z_e \boldsymbol Z_e^\prime \]
aux = do.call(c, lapply(split(dat, dat$env), nrow))
submat = ZOZ[1:sum(aux[1:6]), 1:sum(aux[1:6])]
col_fun = colorRamp2(c(min(submat), 0, max(submat)), c("blue4", "white", "green4"))
Heatmap(
submat,
show_row_dend = FALSE,
show_row_names = FALSE,
show_column_dend = FALSE,
show_column_names = FALSE,
column_split = rep(names(aux)[1:6], aux[1:6]),
row_split = rep(names(aux)[1:6], aux[1:6]),
col = col_fun,
show_heatmap_legend = FALSE,
border = TRUE,
border_gp = gpar(col = "black", lty = 2),
row_title_gp = gpar(fontsize = 10)
)\[ \boldsymbol K_{ge} = \boldsymbol K_g \odot \boldsymbol K_e \]
\[ \boldsymbol K_{g\omega} = \boldsymbol K_g \odot \boldsymbol K_\omega \]
submat = GEI_EC[1:sum(aux[1:5]),1:sum(aux[1:5])]
col_fun = colorRamp2(c(min(submat), 0, 3.8), c("blue4", "white", "green4"))
Heatmap(
submat,
show_row_dend = FALSE,
show_row_names = FALSE,
show_column_dend = FALSE,
show_column_names = FALSE,
column_split = rep(names(aux)[1:5], aux[1:5]),
row_split = rep(names(aux)[1:5], aux[1:5]),
col = col_fun,
show_heatmap_legend = FALSE,
border = TRUE,
border_gp = gpar(col = "black", lty = 2)
)Eigendecomposition (efficient computation)
Cholesky fatorization (even more efficient computation)
Factorized matrix of environmental relationship
Factorized covariance matrix of environmental covariates
Factorized genotype-by-environmental covariate kernel
submat = X_ge_ec
rownames(submat) = colnames(submat) = rep(names(aux), aux)
col_fun = colorRamp2(c(min(submat), 0, max(submat)), c("blue4", "white", "green4"))
Heatmap(
X_ge_ec,
show_row_dend = FALSE,
show_row_names = FALSE,
show_column_dend = FALSE,
show_column_names = FALSE,
row_order = 1:nrow(X_ge_ec),
column_order = 1:ncol(X_ge_ec),
col = col_fun,
show_heatmap_legend = FALSE,
# row_split = rep(names(aux), aux),
# row_title_gp = gpar(fontsize = 5)
)Models with only genotype-by-environmental covariate interaction
ETA1_kernel = list(ec = list(V = ZOZ$vectors, d = ZOZ$values, model = 'RKHS'),
hyb = list(V = ZGZ$vectors, d = ZGZ$values, model = 'RKHS'),
gei_ec = list(V = GEI_EC$vectors, d = GEI_EC$values, model = 'RKHS'))
ETA1_brr = list(ec = list(X = X_ec, model = 'BRR'),
hyb = list(X = X_g, model = 'BRR'),
gei_ec = list(X = X_ge_ec, model = 'BRR'))Models with both interactions
ETA2_kernel = list(ec = list(V = ZOZ$vectors, d = ZOZ$values, model = 'RKHS'),
hyb = list(V = ZGZ$vectors, d = ZGZ$values, model = 'RKHS'),
gei = list(V = GEI$vectors, d = GEI$values, model = 'RKHS'),
gei_ec = list(V = GEI_EC$vectors, d = GEI_EC$values, model = 'RKHS'))
ETA2_brr = list(ec = list(X = X_ec, model = 'BRR'),
hyb = list(X = X_g, model = 'BRR'),
gei = list(X = X_ge, model = 'BRR'),
gei_ec = list(X = X_ge_ec, model = 'BRR'))model1 = BGLR(y = dat$blue, ETA = ETA1_kernel, nIter = 12000, burnIn = 2000,
thin = 10, verbose = TRUE, saveAt = "Saves/dat/complete_met_ec_kernel")
model2 = BGLR(y = dat$blue, ETA = ETA1_brr, nIter = 12000, burnIn = 2000,
thin = 10, verbose = TRUE, saveAt = "Saves/dat/complete_met_ec_brr")
model3 = BGLR(y = dat$blue, ETA = ETA2_kernel, nIter = 12000, burnIn = 2000,
thin = 10, verbose = TRUE, saveAt = "Saves/dat/complete_met_geiec_kernel")
model4 = BGLR(y = dat$blue, ETA = ETA2_brr, nIter = 12000, burnIn = 2000,
thin = 10, verbose = TRUE, saveAt = "Saves/dat/complete_met_geiec_brr")Ellapsed time
ellapsed = data.frame(
model = rep(c("GxEC", "GxEC + GxE"), each = 2),
mechanism = rep(c("RKHS", "CHOL"), times = 2),
ellapsed = c(model1$ellapsed, model2$ellapsed, model3$ellapsed, model4$ellapsed)
)
ggplot(data = ellapsed, aes(x = model, y = ellapsed, fill = mechanism)) +
geom_col(position = position_dodge(), color = "black") +
theme_bw() +
theme(text = element_text(size = 18), legend.position = 'top',
legend.title = element_blank()) +
scale_fill_manual(values = c('#b2bc63','#10342d')) +
labs(x = "Model", y = "Ellapsed time (min)")Model parameters
modparam = data.frame(
residual = c(model1$varE, model2$varE, model3$varE, model4$varE),
ec = c(model1$ETA$ec$varU, model2$ETA$ec$varB, model3$ETA$ec$varU, model4$ETA$ec$varB),
g = c(model1$ETA$hyb$varU, model2$ETA$hyb$varB, model3$ETA$hyb$varU, model4$ETA$hyb$varB),
gxec = c(model1$ETA$gei_ec$varU,model2$ETA$gei_ec$varB, model3$ETA$gei_ec$varU, model4$ETA$gei_ec$varB),
gxe = c(NA, NA, model3$ETA$gei$varU, model4$ETA$gei$varB),
model = rep(c("GxEC", "GxEC + GxE"), each = 2),
mechanism = rep(c("RKHS", "CHOL"), times = 2)
)
modparam residual ec g gxec gxe model mechanism
1 2.213843 7.935072 1.739174 1.810245 NA GxEC RKHS
2 2.192019 8.904645 1.947036 1.855380 NA GxEC CHOL
3 1.859933 7.890657 1.743004 1.111975 1.169445 GxEC + GxE RKHS
4 1.850351 8.759556 1.905014 1.169101 1.142758 GxEC + GxE CHOL
Model parameters
modparam |> mutate(control = paste(model, mechanism, sep = "_")) %>%
group_by(control) |>
mutate(varT = ifelse(grepl("GxE_", control),residual + ec + g + gxec + gxe,
residual + ec + g + gxec),
PvR = residual/varT,
PvEC = ec/varT,
PvG = g/varT,
PvGxEC = gxec/varT,
PvGxE = gxe/varT) |>
pivot_longer(PvR:PvGxE) |>
ggplot(aes(x = model, y = value)) +
facet_wrap(.~mechanism) +
geom_col(aes(fill = name), color = "black") +
theme_bw() +
labs(x = "Model", y = "Percentage from total variance") +
scale_y_continuous(labels = scales::percent) +
scale_fill_viridis_d(option="mako",
labels = c('PvR' = "varE", 'PvG' = "varG", 'PvGxE' = "varGEI",
"PvGxEC" = "varE", "PvEC" = "varEC")) +
theme(text = element_text(size = 18), legend.position = "top",
legend.title = element_blank())Predictions
CV0: prediction of untested environments
CV0 is carried out via leave-one-out cross-validation. That means that we cannot make multiple CV rounds with different training population compositions. The training population will always be the number of environments minus one.
cv0=list()
for (i in levels(dat$env)) {
yNA = dat$blue
yNA[dat$env == i] = NA
mod1_cv = BGLR(y = yNA, ETA = ETA1_brr, nIter = 8000, burnIn = 1000,
thin = 10, verbose = FALSE, saveAt = "modCV")
unlink(list.files(pattern = "modCV"))
mod2_cv = BGLR(y = yNA, ETA = ETA2_brr, nIter = 8000, burnIn = 1000,
thin = 10, verbose = FALSE, saveAt = "modCV")
unlink(list.files(pattern = "modCV"))
cv0[[i]] = data.frame(
gen = dat$gen, env = dat$env, blue = dat$blue, yNA = yNA,
yhat_GxEC = mod1_cv$yHat, yhat_GxE_GxEC = mod2_cv$yHat
) |> filter(env == i)
}CV0 results:
res_cv0 = lapply(cv0, function(x){
pa_GxEC = cor(x$blue, x$yhat_GxEC, use = "complete.obs")
pa_GxE_GxEC = cor(x$blue, x$yhat_GxE_GxEC, use = "complete.obs")
rmspe_GxEC = sqrt(mean((x$blue - x$yhat_GxEC)^2, na.rm = T))
rmspe_GxE_GxEC = sqrt(mean((x$blue - x$yhat_GxE_GxEC)^2, na.rm = T))
data.frame(
env = unique(x$env),
value = c(pa_GxEC, rmspe_GxEC, pa_GxE_GxEC, rmspe_GxE_GxEC),
metric = c("pa", "rmspe"),
model = rep(c("GxEC", "GxE_GxEC"), each = 2)
)
})
res_cv0 = do.call(rbind, res_cv0)
tapply(res_cv0$value, res_cv0[,c("model", "metric")], mean) metric
model pa rmspe
GxE_GxEC 0.4993338 2.688657
GxEC 0.4820498 2.684478
CV00: prediction of untested genotypes in untested environments
CV00 will mix CV0 and CV1:
nfolds = 5
nrept = 10
seed = 8
sets = list()
i = 1
repeat{
set.seed(7 * i)
sets[[i]] = sample(rep(1:nfolds, length.out = nlevels(dat$gen)))
i = i + 1
if(i > nrept) break
}
cvdata = lapply(sets, function(x){
cvdata = dat
aux = data.frame(gen = levels(dat$gen), set = x)
cvdata$set = aux$set[match(cvdata$gen, aux$gen)]
return(cvdata)
})
for (i in 1:length(cvdata)) cvdata[[i]]$rept = icv00 = lapply(cvdata, function(x){
res.env = list()
for (j in levels(x$env)) {
res.list = list()
for (i in unique(x$set)) {
yNA = x$blue
yNA[x$env == j | x$set == i] = NA
mod1_cv = BGLR(y = yNA, ETA = ETA1_brr, nIter = 8000, burnIn = 1000,
thin = 10, verbose = FALSE, saveAt = "modCV")
unlink(list.files(pattern = "modCV"))
mod2_cv = BGLR(y = yNA, ETA = ETA2_brr, nIter = 8000, burnIn = 1000,
thin = 10, verbose = FALSE, saveAt = "modCV")
unlink(list.files(pattern = "modCV"))
res.list[[i]] = data.frame(
gen = x$gen, env = x$env, set = x$set, blue = x$blue,
yNA = yNA, yhat_GxEC = mod1_cv$yHat, yhat_GxE_GxEC = mod2_cv$yHat
) |> filter(env == j & set == i)
}
res.env[[j]] = do.call(rbind, res.list)
}
res.env
})This will take a very long time to run: 5 folds \(\times\) 10 repetitions \(\times\) 12 environments \(\times\) 2 modelling strategies = 1200 models
CV00 results:
res_cv00 = lapply(cv00, function(x){
pa_GxEC = do.call(rbind, lapply(x, function(x) cor(x$blue, x$yhat_GxEC, use = "complete.obs")))
rmspe_GxEC = do.call(rbind, lapply(x, function(x) sqrt(mean((x$blue - x$yhat_GxEC)^2, na.rm = T))))
pa_GxE_GxEC = do.call(rbind, lapply(x, function(x) cor(x$blue, x$yhat_GxE_GxEC, use = "complete.obs")))
rmspe_GxE_GxEC = do.call(rbind, lapply(x, function(x) sqrt(mean((x$blue - x$yhat_GxE_GxEC)^2, na.rm = T))))
data.frame(
env = rep(rownames(rmspe_GxEC), times = 4),
value = c(pa_GxEC, rmspe_GxEC, pa_GxE_GxEC, rmspe_GxE_GxEC),
metric = rep(c("pa", "rmspe"), each = length(rownames(rmspe_GxEC))),
model = rep(c("GxEC", "GxE_GxEC"), each = length(rownames(rmspe_GxEC))*2)
)
})
res_cv00_mean = do.call(rbind, lapply(res_cv00, function(x) reframe(x, value = mean(value), .by = c(metric, model))))
res_cv00_mean$rept = rep(1:5, each = 4)
res_cv00_mean$CV = "CV00"CV2: Sparse-testing
sets = list()
i = 1
repeat{
set.seed(987 * i)
sets[[i]] = sample(rep(1:nfolds, length.out = nrow(dat)))
i = i + 1
if(i > nrept) break
}
cvdata = lapply(sets, function(x){
cvdata = dat
cvdata$set = x
return(cvdata)
})
for (i in 1:length(cvdata)) cvdata[[i]]$rept = i
cv2 = lapply(cvdata, function(x){
res.list = list()
for (i in unique(x$set)) {
yNA = x$blue
yNA[x$set == i] = NA
mod1_cv = BGLR(y = yNA, ETA = ETA1_brr, nIter = 8000, burnIn = 1000,
thin = 10, verbose = FALSE, saveAt = "modCV")
unlink(list.files(pattern = "modCV"))
mod2_cv = BGLR(y = yNA, ETA = ETA2_brr, nIter = 8000, burnIn = 1000,
thin = 10, verbose = FALSE, saveAt = "modCV")
unlink(list.files(pattern = "modCV"))
res.list[[i]] = data.frame(
gen = x$gen,
env = x$env,
blue = x$blue,
set = x$set,
yNA = yNA,
yhat_GxEC = mod1_cv$yHat,
yhat_GxE_GxEC = mod2_cv$yHat
) |> filter(set == i)
}
res.list
})
cv2 = lapply(cv2, function(x) do.call(rbind, x))CV2 results:
res_cv2 = lapply(cv2, function(x){
x |> pivot_longer(yhat_GxEC:yhat_GxE_GxEC, names_to = "model",
values_to = "yhat") |>
mutate(model = gsub("yhat_","",model)) |>
reframe(rmspe = sqrt(mean((blue - yhat)^2, na.rm = T)),
pa = cor(blue, yhat, use = "complete.obs"),
.by = c(env, model)) |>
pivot_longer(rmspe:pa, names_to = "metric", values_to = "value") |>
as.data.frame()
})
res_cv2_mean = do.call(rbind, lapply(res_cv2, function(x) reframe(x, value = mean(value), .by = c(metric, model))))
res_cv2_mean$rept = rep(1:5, each = 4)
res_cv2_mean$CV = "CV2"CV1: Prediction of untested genotypes
sets = list()
i = 1
repeat{
set.seed(7 * i)
sets[[i]] = sample(rep(1:nfolds, length.out = nlevels(dat$gen)))
i = i + 1
if(i > nrept) break
}
cvdata = lapply(sets, function(x){
cvdata = dat
aux = data.frame(gen = levels(dat$gen), set = x)
cvdata$set = aux$set[match(cvdata$gen, aux$gen)]
return(cvdata)
})
for (i in 1:length(cvdata)) cvdata[[i]]$rept = i
cv1 = lapply(cvdata, function(x){
res.list = list()
for (i in unique(x$set)) {
yNA = x$blue
yNA[x$set == i] = NA
mod1_cv = BGLR(y = yNA, ETA = ETA1_brr, nIter = 8000, burnIn = 1000,
thin = 10, verbose = FALSE, saveAt = "modCV")
unlink(list.files(pattern = "modCV"))
mod2_cv = BGLR(y = yNA, ETA = ETA2_brr, nIter = 8000, burnIn = 1000,
thin = 10, verbose = FALSE, saveAt = "modCV")
unlink(list.files(pattern = "modCV"))
res.list[[i]] = data.frame(
gen = x$gen,
env = x$env,
blue = x$blue,
set = x$set,
yNA = yNA,
yhat_GxEC = mod1_cv$yHat,
yhat_GxE_GxEC = mod2_cv$yHat
) |> filter(set == i)
}
res.list
})
cv1 = lapply(cv1, function(x) do.call(rbind, x))CV1 results:
res_cv1 = lapply(cv1, function(x){
x |> pivot_longer(yhat_GxEC:yhat_GxE_GxEC, names_to = "model",
values_to = "yhat") |>
mutate(model = gsub("yhat_","",model)) |>
reframe(rmspe = sqrt(mean((blue - yhat)^2, na.rm = T)),
pa = cor(blue, yhat, use = "complete.obs"),
.by = c(env, model)) |>
pivot_longer(rmspe:pa, names_to = "metric", values_to = "value") |>
as.data.frame()
})
res_cv1_mean = do.call(rbind, lapply(res_cv1, function(x) reframe(x, value = mean(value), .by = c(metric, model))))
res_cv1_mean$rept = rep(1:5, each = 4)
res_cv1_mean$CV = "CV1"rbind(res_cv1_mean, res_cv2_mean, res_cv00_mean) |>
ggplot(aes(x = CV, y = value, fill = model)) +
facet_wrap(.~metric, scales = "free_y", labeller = labeller(.cols = c("pa" = "Predictive ability", "rmspe" = "RMSPE"))) +
geom_boxplot() +
theme_bw() +
theme(axis.title.y = element_blank(), text = element_text(size = 18),
legend.position = 'top') +
labs(x = "Cross-validation scenario", fill = "Model") +
scale_fill_manual("Model", values = c("#b2bc63", "#10342d"),
labels = c("GxE + GxEC", "GxE")) +
scale_color_manual(values = c("#b2bc63", "#10342d"),
labels = c("GxE + GxEC", "GxE"), guide = 'none') +
geom_linerange(
data = res_cv0_mean, aes(x = CV, ymin = 0, ymax = value, color = model),
position = position_dodge(width = 0.6),
show.legend = FALSE,
linewidth = 1
) +
geom_point(
data = res_cv0_mean, aes(x = CV, y = value, color = model),
position = position_dodge(width = 0.6),
size = 2.5,
show.legend = FALSE,
shape = 17
)for (i in 1:length(res_cv1)){
res_cv1[[i]]$rept = i
res_cv1[[i]]$CV = "CV1"
}
for (i in 1:length(res_cv2)){
res_cv2[[i]]$rept = i
res_cv2[[i]]$CV = "CV2"
}
for (i in 1:length(res_cv00)){
res_cv00[[i]]$rept = i
res_cv00[[i]]$CV = "CV00"
}
rbind(
do.call(rbind, res_cv1),
do.call(rbind, res_cv2),
do.call(rbind, res_cv00)
) |>
ggplot(aes(x = env, y = value, fill = model)) +
facet_grid(
metric ~ CV,
scales = 'free_y',
labeller = labeller(.rows = c("pa" = "Predictive ability", "rmspe" = "RMSPE"))
) +
theme_bw() +
theme(
axis.title.y = element_blank(),
text = element_text(size = 14),
axis.text.x = element_text(angle = 90, vjust = .5, hjust = 1),
legend.position = "top"
) +
geom_boxplot() +
labs(x = "Environments", fill = "Model") +
scale_fill_manual(
values = c("#b2bc63", "#10342d"),
labels = c("GxE + GxEC", "GxE")
) +
scale_color_manual(
values = c("#b2bc63", "#10342d"),
labels = c("GxE + GxEC", "GxE"),
guide = 'none'
) +
geom_linerange(
data = res_cv0 |> mutate(CV = "CV0"),
aes(x = env, ymin = 0, ymax = value, color = model, group = model),
position = position_dodge(width = 0.6),
show.legend = FALSE,
linewidth = 1, alpha = .7
) +
geom_point(
data = res_cv0 |> mutate(CV = "CV0"),
aes(x = env, y = value, color = model, group = model),
position = position_dodge(width = 0.6),
size = 2.5,
show.legend = FALSE,
shape = 17,
alpha = .7
)