# Genome-wide methylation analysis using coMethDMR via parallel computing

# Introduction

In the previous vignette “Introduction to coMethDMR”, we discussed components of the coMethDMR pipeline and presented example R scripts for running an analysis with coMethDMR serially. However, because identifying co-methylated clusters and fitting mixed effects models on large numbers of genomic regions can be computationally expensive, we illustrate implementation of parallel computing for coMethDMR via the BiocParallel R package in this vignette.

## Installation

The latest version can be installed by:

    if (!requireNamespace("BiocManager", quietly = TRUE))
        install.packages("BiocManager")

    BiocManager::install("coMethDMR")

First, we load these two packages.

    library(coMethDMR)
    library(BiocParallel)

## Overview

In Section 2, we give a brief re-introduction to the example data. In Section 3, we present example scripts for analyzing a single type (e.g. genic regions) of genomic regions using parallel computing. In Section 4, we present example scripts for analyzing genic and intergenic regions on the Illumina arrays using parallel computing.

# Example Dataset

For illustration, we use a subset of prefrontal cortex methylation data (GEO GSE59685) described in Lunnon et al. (2014). This example dataset includes beta values for 8552 CpGs on chromosome 22 for a random selection of 20 subjects.

    data(betasChr22_df)

We assume quality control and normalization of the methylation dataset have been performed by R packages such as minfi or RnBeads. Further, we assume that probes and samples with excessive missingness have been removed (this task can be completed by subsetting the original methylation data set with the probes and samples identified by the `MarkMissing()` function).

    # Purge any probes or samples with excessive missing values
    markCells_ls <- MarkMissing(dnaM_df = betasChr22_df)
    betasChr22_df <- betasChr22_df[
      markCells_ls$keepProbes,
      markCells_ls$keepSamples
    ]

    # Inspect
    betasChr22_df[1:5, 1:5]

    ##            GSM1443279 GSM1443663 GSM1443434 GSM1443547 GSM1443577
    ## cg00004192  0.9249942  0.8463296  0.8700718  0.9058205  0.9090382
    ## cg00004775  0.6523025  0.6247554  0.7573476  0.6590817  0.6726261
    ## cg00012194  0.8676339  0.8679048  0.8484754  0.8754985  0.8484458
    ## cg00013618  0.9466056  0.9475467  0.9566493  0.9588431  0.9419563
    ## cg00014104  0.3932388  0.5525716  0.4075900  0.3997278  0.3216956

The corresponding phenotype dataset included variables `stage` (Braak AD stage), `subject.id`, `slide` (batch effect), `sex`, `Sample` and `age.brain` (age of the brain donor).

    data(pheno_df)
    head(pheno_df)

    ##    stage subject.id         sex     Sample age.brain       slide
    ## 3      0          1 Sex: FEMALE GSM1443251        82  6042316048
    ## 8      2          2 Sex: FEMALE GSM1443256        82  6042316066
    ## 10    NA          3   Sex: MALE GSM1443258        89  6042316066
    ## 15     1          4 Sex: FEMALE GSM1443263        81  7786923107
    ## 21     2          5 Sex: FEMALE GSM1443269        92  6042316121
    ## 22     1          6   Sex: MALE GSM1443270        78  6042316099

Note that only samples with both methylation data and non-missing phenotype data are analyzed by coMethDMR. So in this example, the sample with `subject.id = 3` which lacks AD stage information will be excluded from analysis. Please also note the phenotype file needs to have a variable called “Sample” that will be used by coMethDMR to link to the methylation dataset.

# Analyzing One Type of Genomic Region via BiocParallel

As mentioned previously in “Introduction to coMethDMR”, there are several steps in the coMethDMR pipeline:

1. Obtain CpGs located closely in pre-defined genomic regions,
2. Identify co-methylated regions, and
3. Test co-methylated regions against the outcome variable (AD stage).

Suppose we are interested in analyzing genic regions on the 450k arrays. In the following, `closeByGeneAll_ls` is a list, where each item includes at least 3 CpGs located closely (max distance between 2 CpGs is 200bp by default).

    closeByGeneAll_ls <- readRDS(
      system.file(
        "extdata",
        "450k_Gene_3_200.rds",
        package = 'coMethDMR',
        mustWork = TRUE
      )
    )

