library(tidyverse)
library(janitor)
library(knitr)
library(kableExtra)
library(scales)
library(gridExtra)

# COLORBLIND-FRIENDLY PALETTE (Tol's bright qualitative palette)
gen_colors <- c("G3" = "#0077BB", "G4" = "#EE7733", "G5" = "#009988")
batch_colors <- c("0" = "#0077BB", "3" = "#33BBEE", "4" = "#009988", 
                  "5" = "#EE7733", "6" = "#CC3311", "7" = "#EE3377")

# Inbreeding category colors (colorblind-friendly, neutral tones)
inbreeding_colors <- c(
  "Non-inbred (F = 0%)" = "#009988",
  "Below 2nd cousin (F < 3.125%)" = "#33BBEE",
  "2nd cousin level (F < 6.25%)" = "#0077BB",
  "1st cousin level (F < 12.5%)" = "#EE7733",
  "Half-sib level (F < 25%)" = "#AA7744",
  "Full-sib level (F ≥ 25%)" = "#7744AA"
)

# Custom theme
theme_tilili <- function() {
  theme_minimal(base_size = 12) +
    theme(
      plot.title = element_text(face = "bold", size = 14, hjust = 0, color = "#234e52"),
      plot.subtitle = element_text(size = 11, hjust = 0, color = "#4a5568"),
      legend.position = "bottom",
      panel.grid.minor = element_blank(),
      axis.title = element_text(face = "bold", size = 11),
      strip.text = element_text(face = "bold", size = 11)
    )
}
theme_set(theme_tilili())
# Base directory
base_dir <- "C:/Users/SAlemu/OneDrive - CGIAR/Documents/Poultryaudit/Dataset"

# Body weight data and WOMBAT results
wombat_base <- file.path(base_dir, "Analysis_with_wombat2")
tilili_pheno_file <- file.path(base_dir, "AllG-345.csv")
tilili_wombat_dir <- file.path(wombat_base, "Tilili", "Model1_Animal_YearMonth")

# Egg production data
egg_weight_file <- file.path(wombat_base, "TililiAEW", "Masterfile_AEW.csv")
egg_number_file <- file.path(wombat_base, "Tililieggnum", "Masterfile_egg.csv")

1 Executive Summary

This report presents a comprehensive genetic evaluation of the Tilili local Ethiopian chicken ecotype, including:

  1. Body Weight Genetic Parameters - Heritability and variance components for BW at 16 weeks
  2. Inbreeding Analysis - Detailed classification based on biological relationship levels
  3. Egg Weight Analysis - Weekly means and trends across generations (Weeks 31-61)
  4. Egg Number Analysis - Weekly means and trends across generations (Weeks 31-61)

🐔 About the Tilili Line

Tilili is a local Ethiopian chicken ecotype included in ILRI’s breeding program to improve productivity while maintaining adaptation to smallholder scavenging conditions. The breed is managed under a semi-scavenging system with 80% restricted feeding during the outdoor phase (weeks 9-16) to simulate smallholder farming conditions.

Attribute Details
Origin Local Ethiopian ecotype
Target Environment Smallholder semi-scavenging systems
Housing Phase 1: Indoor (Wk 0-9), Phase 2: Free-range (Wk 9-16)
Feeding 80% restricted feeding during outdoor phase
Selection Traits Body weight, egg production, adaptability

2 Part 1: Body Weight Genetic Parameters

# Load Tilili phenotype data
tilili_raw <- read.csv(tilili_pheno_file, stringsAsFactors = FALSE, fileEncoding = "Latin1")
colnames(tilili_raw) <- trimws(colnames(tilili_raw))

tilili_data <- data.frame(
  ID = as.character(tilili_raw$ChickenID),
  Sire_ID = as.character(tilili_raw$SireID),
  Dam_ID = as.character(tilili_raw$DamId),
  Sex = as.character(tilili_raw$Sex),
  Batch = as.character(tilili_raw$Batch),
  Generation = as.character(tilili_raw$Generation),
  BW_16wk = as.numeric(tilili_raw$Week16),
  stringsAsFactors = FALSE
)

# Clean parent IDs
tilili_data$Sire_ID[tilili_data$Sire_ID %in% c("", "0", "NA", "na") | is.na(tilili_data$Sire_ID)] <- NA
tilili_data$Dam_ID[tilili_data$Dam_ID %in% c("", "0", "NA", "na") | is.na(tilili_data$Dam_ID)] <- NA

# Summary statistics
n_total <- nrow(tilili_data)
n_with_bw <- sum(!is.na(tilili_data$BW_16wk))
mean_bw <- round(mean(tilili_data$BW_16wk, na.rm = TRUE), 0)
sd_bw <- round(sd(tilili_data$BW_16wk, na.rm = TRUE), 0)
cv_bw <- round(sd_bw / mean_bw * 100, 1)

male_bw <- round(mean(tilili_data$BW_16wk[tilili_data$Sex == "M"], na.rm = TRUE), 0)
female_bw <- round(mean(tilili_data$BW_16wk[tilili_data$Sex == "F"], na.rm = TRUE), 0)
dimorphism <- round((male_bw - female_bw) / female_bw * 100, 1)

gen_counts <- table(tilili_data$Generation)
# Extract WOMBAT variance component results
sum_est_file <- file.path(tilili_wombat_dir, "SumEstimates.out")
ebv_file <- file.path(tilili_wombat_dir, "RnSoln_animal.dat")
id_map_file <- file.path(wombat_base, "Tilili", "ID_mapping.csv")

# Initialize
Va <- NA; Va_SE <- NA; Ve <- NA; Ve_SE <- NA; Vp <- NA; h2 <- NA; h2_SE <- NA; N_rec <- NA
tilili_vc <- NULL
tilili_ebv <- NULL

