Author: Breeding Insight - Josue Chinchilla-Vargas
Date: 2026-07-16

# BIGpopA Tutorial

### Pedigree cleaning, validation, and parentage assignment from SNP genotype data

#### Breeding Insight - Josue Chinchilla-Vargas

#### 2026-07-16

# Introduction

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:

- check_ped() — detect and correct structural pedigree errors (duplicates, conflicting trios, inconsistent sex roles, missing parents).
- validate_pedigree() — test parent–offspring trios for Mendelian errors against SNP genotypes and flag incorrect assignments.
- find_parentage() — assign the most likely parent(s) to progeny from a pool of candidate parents.

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.

## Install

- It requires R version superior to 4.4.0

Install BIGpopA from CRAN (if not already installed):

    install.packages("BIGpopA")

# Input data

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

    genotypes

    ##      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 mis-assigned trio (C_bad), and a sample with mostly missing genotypes (C_low).

# Pedigree cleaning

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.

# Pedigree validation

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, and a best-matching replacement parent is suggested where relevant. 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)

    ped_validate_report = ped_validate_results$full_results
    ped_validate_report

    ## 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 best_male_candidate
    ##                         <num>                       <num>              <char>
    ##  1:                        NA                          NA                  C1
    ##  2:                        NA                          NA                  C1
    ##  3:                        NA                          NA                  C2
    ##  4:                        NA                          NA                  C2
    ##  5:                        NA                          NA                <NA>
    ##  6:                        NA                          NA                <NA>
    ##  7:                        NA                          NA                <NA>
    ##  8:                     83.33                           0                  M1
    ##  9:                      0.00                          NA                <NA>
    ## 10:                        NA                          NA                <NA>
    ##     best_male_candidate_error_pct best_female_candidate
    ##                             <num>                <char>
    ##  1:                             0                    C3
    ##  2:                             0                 C_bad
    ##  3:                             0                    M1
    ##  4:                             0                    C3
    ##  5:                            NA                  <NA>
    ##  6:                            NA                  <NA>
    ##  7:                            NA                  <NA>
    ##  8:                             0                  <NA>
    ##  9:                            NA                    C1
    ## 10:                            NA                  <NA>
    ##     best_female_candidate_error_pct
    ##                               <num>
    ##  1:                            0.00
    ##  2:                            0.00
    ##  3:                           55.56
    ##  4:                            0.00
    ##  5:                              NA
    ##  6:                              NA
    ##  7:                              NA
    ##  8:                              NA
    ##  9:                            0.00
    ## 10:                              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).

# Parentage assignment

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)

    find_parentage_report = find_parentage_results$full_results
    find_parentage_report

    ## 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
    ##         <char>
    ## 1:        pass
    ## 2:        pass
    ## 3:        pass
    ## 4:        pass
    ## 5: low_markers

Each progeny is assigned the pair minimizing Mendelian error and labelled 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.

# Breed and line composition

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 each sample to the cultivar with the highest composition (ignoring the R2 fit column) and format the panel columns as percentages:

    line_cols = setdiff(names(prediction), "R2")

    pred_results = prediction %>%
      mutate(
        ID             = rownames(prediction),
        predicted_line = line_cols[max.col(as.matrix(prediction[line_cols]), ties.method = "first")],
        across(all_of(line_cols), ~ scales::percent(., accuracy = 0.1))
      ) %>%
      select(ID, predicted_line, all_of(line_cols))

    pred_results

    ##              ID predicted_line  Jewel Draper
    ## Sample1 Sample1          Jewel 100.0%   0.0%
    ## Sample2 Sample2         Draper   0.0% 100.0%
    ## Sample3 Sample3          Jewel  50.0%  50.0%

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 = “, row.names = FALSE).

# Complete workflow

    # 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)

# Session information

    sessionInfo()

    ## R version 4.5.2 (2025-10-31)
    ## Platform: aarch64-apple-darwin20
    ## Running under: macOS Tahoe 26.5.2
    ##
    ## 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/New_York
    ## tzcode source: internal
    ##
    ## attached base packages:
    ## [1] stats     graphics  grDevices utils     datasets  methods   base
    ##
    ## other attached packages:
    ## [1] dplyr_1.2.1   BIGpopA_1.0.6
    ##
    ## loaded via a namespace (and not attached):
    ##  [1] gtable_0.3.6       jsonlite_2.0.0     compiler_4.5.2     tidyselect_1.2.1
    ##  [5] stringr_1.6.0      snakecase_0.11.1   jquerylib_0.1.4    scales_1.4.0
    ##  [9] yaml_2.3.12        fastmap_1.2.0      ggplot2_4.0.3      R6_2.6.1
    ## [13] labeling_0.4.3     generics_0.1.4     knitr_1.51         tibble_3.3.1
    ## [17] janitor_2.2.1      lubridate_1.9.5    bslib_0.11.0       pillar_1.11.1
    ## [21] RColorBrewer_1.1-3 rlang_1.2.0        stringi_1.8.7      cachem_1.1.0
    ## [25] xfun_0.59          quadprog_1.5-8     sass_0.4.10        S7_0.2.2
    ## [29] otel_0.2.0         timechange_0.4.0   cli_3.6.6          withr_3.0.3
    ## [33] magrittr_2.0.5     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     glue_1.8.1
    ## [41] data.table_1.18.4  farver_2.1.2       rmarkdown_2.31     tools_4.5.2
    ## [45] pkgconfig_2.0.3    htmltools_0.5.9

# How to cite

If you use BIGpopA in research, run citation(“BIGpopA”) for the current reference.

    citation("BIGpopA")

    ## 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 1.0.6, <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 1.0.6},
    ##     url = {https://github.com/Breeding-Insight/BIGpopA},
    ##   }

# Acknowledgments

Developed as part of the Breeding Insight initiative to provide open-source, data-driven tools for modern breeding programs.