This tutorial demonstrates a typical pedigree workflow using the BIGpopA R package, the analytical engine behind Familia, Breeding Insight’s Shiny application for pedigree validation and ancestry assessment. We run three functions in sequence:
It also estimates genome-wide breed/line composition for diploid and polyploid populations with allele_freq_poly() and solve_composition_poly(), shown at the end.
The methods are species-agnostic and apply to a wide range of plant and animal breeding programs.
BIGpopA uses a small toy dataset that exercises every result category. Each function accepts a file path or a data.frame / data.table; to keep this vignette self-contained we build the objects inline: a pedigree, a SNP genotype matrix coded 0/1/2, and candidate parent / progeny lists.
library(BIGpopA)
# SNP genotypes coded 0/1/2 (NA = missing); C_low is mostly missing
genotypes = data.frame(
id = c("M1","F1","M2","F2","C1","C2","C3","C_bad","C_low"),
snp01 = c( 0, 0, 2, 2, 0, 2, 1, 0, NA),
snp02 = c( 0, 0, 2, 0, 0, 1, 0, 0, NA),
snp03 = c( 0, 2, 0, 2, 1, 1, 1, 1, NA),
snp04 = c( 0, 2, 0, 0, 1, 0, 0, 1, NA),
snp05 = c( 2, 0, 1, 1, 1, 1, 1, 1, NA),
snp06 = c( 2, 0, 1, 0, 1, 1, 1, 1, NA),
snp07 = c( 2, 2, 0, 1, 2, 0, 2, 2, NA),
snp08 = c( 2, 2, 2, 2, 2, 2, 2, 2, NA),
snp09 = c( 0, 1, 0, 2, 1, 1, 1, 1, 0),
snp10 = c( 0, 1, 2, 0, 0, 1, 0, 0, 0),
snp11 = c( 2, 1, 1, 2, 1, 2, 2, 1, 2),
snp12 = c( 2, 1, 0, 1, 2, 1, 1, 2, 2),
stringsAsFactors = FALSE
)
# pedigree with founders coded 0, a mis-assigned trio (C_bad),
# and a duplicated row (C_missing)
pedigree = data.frame(
id = c("M1","F1","M2","F2","C1","C2","C3","C_bad","C_low","C_missing","C_missing"),
male_parent = c("0", "0", "0", "0", "M1","M2","M1","M2", "M1", "M1", "M1"),
female_parent = c("0", "0", "0", "0", "F1","F2","F2","F1", "F1", "F1", "F1"),
stringsAsFactors = FALSE
)
# candidate parents (with sex) and progeny to assign
parents = data.frame(
id = c("M1","M2","F1","F2"),
sex = c("M", "M", "F", "F"),
stringsAsFactors = FALSE
)
progeny = data.frame(
id = c("C1","C2","C3","C_bad","C_low","C_missing"),
stringsAsFactors = FALSE
)
pedigree## id male_parent female_parent
## 1 M1 0 0
## 2 F1 0 0
## 3 M2 0 0
## 4 F2 0 0
## 5 C1 M1 F1
## 6 C2 M2 F2
## 7 C3 M1 F2
## 8 C_bad M2 F1
## 9 C_low M1 F1
## 10 C_missing M1 F1
## 11 C_missing M1 F1
## id snp01 snp02 snp03 snp04 snp05 snp06 snp07 snp08 snp09 snp10 snp11 snp12
## 1 M1 0 0 0 0 2 2 2 2 0 0 2 2
## 2 F1 0 0 2 2 0 0 2 2 1 1 1 1
## 3 M2 2 2 0 0 1 1 0 2 0 2 1 0
## 4 F2 2 0 2 0 1 0 1 2 2 0 2 1
## 5 C1 0 0 1 1 1 1 2 2 1 0 1 2
## 6 C2 2 1 1 0 1 1 0 2 1 1 2 1
## 7 C3 1 0 1 0 1 1 2 2 1 0 2 1
## 8 C_bad 0 0 1 1 1 1 2 2 1 0 1 2
## 9 C_low NA NA NA NA NA NA NA NA 0 0 2 2
The pedigree has four founders (M1, F1, M2, F2, both parents coded 0) and several crosses, including a deliberately duplicated row (C_missing), a misassigned trio (C_bad), and a sample with mostly missing genotypes (C_low).
check_ped() operates on the pedigree columns id, male_parent, female_parent. Exact duplicates and missing parents are always corrected; conflicting trios and inconsistent sex roles are corrected by default. Cycles are reported only and must be resolved manually. Set verbose = TRUE to print a full report to the console.
#check_ped
clean_ped_results = check_ped(pedigree, verbose = FALSE)
# the corrected, analysis-ready pedigree
clean_ped = clean_ped_results$corrected_pedigree
clean_ped## id male_parent female_parent
## 1 M1 0 0
## 2 F1 0 0
## 3 M2 0 0
## 4 F2 0 0
## 5 C1 M1 F1
## 6 C2 M2 F2
## 7 C3 M1 F2
## 8 C_bad M2 F1
## 9 C_low M1 F1
## 10 C_missing M1 F1
The returned list also exposes per-issue tables for review, e.g. clean_ped_results\(exact_duplicates (here, the repeated C_missing row) and clean_ped_results\)missing_parents.
validate_pedigree() compares each trio against the genotype matrix and computes a Mendelian error rate. Trios are classified as pass, fail, low_markers, no_genotype_data, founders, or missing_parents. To fill in missing or removed parents, run find_parentage() with method = “fill_pedigree”. With plot_results = TRUE the function draws a histogram of trio error percentages.
#validate_ped
ped_validate_results = validate_pedigree(clean_ped, genotypes,
verbose = FALSE, plot_results = TRUE)## Index: <status>
## id orig_male_parent orig_female_parent trio_mendelian_error_pct
## <char> <char> <char> <num>
## 1: M1 0 0 NA
## 2: F1 0 0 NA
## 3: M2 0 0 NA
## 4: F2 0 0 NA
## 5: C1 M1 F1 0.00
## 6: C2 M2 F2 0.00
## 7: C3 M1 F2 0.00
## 8: C_bad M2 F1 41.67
## 9: C_low M1 F1 0.00
## 10: C_missing M1 F1 NA
## trio_markers_tested status recommended_correction
## <int> <char> <char>
## 1: 0 missing_both_parents none
## 2: 0 missing_both_parents none
## 3: 0 missing_both_parents none
## 4: 0 missing_both_parents none
## 5: 12 pass none
## 6: 12 pass none
## 7: 12 pass none
## 8: 12 fail remove_male_parent
## 9: 4 low_markers low_markers_remove_female_parent
## 10: 0 no_genotype_data none
## male_parent_hom_error_pct female_parent_hom_error_pct
## <num> <num>
## 1: NA NA
## 2: NA NA
## 3: NA NA
## 4: NA NA
## 5: NA NA
## 6: NA NA
## 7: NA NA
## 8: 83.33 0
## 9: 0.00 NA
## 10: NA NA
Reading the status column: C1, C2, C3 pass; C_bad fails (its recorded male parent shows a high homozygous-mismatch rate, so the recommended correction is to remove it); C_low is flagged low_markers because too few markers were genotyped; and C_missing has no_genotype_data. Key tuning arguments are trio_error_threshold (default 5.0), min_markers (default 10), and single_parent_error_threshold (default 2.0).
The result also contains marker_summary, a per-marker table with the number of trios tested, the number with a Mendelian mismatch, and the mismatch percentage, sorted worst first. It covers every trio with both parents known and genotyped, and helps find markers (for example paralogous or miscalled SNPs) that drive many failures.
## marker trios_tested trios_mismatch mismatch_pct
## <char> <int> <int> <num>
## 1: snp01 4 1 25
## 2: snp02 4 1 25
## 3: snp07 4 1 25
## 4: snp10 5 1 20
## 5: snp12 5 1 20
## 6: snp03 4 0 0
When parentage is unknown, find_parentage() searches a pool of candidate parents for the best assignment to each progeny. The method argument selects the search: best_pair (default), best_male_parent, best_female_parent, or best_match.
#find_parentage
find_parentage_results = find_parentage(genotypes, parents, progeny,
method = "best_pair",
verbose = FALSE, plot_results = TRUE)## Index: <status>
## id male_parent female_parent mendelian_error_pct markers_tested
## <char> <char> <char> <char> <int>
## 1: C1 M1 F1 0.00 12
## 2: C2 M2 F2 0.00 12
## 3: C3 M1 F2 0.00 12
## 4: C_bad M1 F1 0.00 12
## 5: C_low M1 F1 0.00 4
## status tied_candidates
## <char> <int>
## 1: pass 1
## 2: pass 1
## 3: pass 1
## 4: pass 1
## 5: low_markers 1
Each progeny is assigned the pair minimizing Mendelian error and labeled pass, high_error, or low_markers. Notice that C_bad, which failed validation against its recorded parents, is here correctly recovered to its true pair (M1/F1) at 0% error, while C_low remains low_markers. Useful arguments include error_threshold, min_markers, show_ties, allow_parent_selfing, and exclude_self_match.
The tied_candidates column gives the number of candidates (pairs for best_pair) that share each progeny’s lowest error; values above 1 mean the best assignment is ambiguous. Set top_n to report the best top_n candidates per progeny; rank 1 stays in the base columns and ranks 2 to top_n are added as suffix columns (for example male_parent_2, female_parent_2, mendelian_error_pct_2).
#top_n
find_parentage(genotypes, parents, progeny, method = "best_pair", top_n = 3,
verbose = FALSE, plot_results = FALSE)$full_results## Index: <status>
## id male_parent female_parent mendelian_error_pct markers_tested
## <char> <char> <char> <char> <int>
## 1: C1 M1 F1 0.00 12
## 2: C2 M2 F2 0.00 12
## 3: C3 M1 F2 0.00 12
## 4: C_bad M1 F1 0.00 12
## 5: C_low M1 F1 0.00 4
## status tied_candidates male_parent_2 female_parent_2
## <char> <int> <char> <char>
## 1: pass 1 M1 F2
## 2: pass 1 M2 F1
## 3: pass 1 M1 F1
## 4: pass 1 M1 F2
## 5: low_markers 1 M1 F2
## mendelian_error_pct_2 markers_tested_2 male_parent_3 female_parent_3
## <num> <num> <char> <char>
## 1: 25.00 12 M2 F1
## 2: 25.00 12 M1 F1
## 3: 16.67 12 M2 F1
## 4: 25.00 12 M2 F1
## 5: 25.00 4 M2 F1
## mendelian_error_pct_3 markers_tested_3
## <num> <num>
## 1: 41.67 12
## 2: 33.33 12
## 3: 33.33 12
## 4: 41.67 12
## 5: 50.00 4
With method = “fill_pedigree”, progeny is a pedigree with id, male_parent and female_parent columns (for example validate_pedigree()$corrected_pedigree). Parents coded 0, NA or empty are unknown. A known male parent searches only females, a known female parent only males, and rows with both unknown get the full pair search; rows with both parents known are skipped. The parents file (with sex M, F or A) is still required. The founders_file argument skips founders. The output adds search_mode and known_parent_error_pct.
#fill_pedigree
ped_to_fill = data.frame(id = c("C1", "C2"),
male_parent = c("M1", "0"),
female_parent = c("0", "0"))
find_parentage(genotypes, parents, ped_to_fill, method = "fill_pedigree",
verbose = FALSE, plot_results = FALSE)$full_results## Index: <status>
## id male_parent female_parent mendelian_error_pct markers_tested status
## <char> <char> <char> <char> <int> <char>
## 1: C1 M1 F1 0.00 12 pass
## 2: C2 M2 F2 0.00 12 pass
## tied_candidates search_mode known_parent_error_pct
## <int> <char> <num>
## 1: 1 known_male 0
## 2: 1 pair NA
Beyond pedigree QC, BIGpopA estimates genome-wide breed or line composition for diploid and polyploid populations using a breedTools-style quadratic programming approach (Funkhouser et al. 2017). This example mirrors a blueberry (autotetraploid, ploidy = 4) analysis that assigns validation samples to two cultivar reference panels, Jewel and Draper.
The workflow has two steps: compute reference-panel allele frequencies with allele_freq_poly(), then estimate each validation sample’s composition with solve_composition_poly().
library(dplyr)
# reference genotypes: individuals x SNPs, tetraploid dosage of allele B (0..4)
reference = data.frame(
S1 = c(0, 1, 0, 4, 3, 4),
S2 = c(1, 0, 0, 3, 4, 4),
S3 = c(0, 1, 1, 4, 3, 3),
S4 = c(1, 0, 1, 3, 4, 3),
S5 = c(0, 1, 0, 4, 3, 4),
S6 = c(1, 0, 1, 3, 4, 3),
row.names = c("Jewel1","Jewel2","Jewel3","Draper1","Draper2","Draper3")
)
# reference panel membership (which individuals define each cultivar)
ref_ids = list(
Jewel = c("Jewel1","Jewel2","Jewel3"),
Draper = c("Draper1","Draper2","Draper3")
)
# allele frequencies of the reference panels (SNPs x panels)
freq = allele_freq_poly(reference, ref_ids, ploidy = 4)
freq## Jewel Draper
## S1 0.08333333 0.9166667
## S2 0.08333333 0.9166667
## S3 0.16666667 0.8333333
## S4 0.16666667 0.8333333
## S5 0.08333333 0.9166667
## S6 0.16666667 0.8333333
# validation samples to assign: samples x SNPs (same SNPs as the reference)
validation = data.frame(
S1 = c(0, 4, 2),
S2 = c(1, 3, 2),
S3 = c(0, 4, 2),
S4 = c(1, 3, 2),
S5 = c(0, 4, 2),
S6 = c(1, 3, 2),
row.names = c("Sample1","Sample2","Sample3")
)
# breed/line composition (panel columns plus an R2 model-fit column)
prediction = as.data.frame(solve_composition_poly(validation, freq, ploidy = 4))
prediction## Jewel Draper R2
## Sample1 1.000000e+00 2.408405e-16 0.1111111
## Sample2 4.834614e-16 1.000000e+00 0.1111111
## Sample3 5.000000e-01 5.000000e-01 NA
It is important to note that while the model produces an R2 value, it is expected to be low since an allele dosage (discrete number) is being regressed on an allele frequency (continuous value).
assign_composition_line() turns the result into the
table used by Familia: the estimated percentage of each panel column and
a Predicted line. A sample is assigned to its
highest-proportion line only if that proportion is at least
assign_threshold (default 50); otherwise it is labelled
Undetermined. Use assign_threshold = 0 to
always assign the highest line.
pred_results = assign_composition_line(solve_composition_poly(validation, freq, ploidy = 4))
pred_results## ID Jewel Draper Predicted line
## 1 Sample1 100 0 Jewel
## 2 Sample2 0 100 Draper
## 3 Sample3 50 50 Jewel
Sample1 is assigned Jewel and Sample2 Draper (each essentially 100%),
while the admixed Sample3 splits roughly evenly between them. For
diploid data set ploidy = 2. Save the table with
write.table(pred_results, "composition.txt", sep = "\t", row.names = FALSE).
# pedigree quality control
clean_ped = check_ped(pedigree)$corrected_pedigree
validated = validate_pedigree(clean_ped, genotypes)$full_results
assigned = find_parentage(genotypes, parents, progeny, method = "best_pair")$full_results
# breed/line composition
freq = allele_freq_poly(reference, ref_ids, ploidy = 4)
prediction = solve_composition_poly(validation, freq, ploidy = 4)
composition_table = assign_composition_line(prediction, assign_threshold = 50)## R version 4.5.2 (2025-10-31)
## Platform: aarch64-apple-darwin20
## Running under: macOS 27.0.1
##
## Matrix products: default
## BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: America/Indiana/Indianapolis
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] dplyr_1.2.1 BIGpopA_2.2.0
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 jsonlite_2.0.0 compiler_4.5.2
## [4] tidyselect_1.2.1 stringr_1.6.0 snakecase_0.11.1
## [7] jquerylib_0.1.4 scales_1.4.0 yaml_2.3.12
## [10] fastmap_1.2.0 ggplot2_4.0.3 R6_2.6.1
## [13] labeling_0.4.3 generics_0.1.4 knitr_1.52
## [16] tibble_3.3.1 janitor_2.2.1 lubridate_1.9.5
## [19] bslib_0.12.0 pillar_1.11.1 RColorBrewer_1.1-3
## [22] rlang_1.3.0 stringi_1.8.9 cachem_1.1.0
## [25] xfun_0.61 quadprog_1.5-8 sass_0.4.10
## [28] S7_0.2.2 otel_0.2.0 timechange_0.4.0
## [31] cli_3.6.6 withr_3.0.3 magrittr_2.0.5
## [34] digest_0.6.39 grid_4.5.2 rstudioapi_0.19.0
## [37] lifecycle_1.0.5 vctrs_0.7.3 evaluate_1.0.5
## [40] glue_1.8.1 data.table_1.18.6.1 farver_2.1.2
## [43] rmarkdown_2.32 tools_4.5.2 pkgconfig_2.0.3
## [46] htmltools_0.5.9
If you use BIGpopA in research, run citation(“BIGpopA”) for the current reference.
## To cite package 'BIGpopA' in publications use:
##
## Chinchilla-Vargas J, Sandercock A, of Florida U (2026). _BIGpopA:
## Pedigree Validation Genetic Composition of Diploids & Polyploids_. R
## package version 2.2.0, <https://github.com/Breeding-Insight/BIGpopA>.
##
## A BibTeX entry for LaTeX users is
##
## @Manual{,
## title = {BIGpopA: Pedigree Validation Genetic Composition of Diploids & Polyploids},
## author = {Josue Chinchilla-Vargas and Alexander Sandercock and University {of Florida}},
## year = {2026},
## note = {R package version 2.2.0},
## url = {https://github.com/Breeding-Insight/BIGpopA},
## }
Developed as part of the Breeding Insight initiative to provide open-source, data-driven tools for modern breeding programs.