Introduction to the hwep package

# Introduction to the `hwep` package

# Introduction

This vignette covers the basic usage of the `hwep` package. The methods implemented here are described in detail in Gerard (2023a) and Gerard (2023b). We will cover:

1. Functions for calculating segregation probabilities.
2. Functions for calculating equilibrium genotype frequencies.
3. Functions for testing for equilibrium and random mating.

Let’s load the package so we can begin.

    library(hwep)

# The double reduction parameter.

Throughout this vignette, we will discuss the “double reduction parameter”, which we should briefly clarify. This parameter is a vector of probabilities of length `floor(ploidy / 4)`, where `ploidy` is the ploidy of the species. Element `i` of this vector is the probability that an offspring will have exactly `i` copies of identical-by-double-reduction (IBDR) alleles. If `alpha` is this parameter vector, than `1 - sum(alpha)` is the probability that an offspring has no IBDR alleles.

The double reduction parameter is known to have an upper bound, based on the model for meiosis considered. The largest bound typically assumed in the literature is derived from the “complete equational segregation model”, introduced by Mather (1935) and later generalized in Huang et al. (2019). These bounds can be calculated by the `drbounds()` function for different ploidies.

    drbounds(ploidy = 4)
    #> [1] 0.1666667
    drbounds(ploidy = 6)
    #> [1] 0.3
    drbounds(ploidy = 8)
    #> [1] 0.38571429 0.02142857
    drbounds(ploidy = 10)
    #> [1] 0.43650794 0.05952381
    drbounds(ploidy = 12)
    #> [1] 0.462662338 0.105519481 0.002705628

During our analysis procedures, we typically assume that the double reduction parameter is between 0 and these upper bounds.

# Segregation probabilities

This package comes with a few functions to calculate the probabilities of gamete and offspring dosages given parental dosages. These generalize classical Mendelian inheritance to include rates of double reduction. We use the specific model derived by Fisher & Mather (1943), and later generalized by Huang et al. (2019).

- `dgamete()`: This function will calculate gamete dosage probabilities given the parental genotype. So, if we want to calculate the probability of gametes having dosage 0, 1, and 2 when the parent has a dosage of 3, the double reduction rate is 0.1, and the ploidy is 4, we would run:

      dgamete(x = 0:2, alpha = 0.1, G = 3, ploidy = 4)
      #> [1] 0.025 0.450 0.525

- `gsegmat()`: Given a ploidy level and a double reduction rate, this function will calculate all possible gamete dosage probabilities for each possible parental genotype. The rows index the parental genotypes and the columns index the gamete genotypes.

      gsegmat(alpha = 0.1, ploidy = 4)
      #>       0    1     2
      #> 0 1.000 0.00 0.000
      #> 1 0.525 0.45 0.025
      #> 2 0.200 0.60 0.200
      #> 3 0.025 0.45 0.525
      #> 4 0.000 0.00 1.000

  From the above matrix, the probability of a gamete having dosage 1 when the parental dosage is 2 is 0.6.

      gsegmat(alpha = 0.1, ploidy = 4)[3, 2]
      #> [1] 0.6

- `gsegmat_symb()`: This function provides a symbolic representation of the gamete segregation probabilities. In the output `a` represents the probability of exactly zero copies of IBDR alleles, `b` represents the probability of exactly one copy of IBDR alleles, `c` represents the probability of exactly two copies of IBDR alleles, etc…

      gsegmat_symb(ploidy = 4)
      #>   0               1        2
      #> 0 "a+b"           "0"      "0"
      #> 1 "(1/2)a+(3/4)b" "(1/2)a" "(1/4)b"
      #> 2 "(1/6)a+(1/2)b" "(2/3)a" "(1/6)a+(1/2)b"
      #> 3 "(1/4)b"        "(1/2)a" "(1/2)a+(3/4)b"
      #> 4 "0"             "0"      "a+b"

