library(readxl)
library(dplyr)
library(tidyr)
library(ggplot2)
library(knitr)
library(kableExtra)
working_dir <- if (dir.exists("C:/Users/SAlemu/OneDrive - CGIAR/Documents/Poultryaudit/Dataset")) {
  "C:/Users/SAlemu/OneDrive - CGIAR/Documents/Poultryaudit/Dataset"
} else {
  "/mnt/c/Users/SAlemu/OneDrive - CGIAR/Documents/Poultryaudit/Dataset"
}

pheno_3lines <- file.path(working_dir, "Additional_Poultry dataset_audit.xlsx")
pheno_wil    <- file.path(working_dir, "Dataset_evaluation_corrected.xlsx")
tilili_file  <- file.path(working_dir, "AllG-345.csv")

line_colors  <- c("LIL-20"  = "#3182ce", "BIL-20"  = "#38a169",
                  "NHIL-20" = "#805ad5", "WIL-20"  = "#ed8936",
                  "Tilili"  = "#e53e3e")
lil    <- read_excel(pheno_3lines, sheet = "LIL");  colnames(lil)  <- gsub(" ", "_", colnames(lil))
bil    <- read_excel(pheno_3lines, sheet = "BIL");  colnames(bil)  <- gsub(" ", "_", colnames(bil))
nhil   <- read_excel(pheno_3lines, sheet = "NHIL"); colnames(nhil) <- gsub(" ", "_", colnames(nhil))
wil    <- read_excel(pheno_wil);                    colnames(wil)  <- gsub(" ", "_", colnames(wil))
tilili <- read.csv(tilili_file, stringsAsFactors = FALSE, fileEncoding = "Latin1")
colnames(tilili) <- trimws(colnames(tilili))

for (nm in c("lil", "bil", "nhil", "wil")) {
  x <- get(nm)
  x$ID <- as.character(x$ID)
  x$Generation <- as.integer(x$Generation)
  x$Sire_ID <- as.character(x$Sire_ID)
  x$Dam_ID  <- as.character(x$Dam_ID)
  assign(nm, x)
}
tilili$ChickenID  <- as.character(tilili$ChickenID)
tilili$SireID     <- as.character(tilili$SireID)
tilili$DamId      <- as.character(tilili$DamId)
tilili$Generation <- as.integer(tilili$Generation)
# Wright (1931) Ne formula
ne_formula <- function(Nm, Nf) {
  if (is.na(Nm) | is.na(Nf) | Nm == 0 | Nf == 0) return(NA_real_)
  round((4 * Nm * Nf) / (Nm + Nf), 1)
}

# Keep only rows with valid sire AND dam
valid_parents <- function(df, sc, dc) {
  bad  <- c("", "0", "NA", "na")
  s_ok <- !(df[[sc]] %in% bad) & !is.na(df[[sc]])
  d_ok <- !(df[[dc]] %in% bad) & !is.na(df[[dc]])
  df[s_ok & d_ok, ]
}

# Ne from pedigree per generation
ne_pedigree <- function(df, line, sc, dc) {
  df2 <- valid_parents(df, sc, dc)
  df2 %>%
    group_by(Generation) %>%
    summarise(Nm = n_distinct(!!sym(sc)),
              Nf = n_distinct(!!sym(dc)),
              N_offspring = n(), .groups = "drop") %>%
    mutate(Line = line,
           Ne   = mapply(ne_formula, Nm, Nf)) %>%
    arrange(Generation)
}

1 Background

📋 Scope of This Report

This report estimates the Effective Population Size (Ne) for each generation across all five poultry breeding lines. Ne is the single most important metric for monitoring genetic diversity and the long-term sustainability of a breeding programme.

Line Generations with pedigree Current (latest) generation
LIL-20 G1, G2, G3 G3
BIL-20 G1, G2, G3 G3
NHIL-20 G1, G2, G3 G3
WIL-20 G1, G2, G3 G3
Tilili G3, G4, G5 G5

Important notes on data availability:

  • For the four ILRI lines, G1 is the base/founder generation — no sire or dam records exist for these animals as they were assembled before the programme began. Ne cannot be computed from pedigree for G1; it is reported as not available.
  • For Tilili, the programme entered at generation 3. Generations 1 and 2 are not available in the dataset.
  • Current generations (G3 for ILRI lines, G5 for Tilili) are highlighted in yellow throughout.

