Author: Developed by Gabriel Hoffman

# Normal approximation vs. empirical simulation

## Asymptotic normal approximation

Let the vector \({\bf p}\) be the true fractions across \(D\) categories. Consider \(C\) total counts sampled from a Dirichlet-multinomial (DMN) distribution with overdispersion \(\tau\), where \(\tau=1\) reduces to the multinomial distribution. The centered log ratio (CLR) of the \(i^{th}\) estimated fraction in \({\bf \hat p}\) is

\[\begin{equation} \tag{1} \text{clr}_i({\bf \hat p}) = \log(\hat p_i) - \frac{1}{D}\sum_{j=1}^D \log(\hat p_j) \end{equation}\] and we show that the sampling variance is

\[\begin{equation} \tag{2} \text{var}[\text{clr}_i({\bf \hat p})] = \frac{\tau}{C} \left[ \frac{1}{\hat p_i} - \frac{2}{ D \hat p_i} + \frac{1}{D^2}\sum_{j=1}^D \frac{1}{\hat p_j} \right]. \label{eqn2} \end{equation}\]

## Simulations

The sampling variance is derived from asymototic theory, so we examine its behavior for finite total counts. Here we evaluate the empirical variance from \(1,000\) draws from a Dirichlet-multinomial distribution while varying \(D\), \(\tau\), \(C\). A pseudocount of 0.5 is added to the observed counts since the asymptotic theory is not defined for counts of zero.

Here we plot the standard deviation after CLR transform from the empirical DMN and the asymptotic normal approximation under a range of conditions. Results are shown for instances with at least 2 counts.

### D=2 categories

### D=15 categories

### Interpretation

The asymptotic standard deviation shows good agreement with the empirical results even for small values of \(C\), when at least 2 counts are observed. In practice, it is often reasonable to assume a sufficient number of counts before a variable is included in an analysis. Importantly, with less than 2 counts the asymptotic theory gives a larger standard deviation than the emprical results (results not shown). Therefore, this approach is conservative and should not underestimate the true amount of variation. The asymptotic normal approximation is most accurate for large total counts \(C\), large proportions \(p\), and small overdispersion \(\tau\).

#### Consideration of overdispersion

Based on Equation (2), the variance of the CLR-transformed proportions is a linear function of \(\tau\). Importantly, downstream analysis of the CLR-transformed proportions with a precision-weighted linear (mixed) model or a variance stabilizing transform depends only on the relative variances. Since relative variances are invariant to the scale of \(\tau\), for these applications the value of \(\tau\) can be set to 1 instead of being estimated from the data.

For other applications, `crumblr` can estimate \(\tau\) from the data by using `crumblr(counts, tau=NULL)`. This calls `dmn.mle()` to estimate the parameters of the DMN distribution and is substantially faster than alternatives.