- `zsegarray()`: Instead of considering gamete dosages, this function will calculate zygote dosage probabilities given both parental genotypes. It will do this for each possible offspring dosage and each possible parental genotype.

      sega <- zsegarray(alpha = 0.1, ploidy = 4)
      sega
      #> , , offspring = 0
      #>
      #>        parent2
      #> parent1     0        1     2            3            4
      #>       0 1.000 0.525000 0.200 2.500000e-02 4.440892e-17
      #>       1 0.525 0.275625 0.105 1.312500e-02 6.661338e-17
      #>       2 0.200 0.105000 0.040 5.000000e-03 3.330669e-17
      #>       3 0.025 0.013125 0.005 6.250000e-04 4.440892e-17
      #>       4 0.000 0.000000 0.000 4.440892e-17 4.440892e-17
      #>
      #> , , offspring = 1
      #>
      #>        parent2
      #> parent1            0      1     2      3 4
      #>       0 6.661338e-17 0.4500 0.600 0.4500 0
      #>       1 4.500000e-01 0.4725 0.405 0.2475 0
      #>       2 6.000000e-01 0.4050 0.240 0.1050 0
      #>       3 4.500000e-01 0.2475 0.105 0.0225 0
      #>       4 0.000000e+00 0.0000 0.000 0.0000 0
      #>
      #> , , offspring = 2
      #>
      #>        parent2
      #> parent1            0       1    2       3            4
      #>       0 6.661338e-17 0.02500 0.20 0.52500 1.000000e+00
      #>       1 2.500000e-02 0.22875 0.38 0.47875 5.250000e-01
      #>       2 2.000000e-01 0.38000 0.44 0.38000 2.000000e-01
      #>       3 5.250000e-01 0.47875 0.38 0.22875 2.500000e-02
      #>       4 1.000000e+00 0.52500 0.20 0.02500 1.746995e-17
      #>
      #> , , offspring = 3
      #>
      #>        parent2
      #> parent1            0            1     2      3    4
      #>       0 6.661338e-17 2.498002e-17 0.000 0.0000 0.00
      #>       1 1.151856e-16 2.250000e-02 0.105 0.2475 0.45
      #>       2 7.771561e-17 1.050000e-01 0.240 0.4050 0.60
      #>       3 8.881784e-17 2.475000e-01 0.405 0.4725 0.45
      #>       4 8.881784e-17 4.500000e-01 0.600 0.4500 0.00
      #>
      #> , , offspring = 4
      #>
      #>        parent2
      #> parent1            0            1     2        3            4
      #>       0 6.661338e-17 4.440892e-17 0.000 0.000000 1.746995e-17
      #>       1 0.000000e+00 6.250000e-04 0.005 0.013125 2.500000e-02
      #>       2 0.000000e+00 5.000000e-03 0.040 0.105000 2.000000e-01
      #>       3 0.000000e+00 1.312500e-02 0.105 0.275625 5.250000e-01
      #>       4 5.970534e-17 2.500000e-02 0.200 0.525000 1.000000e+00

  Thus, the probability of an offspring dosage of 3 when parental dosages are 2 and 4, is

      sega[4, 3, 5]
      #> [1] 0.105

# Equilibrium genotype frequencies

Equilibrium frequencies can be generated with `hwefreq()` for arbitrary (even) ploidy levels.

    hout <- hwefreq(r = 0.1, alpha = 0.1, ploidy = 6)
    round(hout, digits = 5)
    #> [1] 0.55062 0.32437 0.10232 0.01998 0.00250 0.00019 0.00001

Alternatively, you can control the number of iterations of random mating before stopping. The population begins in a state where `r` proportion of individuals have genotype `ploidy` and `1-r` proportion has genotype `0`. It then updates each generation’s genotype frequencies using `freqnext()`. E.g., for `r=0.1` and `alpha=0.1`, after the first round of random mating, we have:

    freqnext(freq = c(0.9, 0, 0, 0, 0.1), alpha = 0.1)
    #> [1] 8.100000e-01 3.677614e-17 1.800000e-01 4.440892e-17 1.000000e-02

    hwefreq(r = 0.1, alpha = 0.1, niter = 1, ploidy = 4)
    #> [1] 8.100000e-01 3.677614e-17 1.800000e-01 4.440892e-17 1.000000e-02

# Testing for equilibrium and random mating

The main function for this package is `hwefit()`, which implements various tests for random mating and equilibrium. This function has parallelization support through the future package. We’ll demonstrate using the future package assuming at least two cores are available.

    library(future)
    availableCores()
    #> system
    #>     16

    plan(multisession, workers = 2)