2 Methodology

🔬 How Effective Population Size Was Computed

The Formula

Effective population size was estimated using the Wright (1931) formula, which accounts for unequal sex ratios between sires and dams:

Ne = (4 × Nm × Nf) / (Nm + Nf)

where: Nm = number of unique sires that contributed offspring in a given generation Nf = number of unique dams that contributed offspring in a given generation

This formula captures the fact that when one sex is few in number, genetic diversity is limited far more than raw census size would suggest. For example, 6 sires mating with 27 dams gives Ne = (4×6×27)/(6+27) = 19.6 — a small fraction of the 33 breeding animals used.


Computation Steps

Step 1. From the pedigree records, retain only individuals that have both a recorded Sire ID and a Dam ID. Records with missing, blank, or placeholder values (e.g., “0”, “NA”) are excluded.

Step 2. For each generation, count the number of unique sires (Nm) and unique dams (Nf) among the parents of that generation’s offspring.

Step 3. Apply Ne = (4 × Nm × Nf) / (Nm + Nf).

Step 4. For G1 (ILRI base) and Tilili G1–G2: no parent records exist, so Ne is reported as not available (NA).


Generation Labelling
Generation ILRI Lines Tilili
G1 Base/founder flock — no pedigree, Ne = NA Not available
G2 First selection generation — pedigree available Not available
G3 Current generation ★ — pedigree available First available generation
G4 Second generation
G5 Current generation ★

3 Results

3.1 Ne per Line and Generation

# Pedigree-based Ne: G2 & G3 for ILRI lines
ilri_ped <- bind_rows(
  ne_pedigree(lil,  "LIL-20",  "Sire_ID", "Dam_ID"),
  ne_pedigree(bil,  "BIL-20",  "Sire_ID", "Dam_ID"),
  ne_pedigree(nhil, "NHIL-20", "Sire_ID", "Dam_ID"),
  ne_pedigree(wil,  "WIL-20",  "Sire_ID", "Dam_ID")
) %>%
  filter(Generation %in% c(1L, 2L)) %>%
  mutate(Gen_label = ifelse(Generation == 1L, "G2", "G3"))

# Add G1 rows as NA (no pedigree structure for base generation)
g1_rows <- data.frame(
  Line        = c("LIL-20","BIL-20","NHIL-20","WIL-20"),
  Generation  = 0L,
  Gen_label   = "G1",
  Nm          = NA_integer_,
  Nf          = NA_integer_,
  N_offspring = NA_integer_,
  Ne          = NA_real_
)

# Pedigree-based Ne: G3–G5 for Tilili
til_ped <- ne_pedigree(tilili, "Tilili", "SireID", "DamId") %>%
  mutate(Gen_label = paste0("G", Generation))

# Combine all
ne_all <- bind_rows(
  g1_rows,
  ilri_ped %>% select(Line, Generation, Gen_label, Nm, Nf, N_offspring, Ne),
  til_ped  %>% select(Line, Generation, Gen_label, Nm, Nf, N_offspring, Ne)
)

3.1.1 ILRI Lines

ilri_tbl <- ne_all %>%
  filter(Line != "Tilili") %>%
  arrange(Line, Generation) %>%
  mutate(
    `Ne Calculation` = case_when(
      is.na(Nm) ~ "No pedigree — sire/dam not recorded for base generation",
      TRUE ~ paste0("(4×",Nm,"×",Nf,") / (",Nm,"+",Nf,")")
    ),
    Ne_display = ifelse(is.na(Ne), "—", as.character(Ne))
  ) %>%
  select(Line,
         Generation = Gen_label,
         `Nm (Sires)` = Nm,
         `Nf (Dams)`  = Nf,
         `N Offspring` = N_offspring,
         `Ne Calculation`,
         Ne = Ne_display)

current_rows <- which(ilri_tbl$Generation == "G3")
g1_rows_idx  <- which(ilri_tbl$Generation == "G1")