# Session Info

    ## R version 4.6.0 (2026-04-24)
    ## Platform: x86_64-pc-linux-gnu
    ## Running under: Ubuntu 24.04.4 LTS
    ##
    ## Matrix products: default
    ## BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
    ## LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
    ##
    ## locale:
    ##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8
    ##  [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
    ##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C
    ## [10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
    ##
    ## time zone: Etc/UTC
    ## tzcode source: system (glibc)
    ##
    ## attached base packages:
    ## [1] parallel  stats     graphics  grDevices utils     datasets  methods   base
    ##
    ## other attached packages:
    ##  [1] lubridate_1.9.5  forcats_1.0.1    stringr_1.6.0    dplyr_1.2.1      purrr_1.2.2
    ##  [6] readr_2.2.0      tidyr_1.3.2      tibble_3.3.1     tidyverse_2.0.0  glue_1.8.1
    ## [11] HMP_2.0.1        dirmult_0.1.3-5  crumblr_1.4.0    ggplot2_4.0.3    BiocStyle_2.40.0
    ##
    ## loaded via a namespace (and not attached):
    ##   [1] RColorBrewer_1.1-3          sys_3.4.3                   jsonlite_2.0.0
    ##   [4] magrittr_2.0.5              farver_2.1.2                nloptr_2.2.1
    ##   [7] rmarkdown_2.31              fs_2.1.0                    vctrs_0.7.3
    ##  [10] minqa_1.2.8                 ggtree_4.2.0                htmltools_0.5.9
    ##  [13] S4Arrays_1.12.0             broom_1.0.12                SparseArray_1.12.0
    ##  [16] gridGraphics_0.5-1          variancePartition_1.42.0    sass_0.4.10
    ##  [19] KernSmooth_2.23-26          bslib_0.10.0                htmlwidgets_1.6.4
    ##  [22] pbkrtest_0.5.5              plyr_1.8.9                  cachem_1.1.0
    ##  [25] buildtools_1.0.0            lifecycle_1.0.5             iterators_1.0.14
    ##  [28] pkgconfig_2.0.3             Matrix_1.7-5                R6_2.6.1
    ##  [31] fastmap_1.2.0               rbibutils_2.4.1             MatrixGenerics_1.24.0
    ##  [34] digest_0.6.39               numDeriv_2016.8-1.1         aplot_0.2.9
    ##  [37] patchwork_1.3.2             S4Vectors_0.50.0            GenomicRanges_1.64.0
    ##  [40] vegan_2.7-3                 labeling_0.4.3              timechange_0.4.0
    ##  [43] mgcv_1.9-4                  abind_1.4-8                 compiler_4.6.0
    ##  [46] fontquiver_0.2.1            aod_1.3.3                   withr_3.0.2
    ##  [49] S7_0.2.2                    backports_1.5.1             BiocParallel_1.46.0
    ##  [52] viridis_0.6.5               gplots_3.3.0                MASS_7.3-65
    ##  [55] rappdirs_0.3.4              DelayedArray_0.38.0         corpcor_1.6.10
    ##  [58] gtools_3.9.5                caTools_1.18.3              permute_0.9-10
    ##  [61] tools_4.6.0                 ape_5.8-1                   remaCor_0.0.20
    ##  [64] nlme_3.1-169                grid_4.6.0                  cluster_2.1.8.2
    ##  [67] reshape2_1.4.5              generics_0.1.4              gtable_0.3.6
    ##  [70] tzdb_0.5.0                  hms_1.1.4                   XVector_0.52.0
    ##  [73] BiocGenerics_0.58.0         foreach_1.5.2               pillar_1.11.1
    ##  [76] yulab.utils_0.2.4           limma_3.68.0                splines_4.6.0
    ##  [79] treeio_1.36.0               lattice_0.22-9              tidyselect_1.2.1
    ##  [82] fontLiberation_0.1.0        SingleCellExperiment_1.34.0 maketools_1.3.2
    ##  [85] knitr_1.51                  fontBitstreamVera_0.1.1     reformulas_0.4.4
    ##  [88] gridExtra_2.3               IRanges_2.46.0              Seqinfo_1.2.0
    ##  [91] SummarizedExperiment_1.42.0 RhpcBLASctl_0.23-42         stats4_4.6.0
    ##  [94] xfun_0.57                   Biobase_2.72.0              statmod_1.5.1
    ##  [97] matrixStats_1.5.0           stringi_1.8.7               lazyeval_0.2.3
    ## [100] ggfun_0.2.0                 yaml_2.3.12                 boot_1.3-32
    ## [103] evaluate_1.0.5              codetools_0.2-20            rpart.plot_3.1.4
    ## [106] gdtools_0.5.0               BiocManager_1.30.27         ggplotify_0.1.3
    ## [109] cli_3.6.6                   rpart_4.1.27                RcppParallel_5.1.11-2
    ## [112] systemfonts_1.3.2           Rdpack_2.6.6                jquerylib_0.1.4
    ## [115] Rcpp_1.1.1-1.1              zigg_0.0.2                  EnvStats_3.1.0
    ## [118] Rfast_2.1.5.2               bitops_1.0-9                lme4_2.0-1
    ## [121] viridisLite_0.4.3           mvtnorm_1.3-7               tidytree_0.4.7
    ## [124] ggiraph_0.9.6               lmerTest_3.2-1              scales_1.4.0
    ## [127] fANCOVA_0.6-1               rlang_1.2.0