if (file.exists(sum_est_file)) {
  sum_lines <- readLines(sum_est_file)
  
  # Extract Va and h2
  covs_a_idx <- grep("COVS A 1 1.*vrat", sum_lines)
  if (length(covs_a_idx) > 0) {
    covs_a_line <- sum_lines[covs_a_idx[1]]
    nums <- as.numeric(unlist(regmatches(covs_a_line, gregexpr("[0-9]+\\.?[0-9]*", covs_a_line))))
    if (length(nums) >= 7) { Va <- nums[4]; Va_SE <- nums[5]; h2 <- nums[6]; h2_SE <- nums[7] }
  }
  
  # Extract Ve
  covs_z_idx <- grep("COVS Z 1 1.*vrat", sum_lines)
  if (length(covs_z_idx) > 0) {
    covs_z_line <- sum_lines[covs_z_idx[1]]
    nums_z <- as.numeric(unlist(regmatches(covs_z_line, gregexpr("[0-9]+\\.?[0-9]*", covs_z_line))))
    if (length(nums_z) >= 5) { Ve <- nums_z[4]; Ve_SE <- nums_z[5] }
  }
  
  # Extract Vp
  covs_t_idx <- grep("COVS T 1 1", sum_lines)
  if (length(covs_t_idx) > 0) {
    covs_t_line <- sum_lines[covs_t_idx[1]]
    nums_t <- as.numeric(unlist(regmatches(covs_t_line, gregexpr("[0-9]+\\.?[0-9]*", covs_t_line))))
    if (length(nums_t) >= 5) { Vp <- nums_t[4] }
  }
  
  # Extract N records
  rec_idx <- grep("No. of records", sum_lines)
  if (length(rec_idx) > 0) {
    rec_line <- sum_lines[rec_idx[1]]
    nums_rec <- as.numeric(unlist(regmatches(rec_line, gregexpr("[0-9]+", rec_line))))
    N_rec <- nums_rec[1]
  }
  
  if (!is.na(h2)) {
    tilili_vc <- data.frame(
      Line = "Tilili", N = ifelse(!is.na(N_rec), N_rec, NA),
      Va = round(Va, 1), Va_SE = round(Va_SE, 1),
      Ve = round(Ve, 1), Ve_SE = round(Ve_SE, 1),
      Vp = round(Vp, 1), h2 = round(h2, 3), h2_SE = round(h2_SE, 3)
    )
  }
}

# Extract EBV results
if (file.exists(ebv_file) && file.exists(id_map_file)) {
  ebv_lines <- readLines(ebv_file)
  header_idx <- grep("Run_No", ebv_lines)
  
  if (length(header_idx) > 0) {
    data_start <- header_idx[1] + 1
    data_lines <- ebv_lines[data_start:length(ebv_lines)]
    data_lines <- data_lines[nchar(trimws(data_lines)) > 0]
    
    ebv_data <- read.table(text = data_lines, header = FALSE, fill = TRUE)
    
    if (ncol(ebv_data) >= 7) {
      colnames(ebv_data) <- c("Run_No", "ID_num", "Trait", "EBV", "SE_EBV", "Accuracy", "Inbreeding_pct")
    } else {
      colnames(ebv_data) <- c("Run_No", "ID_num", "Trait", "EBV", "Inbreeding_pct")[1:ncol(ebv_data)]
    }
    
    id_map <- read.csv(id_map_file, stringsAsFactors = FALSE)
    ebv_merged <- merge(ebv_data, id_map, by.x = "ID_num", by.y = "ID_num", all.x = TRUE)
    
    tilili_ebv <- data.frame(
      ID_original = as.character(ebv_merged$ID_orig),
      ID_wombat = ebv_merged$ID_num, 
      EBV = ebv_merged$EBV,
      SE_EBV = if ("SE_EBV" %in% colnames(ebv_merged)) ebv_merged$SE_EBV else NA,
      Accuracy = if ("Accuracy" %in% colnames(ebv_merged)) ebv_merged$Accuracy else NA,
      Inbreeding_pct = if ("Inbreeding_pct" %in% colnames(ebv_merged)) ebv_merged$Inbreeding_pct else NA
    )
    
    # Add biologically meaningful inbreeding categories
    tilili_ebv <- tilili_ebv %>%
      mutate(
        Inbreeding_Category = case_when(
          is.na(Inbreeding_pct) ~ NA_character_,
          Inbreeding_pct == 0 ~ "Non-inbred (F = 0%)",
          Inbreeding_pct < 3.125 ~ "Below 2nd cousin (F < 3.125%)",
          Inbreeding_pct < 6.25 ~ "2nd cousin level (F < 6.25%)",
          Inbreeding_pct < 12.5 ~ "1st cousin level (F < 12.5%)",
          Inbreeding_pct < 25 ~ "Half-sib level (F < 25%)",
          TRUE ~ "Full-sib level (F ≥ 25%)"
        ),
        Inbreeding_Category = factor(Inbreeding_Category, levels = c(
          "Non-inbred (F = 0%)",
          "Below 2nd cousin (F < 3.125%)",
          "2nd cousin level (F < 6.25%)",
          "1st cousin level (F < 12.5%)",
          "Half-sib level (F < 25%)",
          "Full-sib level (F ≥ 25%)"
        ))
      )
  }
}

# Calculate inbreeding summary statistics
if (!is.null(tilili_ebv)) {
  n_non_inbred <- sum(tilili_ebv$Inbreeding_pct == 0, na.rm = TRUE)
  n_negligible <- sum(tilili_ebv$Inbreeding_pct > 0 & tilili_ebv$Inbreeding_pct < 3.125, na.rm = TRUE)
  n_low <- sum(tilili_ebv$Inbreeding_pct >= 3.125 & tilili_ebv$Inbreeding_pct < 6.25, na.rm = TRUE)
  n_moderate <- sum(tilili_ebv$Inbreeding_pct >= 6.25 & tilili_ebv$Inbreeding_pct < 12.5, na.rm = TRUE)
  n_high <- sum(tilili_ebv$Inbreeding_pct >= 12.5 & tilili_ebv$Inbreeding_pct < 25, na.rm = TRUE)
  n_severe <- sum(tilili_ebv$Inbreeding_pct >= 25, na.rm = TRUE)
  n_concern <- n_moderate + n_high + n_severe  # Animals requiring attention
}