ilri_tbl %>%
  kable(align = c("l","c","r","r","r","l","r"),
        caption = "Table 1. Effective Population Size — ILRI Lines (LIL-20, BIL-20, NHIL-20, WIL-20)") %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = TRUE, font_size = 14) %>%
  row_spec(0, bold = TRUE, background = "#2d3748", color = "white") %>%
  row_spec(g1_rows_idx,  italic = TRUE, color = "#718096", background = "#f7fafc") %>%
  row_spec(current_rows, bold   = TRUE, background = "#fef3c7") %>%
  column_spec(7, bold = TRUE, color = "#c05621") %>%
  pack_rows("LIL-20",  1, 3, bold = TRUE, background = "#eaf4ff") %>%
  pack_rows("BIL-20",  4, 6, bold = TRUE, background = "#eaf4ff") %>%
  pack_rows("NHIL-20", 7, 9, bold = TRUE, background = "#eaf4ff") %>%
  pack_rows("WIL-20", 10, 12, bold = TRUE, background = "#eaf4ff") %>%
  footnote(
    general = "★ Yellow rows = current (G3) generation.  G1 (grey/italic) = base flock; no sire or dam records exist, Ne cannot be estimated from pedigree.  Formula: Ne = (4 × Nm × Nf) / (Nm + Nf), Wright (1931).",
    general_title = "Note: "
  )
Table 1. Effective Population Size — ILRI Lines (LIL-20, BIL-20, NHIL-20, WIL-20)
Line Generation Nm (Sires) Nf (Dams) N Offspring Ne Calculation Ne
LIL-20
BIL-20 G1 NA NA NA No pedigree — sire/dam not recorded for base generation
BIL-20 G2 6 27 155 (4×6×27) / (6+27) 19.6
BIL-20 G3 8 39 342 (4×8×39) / (8+39) 26.6
BIL-20
LIL-20 G1 NA NA NA No pedigree — sire/dam not recorded for base generation
LIL-20 G2 11 22 157 (4×11×22) / (11+22) 29.3
LIL-20 G3 12 43 446 (4×12×43) / (12+43) 37.5
NHIL-20
NHIL-20 G1 NA NA NA No pedigree — sire/dam not recorded for base generation
NHIL-20 G2 5 24 151 (4×5×24) / (5+24) 16.6
NHIL-20 G3 6 27 244 (4×6×27) / (6+27) 19.6
WIL-20
WIL-20 G1 NA NA NA No pedigree — sire/dam not recorded for base generation
WIL-20 G2 7 24 92 (4×7×24) / (7+24) 21.7
WIL-20 G3 9 23 306 (4×9×23) / (9+23) 25.9
Note:
★ Yellow rows = current (G3) generation. G1 (grey/italic) = base flock; no sire or dam records exist, Ne cannot be estimated from pedigree. Formula: Ne = (4 × Nm × Nf) / (Nm + Nf), Wright (1931).

📊 ILRI Lines — Key Findings

  • G1 (Base): No pedigree structure — sire and dam IDs were not recorded for the founder flock. Ne cannot be estimated from these records.
  • G2 (First selection generation): Ne ranges from 16.6 to 29.3 across the four lines, driven by the strict 1:4 mating design in which genetic contribution is concentrated in a small number of sires.
  • G3 (Current generation ★): Ne is 19.6–37.5 across lines. All four lines remain below the internationally recommended minimum threshold of Ne = 50.

3.1.2 Tilili

til_tbl <- ne_all %>%
  filter(Line == "Tilili") %>%
  arrange(Generation) %>%
  mutate(
    `Ne Calculation` = paste0("(4×",Nm,"×",Nf,") / (",Nm,"+",Nf,")"),
    Ne_display = as.character(Ne)
  ) %>%
  select(Line,
         Generation = Gen_label,
         `Nm (Sires)` = Nm,
         `Nf (Dams)`  = Nf,
         `N Offspring` = N_offspring,
         `Ne Calculation`,
         Ne = Ne_display)