Let’s simulate some data at equilibrium to demonstrate our methods:

    geno_freq <- hwefreq(r = 0.5, alpha = 0.1, ploidy = 6)
    nmat <- t(rmultinom(n = 1000, size = 100, prob = geno_freq))
    head(nmat)
    #>      [,1] [,2] [,3] [,4] [,5] [,6] [,7]
    #> [1,]    0   10   28   22   23   12    5
    #> [2,]    1    7   25   35   15   15    2
    #> [3,]    2   10   23   26   22   14    3
    #> [4,]    3   12   18   26   19   15    7
    #> [5,]    2    8   26   27   25   10    2
    #> [6,]    0   11   19   31   26   10    3

`hwefit()` expects a matrix of genotype counts, where the rows index the loci and the columns index the genotype. So `nmat[i, j]` is the count of the number of individuals that have dosage `j-1` at locus `i`.

You control the type of test via the `type` argument. Using `type = "ustat"` will use the \(U\)-statistic approach to test for equilibrium, as implemented in `hweustat()`.

    uout <- hwefit(nmat = nmat, type = "ustat")
    #> Using 1 worker(s) to run hwefit() on 1000 loci...
    #> Loading required package: foreach
    #> Loading required package: rngtools
    #> Done!

The output is a list-like object that contains the estimates of double reduction (`alpha`), the \(p\)-values for the test against the null of equilibrium (`p_hwe`), as well as the test-statistics (`chisq_hwe`) and degrees of freedom (`df_hwe`) of this test.

On average, we obtain good estimates of the double reduction rate

    mean(uout$alpha1)
    #> [1] 0.1109858

But the sampling properties of this estimator are highly variable, even for such a large sample size:

    hist(uout$alpha1)

This highlights the difficulty in estimating double reduction using just a single biallelic locus.

The p-values are generally uniformly distributed, as they should be since we generated data under the null of equilibrium.

    hist(uout$p_hwe, breaks = 10, xlab = "P-values", main = "")

    qqplot(
      x = ppoints(length(uout$p_hwe)),
      y = uout$p_hwe,
      xlab = "Theoretical Quantiles",
      ylab = "Empirical Quantiles",
      main = "QQ-plot"
    )
    abline(0, 1, lty = 2, col = 2)

You can view this QQ-plot on the \(-log_{10}\)-scale using `qqpvalue()`. This plot will also show the simultaneous confidence bands for the QQ-plot from Aldor-Noiman et al. (2013), which can also be calculated using `ts_bands()`.

    qqpvalue(pvals = uout$p_hwe, method = "base")

Make sure to shut down your workers after you are done:

    plan(sequential)

The other values of “type” run different procedures:

- `"mle"`: Runs likelihood procedures to test for equilibrium and estimate double reduction. Only available for (even) ploidies less than or equal to 10. This generally behaves similarly to the \(U\)-statistic approach. This is implemented by the `hwelike()` function.
- `"rm"`: Runs likelihood procedures to test for random mating. This is implemented by the `rmlike()` function.
- `"nodr"`: Runs likelihood procedures to test for equilibrium assuming no double reduction. This is implemented by the `hwenodr()` function.

# References

- Aldor-Noiman, S., Brown, L. D., Buja, A., Rolke, W., & Stine, R. A. (2013). The power to see: A new graphical test of normality. The American Statistician, 67(4), 249-260. doi: 10.1080/00031305.2013.847865

- Fisher, R. A., & Mather, K. (1943). The inheritance of style length in Lythrum salicaria. Annals of Eugenics, 12(1), 1-23. doi: 10.1111/j.1469-1809.1943.tb02307.x

- Gerard D (2023a). “Double reduction estimation and equilibrium tests in natural autopolyploid populations.” Biometrics, 79(3), 2143–2156. doi: 10.1111/biom.13722

- Gerard D (2023b). “Bayesian tests for random mating in polyploids.” Molecular Ecology Resources, 23(8), 1812–1822. doi: 10.1111/1755-0998.13856

- Huang, K., Wang, T., Dunn, D. W., Zhang, P., Cao, X., Liu, R., & Li, B. (2019). Genotypic frequencies at equilibrium for polysomic inheritance under double-reduction. G3: Genes | Genomes | Genetics, 9(5), 1693-1706. doi: 10.1534/g3.119.400132

- Mather, K. (1935). Reductional and equational separation of the chromosomes in bivalents and multivalents. Journal of Genetics, 30(1), 53-78. doi: 10.1007/BF02982205