2.1 Population Overview

5,906
Total Birds
3,783
With BW_16wk
1073g
Mean Weight
0.196
Heritability
3
Generations

2.2 Phenotypic Summary

data.frame(
  Metric = c("Total Birds", "Birds with BW_16wk", "Mean BW_16wk (g)", "SD BW_16wk (g)", 
             "CV (%)", "Min BW_16wk (g)", "Max BW_16wk (g)",
             "Male BW_16wk (g)", "Female BW_16wk (g)", "Sexual Dimorphism (%)"),
  Value = c(n_total, n_with_bw, mean_bw, sd_bw, cv_bw,
            round(min(tilili_data$BW_16wk, na.rm = TRUE), 0),
            round(max(tilili_data$BW_16wk, na.rm = TRUE), 0),
            male_bw, female_bw, dimorphism)
) %>%
  kable(align = c("l", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  row_spec(0, bold = TRUE, background = "#319795", color = "white")
Metric Value
Total Birds 5906.0
Birds with BW_16wk 3783.0
Mean BW_16wk (g) 1073.0
SD BW_16wk (g) 303.0
CV (%) 28.2
Min BW_16wk (g) 110.0
Max BW_16wk (g) 2210.0
Male BW_16wk (g) 1210.0
Female BW_16wk (g) 956.0
Sexual Dimorphism (%) 26.6

2.3 Population Structure by Generation

tilili_data %>%
  group_by(Generation) %>%
  summarise(
    N = n(),
    N_BW = sum(!is.na(BW_16wk)),
    Mean_BW = round(mean(BW_16wk, na.rm = TRUE), 0),
    SD_BW = round(sd(BW_16wk, na.rm = TRUE), 0),
    N_Sires = n_distinct(Sire_ID[!is.na(Sire_ID)]),
    N_Dams = n_distinct(Dam_ID[!is.na(Dam_ID)]),
    .groups = "drop"
  ) %>%
  mutate(Generation = paste0("G", Generation)) %>%
  kable(col.names = c("Generation", "N Total", "N with BW", "Mean BW (g)", "SD (g)", "N Sires", "N Dams"),
        align = c("l", rep("r", 6))) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, background = "#319795", color = "white")
Generation N Total N with BW Mean BW (g) SD (g) N Sires N Dams
G3 722 458 1188 343 48 212
G4 4112 2537 1014 279 44 289
G5 1072 788 1198 295 16 87
tilili_data %>%
  filter(!is.na(BW_16wk)) %>%
  mutate(Generation = paste0("G", Generation)) %>%
  ggplot(aes(x = Generation, y = BW_16wk, fill = Generation)) +
  geom_boxplot(alpha = 0.8, outlier.shape = 21) +
  geom_jitter(alpha = 0.2, width = 0.2, size = 0.8) +
  scale_fill_manual(values = gen_colors) +
  labs(title = "Tilili: Body Weight at 16 Weeks by Generation",
       x = "Generation", y = "Body Weight (g)") +
  theme_tilili() + theme(legend.position = "none")

2.4 Variance Components and Heritability

ℹ️ Analysis Method

Genetic parameters were estimated using WOMBAT software with an animal model:

Model: y = Xb + Za + e

  • Fixed effects (b): Sex, Batch, Year, Month
  • Random effect (a): Additive genetic effect (animal)
  • Trait: Body weight at 16 weeks (BW_16wk)
if (!is.null(tilili_vc)) {
  tilili_vc %>%
    kable(col.names = c("Line", "N", "Va", "SE(Va)", "Ve", "SE(Ve)", "Vp", "h²", "SE(h²)"),
          align = c("l", rep("r", 8))) %>%
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
    row_spec(0, bold = TRUE, background = "#319795", color = "white")
} else {
  cat("Variance component results not available.")
}
Line N Va SE(Va) Ve SE(Ve) Vp SE(h²)
Tilili 3710 12914.1 2674.6 52893.5 2018.5 65807.5 0.196 0.037

2.5 EBV Summary Statistics

if (!is.null(tilili_ebv) && nrow(tilili_ebv) > 0) {
  data.frame(
    Metric = c("N Animals with EBV", "Mean EBV (g)", "SD EBV (g)", 
               "Min EBV (g)", "Max EBV (g)", "EBV Range (g)"),
    Value = c(nrow(tilili_ebv),
              round(mean(tilili_ebv$EBV, na.rm = TRUE), 1),
              round(sd(tilili_ebv$EBV, na.rm = TRUE), 1),
              round(min(tilili_ebv$EBV, na.rm = TRUE), 1),
              round(max(tilili_ebv$EBV, na.rm = TRUE), 1),
              round(max(tilili_ebv$EBV, na.rm = TRUE) - min(tilili_ebv$EBV, na.rm = TRUE), 1))
  ) %>%
    kable(align = c("l", "r")) %>%
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
    row_spec(0, bold = TRUE, background = "#319795", color = "white")
}
Metric Value
N Animals with EBV 3882.0
Mean EBV (g) 43.7
SD EBV (g) 74.8
Min EBV (g) -182.3
Max EBV (g) 312.6
EBV Range (g) 494.9
if (!is.null(tilili_ebv) && nrow(tilili_ebv) > 0) {
  ggplot(tilili_ebv, aes(x = EBV)) +
    geom_histogram(bins = 30, fill = "#319795", color = "white", alpha = 0.8) +
    geom_vline(xintercept = 0, linetype = "dashed", color = "black", linewidth = 1) +
    geom_vline(xintercept = mean(tilili_ebv$EBV, na.rm = TRUE), 
               linetype = "solid", color = "#EE7733", linewidth = 1) +
    labs(title = "Tilili: EBV Distribution for Body Weight at 16 Weeks",
         subtitle = "Black dashed = 0 | Orange solid = Mean EBV",
         x = "EBV (g)", y = "Count") +
    theme_tilili()
}

2.6 Top 10 Breeding Animals (by EBV)

if (!is.null(tilili_ebv) && nrow(tilili_ebv) > 0) {
  tilili_ebv %>%
    arrange(desc(EBV)) %>%
    head(10) %>%
    mutate(
      Rank = 1:10,
      EBV = round(EBV, 1),
      SE_EBV = ifelse(is.na(SE_EBV), "-", round(SE_EBV, 1)),
      Accuracy = ifelse(is.na(Accuracy), "-", round(Accuracy, 2)),
      Inbreeding_pct = ifelse(is.na(Inbreeding_pct), "-", round(Inbreeding_pct, 2)),
      Inbreeding_Category = as.character(Inbreeding_Category)
    ) %>%
    select(Rank, ID_original, EBV, SE_EBV, Accuracy, Inbreeding_pct, Inbreeding_Category) %>%
    kable(col.names = c("Rank", "Animal ID", "EBV (g)", "SE", "Accuracy", "F (%)", "Inbreeding Level"),
          align = c("c", "l", rep("r", 4), "l")) %>%
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
    row_spec(0, bold = TRUE, background = "#276749", color = "white") %>%
    row_spec(1, bold = TRUE, background = "#c6f6d5")
}
Rank Animal ID EBV (g) SE Accuracy F (%) Inbreeding Level
1 6233 312.6 85.3 0.74 25.76 Full-sib level (F ≥ 25%)
2 6235 295.0 85.3 0.74 25.76 Full-sib level (F ≥ 25%)
3 2481 273.2 85.3 0.74 25.76 Full-sib level (F ≥ 25%)
4 2485 267.3 85.3 0.74 25.76 Full-sib level (F ≥ 25%)
5 1941 265.6 85.3 0.74 25.76 Full-sib level (F ≥ 25%)
6 1370 257.4 85.3 0.74 25.76 Full-sib level (F ≥ 25%)
7 1371 255.7 85.3 0.74 25.76 Full-sib level (F ≥ 25%)
8 257 255.7 63.7 0.83 0.98 Below 2nd cousin (F < 3.125%)
9 1054 254.2 85.3 0.74 25.76 Full-sib level (F ≥ 25%)
10 1055 253.2 85.3 0.74 25.76 Full-sib level (F ≥ 25%)

3 Part 2: Inbreeding Analysis

🧬 How WOMBAT Computes Inbreeding Coefficients

WOMBAT calculates inbreeding coefficients (F) using the Meuwissen & Luo (1992) algorithm, which is a computationally efficient recursive method based on the pedigree’s additive genetic relationship matrix (A-matrix).

Inbreeding Coefficient Formula:

Fi = ½ × asd

Where:

  • Fi = Inbreeding coefficient of animal i
  • asd = Additive genetic relationship between the sire (s) and dam (d) of animal i

Interpretation:

  • If parents are unrelated: asd = 0, therefore F = 0%
  • If parents are half-siblings: asd = 0.25, therefore F = 12.5%
  • If parents are full-siblings: asd = 0.50, therefore F = 25%
  • If parents are parent-offspring: asd = 0.50, therefore F = 25%

Algorithm Details:

The Meuwissen & Luo algorithm processes the pedigree in chronological order (from oldest to youngest animals) and recursively computes:

  1. The diagonal elements of the A-matrix (which include inbreeding)
  2. The inbreeding coefficient for each animal based on the relationship between its parents

Reference: Meuwissen, T.H.E. & Luo, Z. (1992). Computing inbreeding coefficients in large populations. Genetics Selection Evolution, 24: 305-313.

3.1 Biologically Meaningful Inbreeding Categories

Rather than simply classifying animals as “inbred” vs “non-inbred” at any F > 0%, we use biologically meaningful thresholds based on equivalent relationship levels:

data.frame(
  Category = c("Non-inbred", "Below 2nd cousin", "2nd cousin level", "1st cousin level", "Half-sib level", "Full-sib level"),
  F_Range = c("F = 0%", "0% < F < 3.125%", "3.125% ≤ F < 6.25%", "6.25% ≤ F < 12.5%", "12.5% ≤ F < 25%", "F ≥ 25%"),
  Equivalent_Mating = c("Unrelated parents", "More distant than 2nd cousins", "Second cousin mating", "First cousin mating", "Half-sibling mating", "Full-sibling or parent-offspring"),
  Management = c("Ideal for breeding", "Suitable for breeding", "Acceptable", "Use with caution", "Limit use", "Avoid as parents"),
  Expected_Depression = c("0%", "< 1%", "1-2%", "2-4%", "4-8%", "> 8%")
) %>%
  kable(col.names = c("Category", "F Range", "Equivalent Mating", "Management", "Expected Inbreeding Depression"),
        align = c("l", "l", "l", "l", "c")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, background = "#319795", color = "white") %>%
  row_spec(4, background = "#fff3e0") %>%
  row_spec(5, background = "#ffe0b2") %>%
  row_spec(6, background = "#e1bee7")
Category F Range Equivalent Mating Management Expected Inbreeding Depression
Non-inbred F = 0% Unrelated parents Ideal for breeding 0%
Below 2nd cousin 0% < F < 3.125% More distant than 2nd cousins Suitable for breeding < 1%
2nd cousin level 3.125% ≤ F < 6.25% Second cousin mating Acceptable 1-2%
1st cousin level 6.25% ≤ F < 12.5% First cousin mating Use with caution 2-4%
Half-sib level 12.5% ≤ F < 25% Half-sibling mating Limit use 4-8%
Full-sib level F ≥ 25% Full-sibling or parent-offspring Avoid as parents > 8%

3.2 Population Inbreeding Distribution

if (!is.null(tilili_ebv) && nrow(tilili_ebv) > 0) {
  # Calculate summary by category
  inbreeding_summary <- tilili_ebv %>%
    filter(!is.na(Inbreeding_Category)) %>%
    count(Inbreeding_Category) %>%
    mutate(
      Percent = round(n / sum(n) * 100, 1),
      Cumulative_Pct = round(cumsum(n) / sum(n) * 100, 1)
    )
  
  # Display summary table
  inbreeding_summary %>%
    kable(col.names = c("Inbreeding Category", "N Animals", "Percent (%)", "Cumulative (%)"),
          align = c("l", rep("r", 3))) %>%
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
    row_spec(0, bold = TRUE, background = "#319795", color = "white") %>%
    row_spec(which(inbreeding_summary$Inbreeding_Category == "1st cousin level (F < 12.5%)"), background = "#fff3e0") %>%
    row_spec(which(inbreeding_summary$Inbreeding_Category == "Half-sib level (F < 25%)"), background = "#ffe0b2") %>%
    row_spec(which(inbreeding_summary$Inbreeding_Category == "Full-sib level (F ≥ 25%)"), background = "#e1bee7")
}
Inbreeding Category N Animals Percent (%) Cumulative (%)
Non-inbred (F = 0%) 408 10.5 10.5
Below 2nd cousin (F < 3.125%) 2233 57.5 68.0
2nd cousin level (F < 6.25%) 647 16.7 84.7
1st cousin level (F < 12.5%) 394 10.1 94.8
Half-sib level (F < 25%) 176 4.5 99.4
Full-sib level (F ≥ 25%) 24 0.6 100.0
if (!is.null(tilili_ebv) && nrow(tilili_ebv) > 0) {
  inbreeding_summary %>%
    ggplot(aes(x = Inbreeding_Category, y = n, fill = Inbreeding_Category)) +
    geom_bar(stat = "identity", alpha = 0.9) +
    geom_text(aes(label = paste0(n, "\n(", Percent, "%)")), 
              vjust = -0.3, size = 3.5, fontface = "bold") +
    scale_fill_manual(values = inbreeding_colors) +
    labs(title = "Tilili: Distribution of Animals by Inbreeding Category",
         subtitle = "Based on biologically meaningful thresholds (relationship levels)",
         x = "Inbreeding Category", y = "Number of Animals") +
    theme_tilili() +
    theme(legend.position = "none",
          axis.text.x = element_text(angle = 25, hjust = 1, size = 10)) +
    ylim(0, max(inbreeding_summary$n) * 1.15)
}

3.3 Inbreeding Coefficient Distribution

if (!is.null(tilili_ebv) && nrow(tilili_ebv) > 0) {
  # Filter to animals with F > 0 for better visualization
  inbred_animals <- tilili_ebv %>% filter(Inbreeding_pct > 0)
  
  if (nrow(inbred_animals) > 0) {
    ggplot(inbred_animals, aes(x = Inbreeding_pct, fill = Inbreeding_Category)) +
      geom_histogram(bins = 30, color = "white", alpha = 0.85) +
      geom_vline(xintercept = c(3.125, 6.25, 12.5, 25), 
                 linetype = "dashed", color = "gray40", linewidth = 0.7) +
      annotate("text", x = 3.125, y = Inf, label = "2nd Cousin", 
               vjust = 2, hjust = -0.1, size = 3, color = "gray30") +
      annotate("text", x = 6.25, y = Inf, label = "1st Cousin", 
               vjust = 2, hjust = -0.1, size = 3, color = "gray30") +
      annotate("text", x = 12.5, y = Inf, label = "Half-sib", 
               vjust = 2, hjust = -0.1, size = 3, color = "gray30") +
      annotate("text", x = 25, y = Inf, label = "Full-sib", 
               vjust = 2, hjust = -0.1, size = 3, color = "gray30") +
      scale_fill_manual(values = inbreeding_colors) +
      labs(title = "Tilili: Inbreeding Coefficient Distribution (F > 0% only)",
           subtitle = "Vertical lines indicate relationship thresholds",
           x = "Inbreeding Coefficient (%)", y = "Number of Animals",
           fill = "Category") +
      theme_tilili() +
      theme(legend.position = "right")
  } else {
    cat("No inbred animals (F > 0%) found in the population.")
  }
}

3.4 Detailed Inbreeding Statistics

if (!is.null(tilili_ebv) && nrow(tilili_ebv) > 0) {
  # Calculate detailed statistics
  F_stats <- tilili_ebv %>%
    filter(!is.na(Inbreeding_pct)) %>%
    summarise(
      N_Total = n(),
      N_Non_Inbred = sum(Inbreeding_pct == 0),
      Pct_Non_Inbred = round(sum(Inbreeding_pct == 0) / n() * 100, 1),
      N_Inbred = sum(Inbreeding_pct > 0),
      Pct_Inbred = round(sum(Inbreeding_pct > 0) / n() * 100, 1),
      Mean_F_All = round(mean(Inbreeding_pct), 3),
      Mean_F_Inbred = round(mean(Inbreeding_pct[Inbreeding_pct > 0]), 3),
      SD_F_Inbred = round(sd(Inbreeding_pct[Inbreeding_pct > 0]), 3),
      Max_F = round(max(Inbreeding_pct), 3),
      N_Concern = sum(Inbreeding_pct >= 6.25)
    )
  
  data.frame(
    Metric = c("Total animals evaluated",
               "Non-inbred animals (F = 0%)",
               "Percentage non-inbred",
               "Inbred animals (F > 0%)",
               "Percentage inbred",
               "Mean F (all animals)",
               "Mean F (inbred animals only)",
               "SD of F (inbred animals)",
               "Maximum F observed",
               "Animals requiring attention (F ≥ 6.25%)"),
    Value = c(F_stats$N_Total,
              F_stats$N_Non_Inbred,
              paste0(F_stats$Pct_Non_Inbred, "%"),
              F_stats$N_Inbred,
              paste0(F_stats$Pct_Inbred, "%"),
              paste0(F_stats$Mean_F_All, "%"),
              paste0(F_stats$Mean_F_Inbred, "%"),
              paste0(F_stats$SD_F_Inbred, "%"),
              paste0(F_stats$Max_F, "%"),
              F_stats$N_Concern)
  ) %>%
    kable(align = c("l", "r")) %>%
    kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
    row_spec(0, bold = TRUE, background = "#319795", color = "white") %>%
    row_spec(10, bold = TRUE, background = "#fffaf0")
}
Metric Value
Total animals evaluated 3882
Non-inbred animals (F = 0%) 408
Percentage non-inbred 10.5%
Inbred animals (F > 0%) 3474
Percentage inbred 89.5%
Mean F (all animals) 3.034%
Mean F (inbred animals only) 3.39%
SD of F (inbred animals) 3.921%
Maximum F observed 25.757%
Animals requiring attention (F ≥ 6.25%) 594

3.5 Animals with Elevated Inbreeding (F ≥ 6.25%)

if (!is.null(tilili_ebv) && nrow(tilili_ebv) > 0) {
  high_inbreeding <- tilili_ebv %>%
    filter(Inbreeding_pct >= 6.25) %>%
    arrange(desc(Inbreeding_pct)) %>%
    mutate(
      EBV = round(EBV, 1),
      Inbreeding_pct = round(Inbreeding_pct, 2)
    ) %>%
    select(ID_original, EBV, Inbreeding_pct, Inbreeding_Category)
  
  if (nrow(high_inbreeding) > 0) {
    high_inbreeding %>%
      head(20) %>%
      kable(col.names = c("Animal ID", "EBV (g)", "Inbreeding (%)", "Relationship Level"),
            align = c("l", "r", "r", "l")) %>%
      kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
      row_spec(0, bold = TRUE, background = "#805ad5", color = "white") %>%
      row_spec(which(high_inbreeding$Inbreeding_Category[1:min(20, nrow(high_inbreeding))] == "Full-sib level (F ≥ 25%)"), 
               background = "#e1bee7") %>%
      row_spec(which(high_inbreeding$Inbreeding_Category[1:min(20, nrow(high_inbreeding))] == "Half-sib level (F < 25%)"), 
               background = "#ffe0b2")
  } else {
    cat("No animals with F ≥ 6.25% (first cousin level or higher) found.")
  }
}
Animal ID EBV (g) Inbreeding (%) Relationship Level
1056 189.8 25.76 Full-sib level (F ≥ 25%)
6235 295.0 25.76 Full-sib level (F ≥ 25%)
6233 312.6 25.76 Full-sib level (F ≥ 25%)
6232 243.3 25.76 Full-sib level (F ≥ 25%)
6189 229.6 25.76 Full-sib level (F ≥ 25%)
6014 186.1 25.76 Full-sib level (F ≥ 25%)
4642 170.7 25.76 Full-sib level (F ≥ 25%)
4422 252.4 25.76 Full-sib level (F ≥ 25%)
4421 223.9 25.76 Full-sib level (F ≥ 25%)
2485 267.3 25.76 Full-sib level (F ≥ 25%)
2484 187.8 25.76 Full-sib level (F ≥ 25%)
2483 217.3 25.76 Full-sib level (F ≥ 25%)
2482 239.0 25.76 Full-sib level (F ≥ 25%)
2481 273.2 25.76 Full-sib level (F ≥ 25%)
1942 241.4 25.76 Full-sib level (F ≥ 25%)
1941 265.6 25.76 Full-sib level (F ≥ 25%)
1371 255.7 25.76 Full-sib level (F ≥ 25%)
1370 257.4 25.76 Full-sib level (F ≥ 25%)
1362 222.5 25.76 Full-sib level (F ≥ 25%)
1055 253.2 25.76 Full-sib level (F ≥ 25%)

📋 Inbreeding Management Guidelines

Based on the inbreeding analysis:

  1. Prioritize non-inbred or distantly related animals (F < 3.125%) for breeding decisions
  2. Limit use of half-sib level animals (F ≥ 12.5%) as breeding candidates
  3. Avoid using full-sib level animals (F ≥ 25%) as parents to prevent inbreeding depression
  4. Monitor effective population size (Ne) to prevent future inbreeding accumulation
  5. Implement Optimal Contribution Selection (OCS) to balance genetic gain and genetic diversity

4 Part 3: Egg Weight Analysis

# Load and process egg weight data
df_egg_weight_raw <- read_csv(egg_weight_file, show_col_types = FALSE) %>%
  clean_names()

week_cols_ew <- names(df_egg_weight_raw)[grepl("^week_", names(df_egg_weight_raw))]

df_egg_weight_long <- df_egg_weight_raw %>%
  select(bird_id = chicken_id, sire_id, dam_id, generation = generation3, batch, pen, all_of(week_cols_ew)) %>%
  pivot_longer(cols = all_of(week_cols_ew), names_to = "week", values_to = "egg_weight") %>%
  mutate(
    week = as.numeric(str_extract(week, "\\d+")),
    egg_weight = na_if(egg_weight, 0),
    egg_weight = if_else(!is.na(egg_weight) & egg_weight > 100, NA_real_, egg_weight),
    generation = factor(generation, levels = sort(unique(generation)), labels = paste0("G", sort(unique(generation)))),
    batch = factor(batch)
  )

n_birds_ew <- n_distinct(df_egg_weight_long$bird_id)
n_records_ew <- sum(!is.na(df_egg_weight_long$egg_weight))
mean_ew <- round(mean(df_egg_weight_long$egg_weight, na.rm = TRUE), 1)
sd_ew <- round(sd(df_egg_weight_long$egg_weight, na.rm = TRUE), 1)
n_gen_ew <- n_distinct(df_egg_weight_long$generation)
170
Birds
4,389
Valid Records
53.1g
Mean Weight
2
Generations
31-61
Week Range

4.1 Weekly Means by Generation

weekly_gen_ew <- df_egg_weight_long %>%
  group_by(generation, week) %>%
  summarise(n = sum(!is.na(egg_weight)), mean = mean(egg_weight, na.rm = TRUE),
            sd = sd(egg_weight, na.rm = TRUE), se = sd / sqrt(n), .groups = "drop") %>%
  filter(n > 0)
weekly_gen_ew %>%
  group_by(generation) %>%
  summarise(weeks_with_data = n(), total_observations = sum(n),
            mean_egg_weight = round(mean(mean, na.rm = TRUE), 1), .groups = "drop") %>%
  kable(col.names = c("Generation", "Weeks with Data", "Total Observations", "Overall Mean (g)"),
        align = c("l", rep("r", 3))) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  row_spec(0, bold = TRUE, background = "#319795", color = "white")
Generation Weeks with Data Total Observations Overall Mean (g)
G3 31 2069 52.4
G4 31 2320 53.8
ggplot(weekly_gen_ew, aes(x = week, y = mean, color = generation, group = generation)) +
  geom_line(linewidth = 1.0) +
  geom_point(size = 2.0) +
  geom_errorbar(aes(ymin = mean - se, ymax = mean + se), width = 0.3, alpha = 0.5) +
  scale_color_manual(values = gen_colors) +
  scale_x_continuous(breaks = seq(31, 61, by = 2)) +
  labs(title = "Tilili: Mean Egg Weight by Week of Age",
       subtitle = "Comparison by Generation | Error bars: ± 1 SE",
       x = "Week of Age", y = "Mean Egg Weight (g)", color = "Generation",
       caption = "Note: Zeros treated as missing | Outliers (>100g) removed") +
  theme_tilili()

4.2 Overall Generation Summary - Egg Weight

df_egg_weight_long %>%
  group_by(generation) %>%
  summarise(n_birds = n_distinct(bird_id), n_records = sum(!is.na(egg_weight)),
            mean_egg_weight = round(mean(egg_weight, na.rm = TRUE), 1),
            sd_egg_weight = round(sd(egg_weight, na.rm = TRUE), 1),
            min_egg_weight = round(min(egg_weight, na.rm = TRUE), 1),
            max_egg_weight = round(max(egg_weight, na.rm = TRUE), 1), .groups = "drop") %>%
  kable(col.names = c("Generation", "N Birds", "N Records", "Mean (g)", "SD (g)", "Min (g)", "Max (g)"),
        align = c("l", rep("r", 6))) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, background = "#319795", color = "white")