til_tbl %>%
  kable(align = c("l","c","r","r","r","l","r"),
        caption = "Table 2. Effective Population Size — Tilili") %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = TRUE, font_size = 14) %>%
  row_spec(0, bold = TRUE, background = "#234e52", color = "white") %>%
  row_spec(3, bold = TRUE, background = "#fef3c7") %>%
  column_spec(7, bold = TRUE, color = "#c05621") %>%
  footnote(
    general = "★ Yellow row = current (G5) generation.  Tilili entered the programme at G3; G1 and G2 are not available.",
    general_title = "Note: "
  )
Table 2. Effective Population Size — Tilili
Line Generation Nm (Sires) Nf (Dams) N Offspring Ne Calculation Ne
Tilili G3 48 212 722 (4×48×212) / (48+212) 156.6
Tilili G4 44 289 4112 (4×44×289) / (44+289) 152.7
Tilili G5 16 87 1072 (4×16×87) / (16+87) 54.1
Note:
★ Yellow row = current (G5) generation. Tilili entered the programme at G3; G1 and G2 are not available.

📊 Tilili — Key Findings

  • G3: Ne = 156.6 (48 sires × 212 dams). The large flock size and natural mating system (1:10 ratio) sustain strong genetic diversity.
  • G4: Ne = 152.7 (44 sires × 289 dams), confirming stable diversity across two generations.
  • G5 (Current ★): Ne dropped sharply to 54.1 (16 sires × 87 dams). Although still above the Ne = 50 minimum threshold, the 65% decline in one generation requires prompt attention to sire numbers before the next mating round.

3.2 Summary Dashboard — Current Generation Ne

37.5
LIL-20 – G3
Current Ne
26.6
BIL-20 – G3
Current Ne
19.6
NHIL-20 – G3
Current Ne
25.9
WIL-20 – G3
Current Ne
54.1
Tilili – G5
Current Ne

Ne threshold colour guide: ■ Ne ≥ 100  Safe ■ Ne 50–99  Caution ■ Ne 30–49  Warning ■ Ne < 30  Critical


3.3 Ne Trend Across Generations

plot_data <- ne_all %>%
  filter(!is.na(Ne)) %>%
  mutate(
    x = case_when(
      Line != "Tilili" & Gen_label == "G2" ~ 2,
      Line != "Tilili" & Gen_label == "G3" ~ 3,
      Line == "Tilili" & Gen_label == "G3" ~ 3,
      Line == "Tilili" & Gen_label == "G4" ~ 4,
      Line == "Tilili" & Gen_label == "G5" ~ 5,
      TRUE ~ NA_real_
    ),
    Is_current = (Line != "Tilili" & Gen_label == "G3") |
                 (Line == "Tilili"  & Gen_label == "G5")
  ) %>%
  filter(!is.na(x))

x_labels <- c(
  "2" = "G2\n(ILRI 1st selection)",
  "3" = "G3\n★ Current ILRI / Tilili G3",
  "4" = "G4\n(Tilili)",
  "5" = "G5\n★ Current Tilili"
)

ggplot(plot_data, aes(x = x, y = Ne, color = Line, group = Line)) +
  annotate("rect", xmin=-Inf, xmax=Inf, ymin=0,   ymax=50,  fill="#fff5f5", alpha=0.55) +
  annotate("rect", xmin=-Inf, xmax=Inf, ymin=50,  ymax=100, fill="#fffaf0", alpha=0.50) +
  annotate("rect", xmin=-Inf, xmax=Inf, ymin=100, ymax=Inf, fill="#f0fff4", alpha=0.40) +
  geom_hline(yintercept = 50,  linetype = "dashed", color = "#e53e3e", linewidth = 1.0) +
  geom_hline(yintercept = 100, linetype = "dotted", color = "#ed8936", linewidth = 0.9) +
  annotate("text", x = 2.05, y = 54,  label = "Ne = 50  minimum safe threshold",
           color = "#e53e3e", hjust = 0, size = 3.5, fontface = "bold") +
  annotate("text", x = 2.05, y = 104, label = "Ne = 100  recommended",
           color = "#ed8936", hjust = 0, size = 3.5, fontface = "bold") +
  geom_line(linewidth = 1.4, alpha = 0.85) +
  geom_point(data = plot_data %>% filter(!Is_current), size = 3.5, shape = 16, alpha = 0.9) +
  geom_point(data = plot_data %>% filter(Is_current),  size = 6,   shape = 18, alpha = 1.0) +
  geom_label(data = plot_data %>% filter(Is_current),
             aes(label = paste0("Ne = ", Ne)),
             vjust = -0.9, fontface = "bold", size = 3.8,
             fill = "#fef3c7", color = "#744210", label.size = 0.3) +
  scale_color_manual(values = line_colors) +
  scale_x_continuous(breaks = 2:5, labels = x_labels, limits = c(1.8, 5.5)) +
  scale_y_continuous(limits = c(0, 200), breaks = seq(0, 200, 25)) +
  labs(
    title    = "Effective Population Size (Ne) Across Generations",
    subtitle = "★ = Current generation  |  Background shading: red = critical zone, amber = caution, green = safe",
    x = "Generation", y = "Effective Population Size (Ne)", color = "Breeding Line"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title       = element_text(face = "bold", size = 15, color = "#1a365d"),
    plot.subtitle    = element_text(size = 11, color = "#4a5568"),
    legend.position  = "bottom",
    legend.text      = element_text(size = 12),
    panel.grid.minor = element_blank(),
    axis.title       = element_text(face = "bold", size = 12),
    axis.text.x      = element_text(size = 11, lineheight = 1.3)
  )


