Show R code
install.packages("remotes") # Once, if needed.
remotes::install_github("psoerensen/gsim")Quantitative Genetics Practical 7: From scores to parent contributions
Apply eligibility rules and selection quotas; interpret score ties; distinguish selected parents from their reproductive contributions; construct random and balanced mating plans; and calculate expected offspring merit under an additive model.
Continue from Practical 6 and the slides on selection on complex traits. This practical uses hypothetical candidates and supplied scores, not mouse measurements or a fitted evaluation. It requires gsim with gsim_select(), gsim_mate() and gsim_pedigree_from_table(). Install the package before starting if needed:
A score answers how candidates rank under an objective and information model. Eligibility answers which candidates may be used now. Here scores are supplied predicted aggregate breeding values in currency units on one fixed base. Age and availability are hypothetical programme requirements. Missing scores are excluded explicitly rather than replaced by zero.
candidates <- data.frame(
animal=c("M1","M2","M3","M4","F1","F2","F3","F4"),
sex=rep(c("M","F"),each=4),
family=rep(c("A","A","B","B"),2),
score=c(12,11,8,20,10,10,7,NA),
age_months=c(14,14,15,9,14,15,14,14),
available=c(TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,TRUE,FALSE))
candidates$eligible <- with(candidates,
age_months>=12 & available & is.finite(score))
scores <- setNames(candidates$score,candidates$animal)
knitr::kable(candidates,row.names=FALSE)| animal | sex | family | score | age_months | available | eligible |
|---|---|---|---|---|---|---|
| M1 | M | A | 12 | 14 | TRUE | TRUE |
| M2 | M | A | 11 | 14 | TRUE | TRUE |
| M3 | M | B | 8 | 15 | TRUE | TRUE |
| M4 | M | B | 20 | 9 | TRUE | FALSE |
| F1 | F | A | 10 | 14 | TRUE | TRUE |
| F2 | F | A | 10 | 15 | TRUE | TRUE |
| F3 | F | B | 7 | 14 | TRUE | TRUE |
| F4 | F | B | NA | 14 | FALSE | FALSE |
Family labels identify supplied groups; they do not reconstruct pedigree relationships. In particular, different families need not be unrelated. We will use them to compare selection rules, not estimate inbreeding.
Question 1. Why is M4 excluded despite having the highest score?
M4 does not meet the declared minimum age. Eligibility is applied before ranking. F4 is unavailable and has no finite score. A different programme could choose different rules, but those rules should be explicit before examining selection outcomes.
Select two eligible candidates for each role. strata and eligible follow candidate order; scores are matched by animal identity. gsim_select() does not fit predictions. Equal scores use UTF-8 animal identity as the tie rule, so F1 precedes F2.
| animal | stratum | score | selected | inclusion_probability |
|---|---|---|---|---|
| M1 | M | 12 | TRUE | 1 |
| M2 | M | 11 | TRUE | 1 |
| M3 | M | 8 | FALSE | 0 |
| F1 | F | 10 | TRUE | 1 |
| F2 | F | 10 | TRUE | 1 |
| F3 | F | 7 | FALSE | 0 |
# Identity matching and the tie rule preserve the selected set after reordering.
j <- rev(seq_len(nrow(candidates)))
reordered <- gsim_select(candidates$animal[j],n=c(M=2,F=2),scores=scores,
method="score",strata=candidates$sex[j],eligible=candidates$eligible[j])
stopifnot(setequal(selected_role,reordered$animal[reordered$selected]))The reported inclusion probabilities are 0 or 1 conditional on these scores and rules. They are not probabilities of being selected over repeated genetic evaluations or evidence of representative population sampling.
Instead choose exactly one candidate from each role/family group. This is an exact quota policy. It is not a general optimizer for overlapping family limits, cost constraints or genetic relationships.
stratum <- paste(candidates$sex,candidates$family,sep="_")
quota <- c(M_A=1,M_B=1,F_A=1,F_B=1)
by_family <- gsim_select(candidates$animal,n=quota,scores=scores,
method="score",strata=stratum,eligible=candidates$eligible)
selected_family <- by_family$animal[by_family$selected]
stopifnot(setequal(selected_family,c("M1","M3","F1","F3")))
knitr::kable(by_family,row.names=FALSE)| animal | stratum | score | selected | inclusion_probability |
|---|---|---|---|---|
| M1 | M_A | 12 | TRUE | 1 |
| M2 | M_A | 11 | FALSE | 0 |
| M3 | M_B | 8 | TRUE | 1 |
| F1 | F_A | 10 | TRUE | 1 |
| F2 | F_A | 10 | FALSE | 0 |
| F3 | F_B | 7 | TRUE | 1 |
Question 2. What is gained and lost by requiring family representation?
Both families contribute selected candidates in each role. The mean selected score decreases because M3 and F3 replace higher-scoring candidates. This preserves representation of the declared groups; a claim about improved genetic diversity or lower offspring inbreeding would require relationships and the actual mating/contribution plan.
role_mean <- function(ids,role) {
animals <- candidates$animal[candidates$sex==role & candidates$animal %in% ids]
mean(scores[animals])
}
comparison <- data.frame(policy=c("Role quotas","Role and family quotas"),
sire_mean=c(role_mean(selected_role,"M"),role_mean(selected_family,"M")),
dam_mean=c(role_mean(selected_role,"F"),role_mean(selected_family,"F")))
comparison$expected_offspring_score <- with(comparison,(sire_mean+dam_mean)/2)
stopifnot(identical(comparison$expected_offspring_score,c(10.75,9.25)))
knitr::kable(comparison,row.names=FALSE)| policy | sire_mean | dam_mean | expected_offspring_score |
|---|---|---|---|
| Role quotas | 11.5 | 10.0 | 10.75 |
| Role and family quotas | 10.0 | 8.5 | 9.25 |
These values assume uniform contributions within each role. They are expected additive offspring merit based on supplied predictions, not observed gain or validation of those predictions.
Construct a small pedigree container and use the family-quota parents. Missing parentage is represented by NA. Treating these candidates as founders supplies no earlier ancestry: it does not establish that real candidates are unrelated, especially within the supplied family groups.
gsim_mate() draws each child’s sire and dam independently and uniformly with replacement. A selected parent can receive no offspring. It does not enforce balanced contributions or avoid related pairs.
parent_table <- data.frame(animal=candidates$animal,sire=NA_character_,
dam=NA_character_,sex=candidates$sex,generation=1L)
pedigree <- gsim_pedigree_from_table(parent_table)
sires <- selected_family[selected_family %in% candidates$animal[candidates$sex=="M"]]
dams <- selected_family[selected_family %in% candidates$animal[candidates$sex=="F"]]
children <- sprintf("O%02d",1:12)
mating <- gsim_mate(pedigree,sires=sires,dams=dams,offspring=children,seed=20261008)
plan <- mating$offspring
stopifnot(nrow(plan)==12,all(plan$sire %in% sires),all(plan$dam %in% dams))
repeat_plan <- gsim_mate(pedigree,sires=sires,dams=dams,
offspring=children,seed=20261008)
stopifnot(identical(plan,repeat_plan$offspring))
knitr::kable(plan[,c("animal","sire","dam")],row.names=FALSE)| animal | sire | dam |
|---|---|---|
| O01 | M1 | F3 |
| O02 | M3 | F3 |
| O03 | M1 | F3 |
| O04 | M1 | F3 |
| O05 | M3 | F3 |
| O06 | M1 | F3 |
| O07 | M1 | F1 |
| O08 | M1 | F3 |
| O09 | M3 | F3 |
| O10 | M1 | F3 |
| O11 | M3 | F1 |
| O12 | M3 | F1 |
The seed is passed to gsim, whose identity-keyed random streams do not use R’s global RNG. Repeating these inputs gives the same parentage. This step creates parentage records; inheritance and offspring phenotypes will be simulated in the next practical.
For \(N\) offspring, let \(n_j\) be the number of parental slots assigned to parent \(j\). Its fraction of transmitted genes is \(c_j=n_j/(2N)\). Sire contributions sum to one half, and dam contributions sum to one half. Under additive inheritance and mean-zero Mendelian sampling,
\[E(\overline H_{\mathrm{offspring}}\mid\mathrm{plan}) =\sum_j c_j\widehat H_j.\]
This is a prediction conditional on the supplied parental evaluations and model. If true parental merit were known, the same expression would give the conditional genetic expectation. It does not describe realized Mendelian sampling.
parent_ids <- c(sires,dams)
counts <- table(factor(c(plan$sire,plan$dam),levels=parent_ids))
contribution <- as.numeric(counts)/(2*nrow(plan))
names(contribution) <- parent_ids
expected_by_child <- (scores[plan$sire]+scores[plan$dam])/2
expected_by_contribution <- sum(contribution*scores[parent_ids])
stopifnot(abs(sum(contribution)-1)<1e-12,
abs(sum(contribution[sires])-.5)<1e-12,
abs(mean(expected_by_child)-expected_by_contribution)<1e-12)
knitr::kable(data.frame(parent=parent_ids,offspring_slots=as.numeric(counts),
gene_contribution=contribution),digits=3,row.names=FALSE)| parent | offspring_slots | gene_contribution |
|---|---|---|
| M1 | 7 | 0.292 |
| M3 | 5 | 0.208 |
| F1 | 3 | 0.125 |
| F3 | 9 | 0.375 |
expected_under_uniform_draws expected_given_drawn_plan
9.250000 9.041667
The unconditional expected mean is 9.25 for this selected pool. A finite drawn plan can have a different conditional expectation because parent counts vary. The number of selected parents therefore does not specify their realized contributions.
For comparison, use each of the four sire/dam combinations three times. This plan is an explicit teaching allocation, not an optimum mating method. We supply it directly to the pedigree validator rather than expecting random mating to enforce it.
pairs <- expand.grid(sire=sires,dam=dams,stringsAsFactors=FALSE)
balanced <- pairs[rep(seq_len(nrow(pairs)),each=3),]
balanced$animal <- sprintf("B%02d",seq_len(nrow(balanced)))
balanced$sex <- rep(c("M","F"),length.out=nrow(balanced))
balanced$generation <- 2L
balanced <- balanced[,names(parent_table)]
balanced_pedigree <- gsim_pedigree_from_table(rbind(parent_table,balanced))
balanced_counts <- table(factor(c(balanced$sire,balanced$dam),levels=parent_ids))
balanced_contribution <- as.numeric(balanced_counts)/(2*nrow(balanced))
stopifnot(all(balanced_counts==6),all(balanced_contribution==.25),
abs(mean((scores[balanced$sire]+scores[balanced$dam])/2)-9.25)<1e-12)
knitr::kable(balanced,row.names=FALSE)| animal | sire | dam | sex | generation |
|---|---|---|---|---|
| B01 | M1 | F1 | M | 2 |
| B02 | M1 | F1 | F | 2 |
| B03 | M1 | F1 | M | 2 |
| B04 | M3 | F1 | F | 2 |
| B05 | M3 | F1 | M | 2 |
| B06 | M3 | F1 | F | 2 |
| B07 | M1 | F3 | M | 2 |
| B08 | M1 | F3 | F | 2 |
| B09 | M1 | F3 | M | 2 |
| B10 | M3 | F3 | F | 2 |
| B11 | M3 | F3 | M | 2 |
| B12 | M3 | F3 | F | 2 |
Balanced use fixes these parent counts. It does not guarantee low inbreeding: this plan includes within-family pairs, and ancestry is incomplete. To assess a mating plan, offspring inbreeding requires parental kinship on a declared pedigree or genomic reference. Optimizing contributions or avoiding related matings is a separate policy problem.
Question 3. Could different pairings with the same contributions change expected mean additive merit?
Under the stated additive model, the expected mean depends on contributions, so rearranging pairs while keeping counts fixed preserves that mean. Pairing can change offspring inbreeding, segregation variance and nonadditive outcomes. Those consequences require additional genetic information; parental scores alone cannot quantify them.
one_each <- gsim_select(candidates$animal,n=c(M=1,F=1),scores=scores,
method="score",strata=candidates$sex,eligible=candidates$eligible)
stopifnot(setequal(one_each$animal[one_each$selected],c("M1","F1")))
mask <- candidates$eligible & candidates$animal!="M3"
capacity <- table(factor(stratum[mask],levels=names(quota)))
stopifnot(capacity["M_B"]==0)
custom_c <- c(M1=9,M3=3,F1=6,F3=6)/24
custom_mean <- sum(custom_c*scores[names(custom_c)])
stopifnot(abs(custom_mean-9.75)<1e-12)
custom_mean[1] 9.75
The M_B quota requests one parent but has zero eligible candidates. The policy must be revised explicitly. In the unequal-use example the contributions are 0.375, 0.125, 0.25 and 0.25; predicted offspring merit rises to 9.75 because the higher-scoring sire receives more slots. This is not an assessment of the diversity cost.
gsel owns objectives and programme policies; its current scope does not export a scientific policy optimizer. This practical supplies rules explicitly and uses existing gsim selection and mating interfaces for mechanics. No new package API is needed.
Continue with Practical 8: Simulating selection response to simulate inheritance and records for offspring cohorts. Keep the genetic architecture and reporting base fixed, separate predictions from simulated truth, and compare realized gain and diversity with these parental expectations.