Generation N Birds N Records Mean (g) SD (g) Min (g) Max (g)
G3 85 2069 52.3 4.6 39 69
G4 85 2320 53.8 4.6 35 74

4.3 Weekly Means by Batch

weekly_batch_ew <- df_egg_weight_long %>%
  group_by(batch, week) %>%
  summarise(n = sum(!is.na(egg_weight)), mean = mean(egg_weight, na.rm = TRUE), .groups = "drop") %>%
  filter(n > 0)

ggplot(weekly_batch_ew, aes(x = week, y = mean, color = batch, group = batch)) +
  geom_line(linewidth = 0.8) +
  geom_point(size = 1.5, alpha = 0.7) +
  scale_color_manual(values = batch_colors) +
  scale_x_continuous(breaks = seq(31, 61, by = 5)) +
  labs(title = "Tilili: Mean Egg Weight by Week of Age",
       subtitle = "Comparison by Batch",
       x = "Week of Age", y = "Mean Egg Weight (g)", color = "Batch") +
  theme_tilili()


5 Part 4: Egg Number Analysis

# Load and process egg number data
df_egg_number_raw <- read_csv(egg_number_file, show_col_types = FALSE) %>%
  clean_names()

week_cols_en <- names(df_egg_number_raw)[grepl("^week_", names(df_egg_number_raw))]