3.4 Complete Summary Table

summary_tbl <- ne_all %>%
  mutate(
    Nm_disp = ifelse(is.na(Nm), "—", as.character(Nm)),
    Nf_disp = ifelse(is.na(Nf), "—", as.character(Nf)),
    N_disp  = ifelse(is.na(N_offspring), "—", as.character(N_offspring)),
    Ne_disp = ifelse(is.na(Ne), "—  (no pedigree)", as.character(Ne))
  ) %>%
  arrange(Line, Generation) %>%
  select(Line, Generation = Gen_label,
         `Nm (Sires)` = Nm_disp, `Nf (Dams)` = Nf_disp,
         `N Offspring` = N_disp, Ne = Ne_disp)

is_current <- (summary_tbl$Line != "Tilili" & summary_tbl$Generation == "G3") |
              (summary_tbl$Line == "Tilili"  & summary_tbl$Generation == "G5")
is_g1      <-  summary_tbl$Line != "Tilili" & summary_tbl$Generation == "G1"

summary_tbl %>%
  kable(
    align   = c("l","c","r","r","r","r"),
    caption = "Table 3. Complete Effective Population Size Summary — All Lines and Generations"
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = TRUE, font_size = 15) %>%
  row_spec(0, bold = TRUE, background = "#2d3748", color = "white") %>%
  row_spec(which(is_g1),      italic = TRUE, color = "#718096", background = "#f7fafc") %>%
  row_spec(which(is_current), bold   = TRUE, background = "#fef3c7") %>%
  column_spec(6, bold = TRUE, color = "#c05621") %>%
  pack_rows("LIL-20",  1,  3, bold = TRUE) %>%
  pack_rows("BIL-20",  4,  6, bold = TRUE) %>%
  pack_rows("NHIL-20", 7,  9, bold = TRUE) %>%
  pack_rows("WIL-20", 10, 12, bold = TRUE) %>%
  pack_rows("Tilili", 13, 15, bold = TRUE) %>%
  footnote(
    general = "★ Yellow = current generation. Grey/italic = base generation with no pedigree structure. Formula: Ne = (4 × Nm × Nf) / (Nm + Nf).",
    general_title = "Note: "
  )
Table 3. Complete Effective Population Size Summary — All Lines and Generations
Line Generation Nm (Sires) Nf (Dams) N Offspring Ne
LIL-20
BIL-20 G1 — (no pedigree)
BIL-20 G2 6 27 155 19.6
BIL-20 G3 8 39 342 26.6
BIL-20
LIL-20 G1 — (no pedigree)
LIL-20 G2 11 22 157 29.3
LIL-20 G3 12 43 446 37.5
NHIL-20
NHIL-20 G1 — (no pedigree)
NHIL-20 G2 5 24 151 16.6
NHIL-20 G3 6 27 244 19.6
WIL-20
Tilili G3 48 212 722 156.6
Tilili G4 44 289 4112 152.7
Tilili G5 16 87 1072 54.1
Tilili
WIL-20 G1 — (no pedigree)
WIL-20 G2 7 24 92 21.7
WIL-20 G3 9 23 306 25.9
Note:
★ Yellow = current generation. Grey/italic = base generation with no pedigree structure. Formula: Ne = (4 × Nm × Nf) / (Nm + Nf).

