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")This report presents a comprehensive genetic evaluation of the Tilili local Ethiopian chicken ecotype, including:
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 |
# 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
}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 |
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")Genetic parameters were estimated using WOMBAT software with an animal model:
Model: y = Xb + Za + e
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 | h² | SE(h²) |
|---|---|---|---|---|---|---|---|---|
| Tilili | 3710 | 12914.1 | 2674.6 | 52893.5 | 2018.5 | 65807.5 | 0.196 | 0.037 |
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()
}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%) |
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:
Interpretation:
Algorithm Details:
The Meuwissen & Luo algorithm processes the pedigree in chronological order (from oldest to youngest animals) and recursively computes:
Reference: Meuwissen, T.H.E. & Luo, Z. (1992). Computing inbreeding coefficients in large populations. Genetics Selection Evolution, 24: 305-313.
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% |
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)
}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.")
}
}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 |
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%) |
Based on the inbreeding 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)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()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 |
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()# 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)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()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 |
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()combined_weekly <- bind_rows(
weekly_gen_ew %>% select(generation, week, mean, se) %>% mutate(trait = "Egg Weight (g)"),
weekly_gen_en %>% select(generation, week, mean, se) %>% mutate(trait = "Egg Number")
)
ggplot(combined_weekly, aes(x = week, y = mean, color = generation, group = generation)) +
geom_line(linewidth = 1.0) +
geom_point(size = 1.5) +
geom_ribbon(aes(ymin = mean - se, ymax = mean + se, fill = generation), alpha = 0.2, color = NA) +
facet_wrap(~ trait, scales = "free_y", ncol = 1) +
scale_color_manual(values = gen_colors) +
scale_fill_manual(values = gen_colors) +
scale_x_continuous(breaks = seq(31, 61, by = 5)) +
labs(title = "Tilili: Egg Production Traits by Week of Age",
subtitle = "Generation Comparison | Shaded area: ± 1 SE",
x = "Week of Age", y = "Mean Value", color = "Generation", fill = "Generation") +
theme_tilili() +
theme(strip.text = element_text(face = "bold", size = 12))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 | h² |
|---|---|---|---|---|---|
| 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 |
Body Weight (16 weeks):
Inbreeding Status:
Egg Weight:
Egg Number:
Estimate genetic parameters for egg traits - Run WOMBAT repeatability model analysis for egg weight and egg number to enable genetic evaluation
Develop selection index - Combine body weight (h² = 0.196) with egg production traits for balanced genetic improvement
Manage inbreeding actively:
Continue phenotype recording - Expand egg production recording to G5 birds as they reach laying age
Use top EBV animals judiciously - Top breeding candidates identified (EBV up to 312.6g) but verify inbreeding category before use
Cohort-based evaluations - Conduct within-batch genetic evaluations for timely selection decisions