We can inspect the first region in the list via:

    closeByGeneAll_ls[1]

    ## $`chr4:91760141-91760229`
    ## [1] "cg01583657" "cg25325192" "cg06044899"

Next, for demonstration, we select regions on chromosome 22.

    indx <- grep("chr22:", names(closeByGeneAll_ls))

    closeByGene_ls <- closeByGeneAll_ls[indx]
    rm(closeByGeneAll_ls)
    length(closeByGene_ls)

    ## [1] 676

There are 676 genic regions to be tested for chromosome 22. As an example, this vignette will analyze the first 10 regions, which contain the following CpGs:

    closeByGene_ls[1:10]

    ## $`chr22:30662799-30663041`
    ## [1] "cg19018155" "cg10467217" "cg24727122" "cg02597698" "cg25666403"
    ## [6] "cg02026204" "cg05539509" "cg07498879"
    ##
    ## $`chr22:18632404-18633123`
    ##  [1] "cg27281093" "cg18699242" "cg01963623" "cg02169692" "cg00077299"
    ##  [6] "cg07964128" "cg02518889" "cg01246421" "cg08205655" "cg10685559"
    ##
    ## $`chr22:43253145-43253208`
    ## [1] "cg15656623" "cg10648908" "cg02351223"
    ##
    ## $`chr22:43253517-43253559`
    ## [1] "cg09861871" "cg00079563" "cg26529516" "cg04527868" "cg01029450"
    ##
    ## $`chr22:43254043-43254168`
    ## [1] "cg23778094" "cg00539564" "cg09367092"
    ##
    ## $`chr22:30901532-30901886`
    ## [1] "cg03214697" "cg00093544" "cg02923264" "cg03014622" "cg19964641"
    ## [6] "cg15829116" "cg07217063"
    ##
    ## $`chr22:39883190-39883347`
    ## [1] "cg00550928" "cg00101350" "cg26692811"
    ##
    ## $`chr22:30685584-30685926`
    ## [1] "cg00101453" "cg23835894" "cg22088670" "cg03652890" "cg24843389"
    ## [6] "cg15272908" "cg20395643" "cg10998744"
    ##
    ## $`chr22:45064086-45064238`
    ## [1] "cg10031995" "cg21293611" "cg11450750"
    ##
    ## $`chr22:45072455-45072724`
    ## [1] "cg24019054" "cg20339720" "cg16166559" "cg06849477"

In order to make these examples as simple as possible, we will remove the CpGs from our data set which are not included in these 10 regions. In practice, you would keep the full data set.

    keepCpGs_char <- unique(unlist(closeByGene_ls[1:10]))
    betasChr22small_df <-
      betasChr22_df[rownames(betasChr22_df) %in% keepCpGs_char, ]
    dim(betasChr22small_df)

    ## [1] 54 20

## Compute residuals

This step removes uninteresting technical and biological effects using the `GetResiduals` function, so that the resulting co-methylated clusters are only driven by the biological factors we are interested in. Note that there may be an error with the parallel back-end (`1 parallel job did not deliver a result`); this has occurred rarely for us (and not consistently enough for us to create a reproducible example), but it goes away when we execute the code a second time. We then recommend that you test this with a smaller subset of your data first. If you are able to consistently replicate a parallel error, please submit a bug report: https://github.com/TransBioInfoLab/coMethDMR/issues.

    resid_mat <- GetResiduals(
      dnam = betasChr22small_df,
      # converts to Mvalues for fitting linear model
      betaToM = TRUE,
      pheno_df = pheno_df,
      # Features in pheno_df used as covariates
      covariates_char = c("age.brain", "sex", "slide"),
      nCores_int = 2
    )

    ## Phenotype data is not in the same order as methylation data. We used column Sample in phenotype data to put these two files in the same order.

## Finding co-methylated regions

The cluster computing with the BiocParallel package involves simply changing the default value of one argument: `nCores_int`. This argument enables you to perform parallel computing when you set the number of cores to an integer value greater than 1. If you do not know how many cores your machine has, use the `detectCores()` function from the `parallel` package. Note that, if you have many applications open on your computer, you should not use all of the cores available.