4 Threshold Assessment

threshold_tbl <- data.frame(
  Line        = c("LIL-20","BIL-20","NHIL-20","WIL-20","Tilili"),
  Current_Gen = c("G3","G3","G3","G3","G5"),
  Ne_current  = c(37.5, 26.6, 19.6, 25.9, 54.1),
  Status      = c("⚠️ Warning","🔴 Critical","🔴 Critical","🔴 Critical","🟡 Caution"),
  Assessment  = c(
    "Ne 30–50: elevated genetic drift; increase sires",
    "Ne < 30: rapid inbreeding accumulation; urgent action",
    "Ne < 20: most critical line; immediate sire increase required",
    "Ne < 30: rapid inbreeding accumulation; urgent action",
    "Ne > 50: currently safe but declining; monitor closely"
  ),
  check.names = FALSE
)

threshold_tbl %>%
  kable(
    col.names = c("Line","Current Gen","Ne (Current ★)","Status","Assessment"),
    align     = c("l","c","r","c","l"),
    caption   = "Table 4. Ne Threshold Assessment — Current Generation"
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = TRUE, font_size = 14) %>%
  row_spec(0, bold = TRUE, background = "#2d3748", color = "white") %>%
  row_spec(1,   bold = TRUE, background = "#fffbeb") %>%
  row_spec(2:4, bold = TRUE, background = "#fff5f5") %>%
  row_spec(5,   bold = TRUE, background = "#f0fff4") %>%
  column_spec(3, bold = TRUE, color = "#c05621")
Table 4. Ne Threshold Assessment — Current Generation
Line Current Gen Ne (Current ★) Status Assessment
LIL-20 G3 37.5 ⚠️ Warning |Ne 30–50: elevated genetic drift; increase sires
BIL-20 G3 26.6 🔴 Critical | e < 30: rapid inbreeding accumulation; urgent action |
NHIL-20 G3 19.6 🔴 Critical | e < 20: most critical line; immediate sire increase required |
WIL-20 G3 25.9 🔴 Critical | e < 30: rapid inbreeding accumulation; urgent action |
Tilili G5 54.1 🟡 Caution | e > 50: currently safe but declining; monitor closely |

📏 Internationally Recognised Ne Thresholds

Ne Range Risk Level Biological Meaning
Ne ≥ 100 ✅ Safe Genetic drift negligible; long-term diversity maintained
50 ≤ Ne < 100 🟡 Caution ΔF < 1%/generation; acceptable short-term; monitor closely
30 ≤ Ne < 50 ⚠️ Warning ΔF 1–2%/generation; inbreeding accumulates noticeably
Ne < 30 🔴 Critical ΔF > 2%/generation; rapid loss of diversity; urgent intervention

Sources: FAO (1998) Guidelines for Breed Management; Frankham, Ballou & Briscoe (2014)


5 How to Recover Effective Population Size

💡 Is Recovery Possible?

Yes — Ne can be increased within a single generation. Unlike inbreeding already accumulated in the pedigree, Ne is determined by the mating decisions made now, specifically the number and balance of sires and dams used in the next round. Targeted adjustments to the mating programme can bring all lines back above the Ne = 50 threshold within one generation.

5.1 What Is Needed — Sire Numbers to Reach Ne = 50

# How many sires are needed to reach Ne = 50, given current dam numbers
# Ne = 50 → 50(Nm + Nf) = 4*Nm*Nf → solve for Nm:  Nm = 50*Nf / (4*Nf - 50)
# Current dam numbers from pedigree data (G3 for ILRI, G5 for Tilili)
current_dams <- c(43, 39, 27, 23, 87)   # LIL, BIL, NHIL, WIL, Tilili
current_sires<- c(12,  8,  6,  9, 16)
current_ne   <- c(37.5, 26.6, 19.6, 25.9, 54.1)