df_egg_number_long <- df_egg_number_raw %>%
  select(bird_id = birds_id, sire_id, dam_id, generation, batch, pen, all_of(week_cols_en)) %>%
  pivot_longer(cols = all_of(week_cols_en), names_to = "week", values_to = "egg_number") %>%
  mutate(
    week = as.numeric(str_extract(week, "\\d+")),
    egg_number = na_if(egg_number, 0),
    generation = factor(generation, levels = sort(unique(generation)), labels = paste0("G", sort(unique(generation)))),
    batch = factor(batch)
  )

n_birds_en <- n_distinct(df_egg_number_long$bird_id)
n_records_en <- sum(!is.na(df_egg_number_long$egg_number))
mean_en <- round(mean(df_egg_number_long$egg_number, na.rm = TRUE), 2)
sd_en <- round(sd(df_egg_number_long$egg_number, na.rm = TRUE), 2)
n_gen_en <- n_distinct(df_egg_number_long$generation)
180
Birds
4,892
Valid Records
4.33
Mean Eggs/Week
2
Generations
31-61
Week Range

5.1 Weekly Means by Generation

weekly_gen_en <- df_egg_number_long %>%
  group_by(generation, week) %>%
  summarise(n = sum(!is.na(egg_number)), mean = mean(egg_number, na.rm = TRUE),
            sd = sd(egg_number, na.rm = TRUE), se = sd / sqrt(n), .groups = "drop") %>%
  filter(n > 0)