Once we know how many cores we have available, we execute the `CoMethAllRegions()` function using each worker in the cluster, to find co-methylated clusters in the genic regions. As an example, we only analyze the first 10 CpG regions (`CpGs_ls`).

    system.time(
      coMeth_ls <- CoMethAllRegions(
        dnam = resid_mat,
        betaToM = FALSE,
        method = "spearman",
        arrayType = "450k",
        CpGs_ls = closeByGene_ls[1:10],
        nCores_int = 2
      )
    )

    ##    user  system elapsed
    ##   4.130   0.105   6.268

    # Windows: NA
    # Mac: ~14 seconds for 2 cores

The object `coMeth_ls` is a list, with each item containing the list of CpGs within an identified co-methylated region.

## Testing the co-methylated regions

Next we test these co-methylated regions against continuous phenotype `stage`, adjusting for covariates `age` and `sex`, by executing the `lmmTestAllRegions()` function.

    res_df <- lmmTestAllRegions(
      betas = betasChr22small_df,
      region_ls = coMeth_ls,
      pheno_df = pheno_df,
      contPheno_char = "stage",
      covariates_char = c("age.brain", "sex"),
      modelType = "randCoef",
      arrayType = "450k",
      nCores_int = 2
      # outLogFile = "res_lmm_log.txt"
    )

Model fit messages and diagnostics for each region will be saved to the log file specified with the `outLogFile` argument. For a single region, this will return a one row of model fit statistics similar to the following:

chrom | start | end | nCpGs | Estimate | StdErr | Stat | pValue
--- | --- | --- | --- | --- | --- | --- | ---
chr22 | 24823455 | 24823519 | 4 | -0.0702 | 0.0290 | -2.4184 | 0.0155

Finally, we can annotate these results:

    AnnotateResults(res_df)

    ##   chrom    start      end nCpGs     Estimate     StdErr       Stat      pValue
    ## 1 chr22 30662799 30663041     8 -0.059977631 0.01878100 -3.1935273 0.001405461
    ## 2 chr22 43253521 43253559     4 -0.007602187 0.02873768 -0.2645373 0.791365960
    ## 3 chr22 30901532 30901886     7  0.018015623 0.01555597  1.1581162 0.246816609
    ## 4 chr22 39883190 39883347     3  0.069276861 0.02863864  2.4189998 0.015563247
    ##           FDR           UCSC_RefGene_Group UCSC_RefGene_Accession
    ## 1 0.005621842 1stExon;5'UTR;TSS1500;TSS200              NM_020530
    ## 2 0.791365960                       TSS200 NM_001142293;NM_014570
    ## 3 0.329088811    1stExon;5'UTR;Body;TSS200 NM_001161368;NM_174977
    ## 4 0.031126493         1stExon;5'UTR;TSS200 NM_001098270;NM_002409
    ##   UCSC_RefGene_Name Relation_to_Island
    ## 1               OSM            OpenSea
    ## 2           ARFGAP3             Island
    ## 3           SEC14L4     Island;S_Shore
    ## 4             MGAT3            N_Shore

# coMethDMR Analysis Pipeline for 450k Methylation Arrays Datasets via BiocParallel

In this section, we provide example scripts for testing genic and intergenic regions using coMethDMR and BiocParallel R packages.

First, we read in clusters of CpGs located closely on the array, in genic and intergenic regions, then combine them into a single list.

    closeByGene_ls <- readRDS(
      system.file(
        "extdata",
        "450k_Gene_3_200.rds",
        package = 'coMethDMR',
        mustWork = TRUE
      )
    )

    closeByInterGene_ls <- readRDS(
      system.file(
        "extdata",
        "450k_InterGene_3_200.rds",
        package = 'coMethDMR',
        mustWork = TRUE
      )
    )

    # put them together in one list
    closeBy_ls <- c(closeByInterGene_ls, closeByGene_ls)
    length(closeBy_ls)

    ## [1] 42536

    closeBy_ls[1:3]

    ## $`chr1:569427-569687`
    ## [1] "cg08858441" "cg03348902" "cg01070250"
    ##
    ## $`chr1:713921-714177`
    ## [1] "cg10037654" "cg14057946" "cg11422233" "cg16047670" "cg18263059"
    ## [6] "cg01014490"
    ##
    ## $`chr1:834183-834356`
    ## [1] "cg13938959" "cg12445832" "cg23999112"

With this list of regions and their probe IDs, repeat the analysis code in the previous section.