sires_needed_50  <- ceiling(50 * current_dams / (4 * current_dams - 50))
sires_needed_100 <- ceiling(100 * current_dams / (4 * current_dams - 100))
ne_if_doubled    <- round((4 * (current_sires * 2) * current_dams) /
                          ((current_sires * 2) + current_dams), 1)

rec_tbl <- data.frame(
  Line          = c("LIL-20","BIL-20","NHIL-20","WIL-20","Tilili"),
  Current_Gen   = c("G3","G3","G3","G3","G5"),
  Current_Nm    = current_sires,
  Current_Nf    = current_dams,
  Current_Ne    = current_ne,
  Sires_for_Ne50  = sires_needed_50,
  Sires_for_Ne100 = sires_needed_100,
  Ne_if_doubled = ne_if_doubled,
  check.names   = FALSE
)

rec_tbl %>%
  kable(
    col.names = c("Line","Gen","Current Sires","Current Dams","Current Ne",
                  "Sires needed\nfor Ne = 50","Sires needed\nfor Ne = 100",
                  "Ne if sires doubled"),
    align = c("l","c","r","r","r","r","r","r"),
    caption = "Table 5. Sire Numbers Required to Recover Ne — Next Generation"
  ) %>%
  kable_styling(bootstrap_options = c("striped","hover","bordered"),
                full_width = TRUE, font_size = 14) %>%
  row_spec(0, bold = TRUE, background = "#2d3748", color = "white") %>%
  row_spec(1:4, background = "#fff5f5") %>%
  row_spec(5,   background = "#f0fff4") %>%
  column_spec(6, bold = TRUE, color = "#276749", background = "#f0fff4") %>%
  column_spec(8, bold = TRUE, color = "#3182ce") %>%
  footnote(
    general = "Sires needed for Ne = 50 calculated by solving Ne = (4×Nm×Nf)/(Nm+Nf) = 50 for Nm, given current dam numbers. 'Ne if doubled' assumes twice the current number of sires with the same dams.",
    general_title = "Note: "
  )
Table 5. Sire Numbers Required to Recover Ne — Next Generation
Line Gen Current Sires Current Dams Current Ne Sires needed for Ne = 5
Sires needed
for Ne = 1
0| Ne if sires doubl
LIL-20 G3 12 43 37.5 18 60 61.6
BIL-20 G3 8 39 26.6 19 70 45.4
NHIL-20 G3 6 27 19.6 24 338 33.2
WIL-20 G3 9 23 25.9 28 -287 40.4
Tilili G5 16 87 54.1 15 36 93.6
Note:
Sires needed for Ne = 50 calculated by solving Ne = (4×Nm×Nf)/(Nm+Nf) = 50 for Nm, given current dam numbers. ‘Ne if doubled’ assumes twice the current number of sires with the same dams.

✅ Practical Recommendations

1. Increase the number of breeding sires — highest impact action

The table above shows the exact number of additional sires required. For example:

NHIL-20: increase sires from 6 → 8 gives Ne = (4×8×27)/(8+27) = 24.7 NHIL-20: increase sires from 6 → 14 gives Ne = (4×14×27)/(14+27) = 36.8 NHIL-20: increase sires from 6 → 20 gives Ne = (4×20×27)/(20+27) = 45.9 NHIL-20: increase sires from 6 → 27 gives Ne = (4×27×27)/(27+27) = 54.0 ✔ above threshold

2. Rotate sires across generations

  • Replace at least 50% of sires each generation with unrelated males
  • Avoid using a son of the previous generation’s sire — this concentrates pedigree and reduces effective Ne

3. Equalise family sizes — avoid the “popular sire” effect

  • When one sire produces far more offspring than others, realised Ne falls below the formula value
  • Allocate dams as evenly as possible across sires

4. For Tilili (G6 onwards)

  • G5 used only 16 sires; recruit at least 10 additional unrelated sires before G6 mating to maintain Ne ≥ 50
  • The large dam population (87+) means even modest sire increases yield big Ne gains

5. Long-term: Optimal Contribution Selection (OCS)

  • OCS simultaneously maximises genetic gain and constrains the rate of inbreeding to ΔF ≤ 1% per generation
  • Can be implemented using the optiSel package in R