weekly_gen_en %>%
  group_by(generation) %>%
  summarise(weeks_with_data = n(), total_observations = sum(n),
            mean_egg_number = round(mean(mean, na.rm = TRUE), 2), .groups = "drop") %>%
  kable(col.names = c("Generation", "Weeks with Data", "Total Observations", "Overall Mean"),
        align = c("l", rep("r", 3))) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
  row_spec(0, bold = TRUE, background = "#319795", color = "white")
Generation Weeks with Data Total Observations Overall Mean
G3 31 2228 4.34
G4 31 2664 4.27
ggplot(weekly_gen_en, aes(x = week, y = mean, color = generation, group = generation)) +
  geom_line(linewidth = 1.0) +
  geom_point(size = 2.0) +
  geom_errorbar(aes(ymin = mean - se, ymax = mean + se), width = 0.3, alpha = 0.5) +
  scale_color_manual(values = gen_colors) +
  scale_x_continuous(breaks = seq(31, 61, by = 2)) +
  labs(title = "Tilili: Mean Egg Number by Week of Age",
       subtitle = "Comparison by Generation | Error bars: ± 1 SE",
       x = "Week of Age", y = "Mean Egg Number (eggs/week)", color = "Generation",
       caption = "Note: Zeros treated as missing") +
  theme_tilili()