# A Comment on Using EPIC Methylation Arrays Datasets

The analysis for EPIC methylation arrays would be the same as those for 450k arrays, except by testing genomic regions in files “EPIC_Gene_3_200.rds” and “EPIC_InterGene_3_200.RDS”, instead of “450k_Gene_3_200.rds” and “450k_InterGene_3_200.rds”. These data sets are available at https://github.com/TransBioInfoLab/coMethDMR_data/tree/main/data.

# Additional Comments on Computational Time and Resources

In this vignette, we have analyzed a small subset of a real EWAS dataset (i.e. only chromosome 22 data on 20 subjects). To give users a more realistic estimate of time for analyzing real EWAS datasets, we also measured time used for analyzing the entire Lunnon et al. (2014) dataset with 110 samples on all chromosomes. These computation times measured on a Dell Precision 5810 with 64Gb of RAM, an Intel Xeon E5-2640 CPU at 2.40Ghz, and using up to 20 cores. More specifically, in Section 4, the entire coMethDMR workflow for took 103 minutes with 6 cores and used a maximum of 24Gb of RAM (for the `CoMethAllRegions()` function). We’re currently working improving the speed and reducing the size of coMethDMR, so please check back soon for updates.

# Session Information

    utils::sessionInfo()

    ## 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
    ##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8
    ##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
    ##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C
    ##  [9] LC_ADDRESS=C               LC_TELEPHONE=C
    ## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
    ##
    ## time zone: Etc/UTC
    ## tzcode source: system (glibc)
    ##
    ## attached base packages:
    ## [1] stats4    stats     graphics  grDevices utils     datasets  methods
    ## [8] base
    ##
    ## other attached packages:
    ##  [1] BiocParallel_1.47.0  corrplot_0.95        GenomicRanges_1.65.0
    ##  [4] Seqinfo_1.3.0        IRanges_2.47.2       S4Vectors_0.51.3
    ##  [7] BiocGenerics_0.59.6  generics_0.1.4       coMethDMR_1.17.0
    ## [10] BiocStyle_2.41.0
    ##
    ## loaded via a namespace (and not attached):
    ##   [1] RColorBrewer_1.1-3
    ##   [2] sys_3.4.3
    ##   [3] jsonlite_2.0.0
    ##   [4] magrittr_2.0.5
    ##   [5] GenomicFeatures_1.65.0
    ##   [6] farver_2.1.2
    ##   [7] nloptr_2.2.1
    ##   [8] rmarkdown_2.31
    ##   [9] BiocIO_1.23.3
    ##  [10] vctrs_0.7.3
    ##  [11] multtest_2.69.0
    ##  [12] memoise_2.0.1
    ##  [13] minqa_1.2.8
    ##  [14] Rsamtools_2.29.0
    ##  [15] DelayedMatrixStats_1.35.0
    ##  [16] RCurl_1.98-1.18
    ##  [17] askpass_1.2.1
    ##  [18] htmltools_0.5.9
    ##  [19] S4Arrays_1.13.0
    ##  [20] BiocBaseUtils_1.15.1
    ##  [21] AnnotationHub_4.3.0
    ##  [22] curl_7.1.0
    ##  [23] Rhdf5lib_2.1.0
    ##  [24] SparseArray_1.13.2
    ##  [25] rhdf5_2.57.1
    ##  [26] sass_0.4.10
    ##  [27] nor1mix_1.3-3
    ##  [28] bslib_0.11.0
    ##  [29] plyr_1.8.9
    ##  [30] httr2_1.2.2
    ##  [31] cachem_1.1.0
    ##  [32] IlluminaHumanMethylation450kanno.ilmn12.hg19_0.6.1
    ##  [33] buildtools_1.0.0
    ##  [34] GenomicAlignments_1.49.0
    ##  [35] lifecycle_1.0.5
    ##  [36] iterators_1.0.14
    ##  [37] pkgconfig_2.0.3
    ##  [38] Matrix_1.7-5
    ##  [39] R6_2.6.1
    ##  [40] fastmap_1.2.0
    ##  [41] rbibutils_2.4.1
    ##  [42] MatrixGenerics_1.25.0
    ##  [43] digest_0.6.39
    ##  [44] numDeriv_2016.8-1.1
    ##  [45] siggenes_1.87.0
    ##  [46] reshape_0.8.10
    ##  [47] AnnotationDbi_1.75.0
    ##  [48] ExperimentHub_3.3.0
    ##  [49] RSQLite_3.53.1
    ##  [50] base64_2.0.2
    ##  [51] filelock_1.0.3
    ##  [52] httr_1.4.8
    ##  [53] abind_1.4-8
    ##  [54] compiler_4.6.0
    ##  [55] beanplot_1.3.1
    ##  [56] rngtools_1.5.2
    ##  [57] bit64_4.8.2
    ##  [58] withr_3.0.2
    ##  [59] S7_0.2.2
    ##  [60] DBI_1.3.0
    ##  [61] HDF5Array_1.41.0
    ##  [62] MASS_7.3-65
    ##  [63] openssl_2.4.1
    ##  [64] rappdirs_0.3.4
    ##  [65] DelayedArray_0.39.3
    ##  [66] rjson_0.2.23
    ##  [67] tools_4.6.0
    ##  [68] rentrez_1.2.4
    ##  [69] quadprog_1.5-8
    ##  [70] glue_1.8.1
    ##  [71] h5mread_1.5.0
    ##  [72] restfulr_0.0.16
    ##  [73] nlme_3.1-169
    ##  [74] rhdf5filters_1.25.0
    ##  [75] grid_4.6.0
    ##  [76] gtable_0.3.6
    ##  [77] tzdb_0.5.0
    ##  [78] preprocessCore_1.75.0
    ##  [79] tidyr_1.3.2
    ##  [80] hms_1.1.4
    ##  [81] data.table_1.18.4
    ##  [82] xml2_1.5.2
    ##  [83] XVector_0.53.0
    ##  [84] BiocVersion_3.24.0
    ##  [85] foreach_1.5.2
    ##  [86] pillar_1.11.1
    ##  [87] limma_3.69.1
    ##  [88] genefilter_1.95.0
    ##  [89] splines_4.6.0
    ##  [90] dplyr_1.2.1
    ##  [91] BiocFileCache_3.3.0
    ##  [92] lattice_0.22-9
    ##  [93] survival_3.8-6
    ##  [94] rtracklayer_1.73.0
    ##  [95] bit_4.6.0
    ##  [96] GEOquery_2.81.0
    ##  [97] annotate_1.91.0
    ##  [98] tidyselect_1.2.1
    ##  [99] locfit_1.5-9.12
    ## [100] maketools_1.3.2
    ## [101] Biostrings_2.81.2
    ## [102] knitr_1.51
    ## [103] reformulas_0.4.4
    ## [104] SummarizedExperiment_1.43.0
    ## [105] xfun_0.57
    ## [106] scrime_1.3.7
    ## [107] Biobase_2.73.1
    ## [108] statmod_1.5.2
    ## [109] matrixStats_1.5.0
    ## [110] yaml_2.3.12
    ## [111] boot_1.3-32
    ## [112] evaluate_1.0.5
    ## [113] codetools_0.2-20
    ## [114] cigarillo_1.3.0
    ## [115] tibble_3.3.1
    ## [116] minfi_1.59.1
    ## [117] BiocManager_1.30.27
    ## [118] cli_3.6.6
    ## [119] bumphunter_1.55.1
    ## [120] xtable_1.8-8
    ## [121] Rdpack_2.6.6
    ## [122] jquerylib_0.1.4
    ## [123] Rcpp_1.1.1-1.1
    ## [124] dbplyr_2.5.2
    ## [125] png_0.1-9
    ## [126] XML_3.99-0.23
    ## [127] parallel_4.6.0
    ## [128] readr_2.2.0
    ## [129] ggplot2_4.0.3
    ## [130] blob_1.3.0
    ## [131] mclust_6.1.2
    ## [132] doRNG_1.8.6.3
    ## [133] sparseMatrixStats_1.25.0
    ## [134] bitops_1.0-9
    ## [135] lme4_2.0-1
    ## [136] lmerTest_3.2-1
    ## [137] illuminaio_0.55.0
    ## [138] scales_1.4.0
    ## [139] purrr_1.2.2
    ## [140] crayon_1.5.3
    ## [141] rlang_1.2.0
    ## [142] KEGGREST_1.53.0