5.2 Overall Generation Summary - Egg Number

df_egg_number_long %>%
  group_by(generation) %>%
  summarise(n_birds = n_distinct(bird_id), n_records = sum(!is.na(egg_number)),
            mean_egg_number = round(mean(egg_number, na.rm = TRUE), 2),
            sd_egg_number = round(sd(egg_number, na.rm = TRUE), 2),
            min_egg_number = round(min(egg_number, na.rm = TRUE), 2),
            max_egg_number = round(max(egg_number, na.rm = TRUE), 2), .groups = "drop") %>%
  kable(col.names = c("Generation", "N Birds", "N Records", "Mean", "SD", "Min", "Max"),
        align = c("l", rep("r", 6))) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, background = "#319795", color = "white")
Generation N Birds N Records Mean SD Min Max
G3 90 2228 4.37 1.67 1.0 7
G4 90 2664 4.29 1.67 0.2 7

5.3 Weekly Means by Batch

weekly_batch_en <- df_egg_number_long %>%
  group_by(batch, week) %>%
  summarise(n = sum(!is.na(egg_number)), mean = mean(egg_number, na.rm = TRUE), .groups = "drop") %>%
  filter(n > 0)

ggplot(weekly_batch_en, aes(x = week, y = mean, color = batch, group = batch)) +
  geom_line(linewidth = 0.8) +
  geom_point(size = 1.5, alpha = 0.7) +
  scale_color_manual(values = batch_colors) +
  scale_x_continuous(breaks = seq(31, 61, by = 5)) +
  labs(title = "Tilili: Mean Egg Number by Week of Age",
       subtitle = "Comparison by Batch",
       x = "Week of Age", y = "Mean Egg Number (eggs/week)", color = "Batch") +
  theme_tilili()


6 Part 5: Combined Trait Summary

6.2 Overall Trait Summary Table

data.frame(
  Trait = c("Body Weight (16 wk)", "Egg Weight (g)", "Egg Number"),
  N_Birds = c(n_with_bw, n_birds_ew, n_birds_en),
  N_Records = c(n_with_bw, n_records_ew, n_records_en),
  Overall_Mean = c(mean_bw, mean_ew, mean_en),
  Overall_SD = c(sd_bw, sd_ew, sd_en),
  Heritability = c(ifelse(!is.na(h2), h2, "N/A"), "TBD", "TBD")
) %>%
  kable(col.names = c("Trait", "N Birds", "N Records", "Mean", "SD", "h²"),
        align = c("l", rep("r", 5))) %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
  row_spec(0, bold = TRUE, background = "#319795", color = "white")
Trait N Birds N Records Mean SD
Body Weight (16 wk) 3783 3783 1073.00 303.00 0.196
Egg Weight (g) 170 4389 53.10 4.60 TBD
Egg Number 180 4892 4.33 1.67 TBD

7 Conclusions and Recommendations

✅ Key Findings

Body Weight (16 weeks):

  • Mean BW_16wk: 1073g (SD: 303g, CV: 28.2%)
  • Heritability: 0.196 ± 0.037 (low to moderate)
  • Sexual dimorphism: 26.6% (males heavier than females)
  • Generations evaluated: G3, G4, G5 with 3,783 phenotyped birds
  • EBV range: 494.9g indicating genetic variation for selection

Inbreeding Status:

  • Non-inbred animals (F = 0%): 408 birds
  • Below 2nd cousin level (F < 3.125%): 2233 birds
  • Animals at 1st cousin level or higher (F ≥ 6.25%): 594 birds
  • Mean inbreeding (all animals): 3.03%

Egg Weight:

  • Mean: 53.1g (SD: 4.6g) across laying period (weeks 31-61)
  • Generations: G3 and G4 with 170 birds and 4,389 valid records
  • G4 shows slightly higher mean egg weight than G3, suggesting positive genetic trend

Egg Number:

  • Mean: 4.33 eggs/week (SD: 1.67)
  • Generations: G3 and G4 with 180 birds and 4,892 valid records
  • Both generations show similar production levels across the 31-week recording period

⚠️ Data Notes

  1. Zero values: Treated as missing data per specification (birds not laying, dead, or not recorded)
  2. Outliers in egg weight: Values >100g removed as likely data entry errors
  3. Body weight coverage: G4 has largest sample size (4,112 birds) providing robust estimates
  4. Inbreeding computation: WOMBAT uses Meuwissen & Luo (1992) algorithm based on pedigree A-matrix
  5. Inbreeding categories: Biologically meaningful thresholds based on relationship levels (e.g., half-sib level = 12.5%, full-sib level = 25%)

📋 Recommendations

  1. Estimate genetic parameters for egg traits - Run WOMBAT repeatability model analysis for egg weight and egg number to enable genetic evaluation

  2. Develop selection index - Combine body weight (h² = 0.196) with egg production traits for balanced genetic improvement

  3. Manage inbreeding actively:

    • Prioritize animals with F < 3.125% for breeding
    • Limit use of half-sib level animals (F ≥ 12.5%) as parents
    • Avoid breeding full-sib level animals (F ≥ 25%)
    • Implement Optimal Contribution Selection (OCS) to balance genetic gain and diversity
  4. Continue phenotype recording - Expand egg production recording to G5 birds as they reach laying age

  5. Use top EBV animals judiciously - Top breeding candidates identified (EBV up to 312.6g) but verify inbreeding category before use

  6. Cohort-based evaluations - Conduct within-batch genetic evaluations for timely selection decisions