An automated discovery tool for discovering hidden biological and technical links

Quick Start

knowYourCG is a tool for evaluating CpG feature enrichment using Illumina probe IDs. Tthis tool automates the hypothesis testing by asking whether a set of CpGs (indexed by Illumina methylation chip IDs, hence a sparse representation of the methylome) is enriched in certain categories or features. These categories or features can be categorical (e.g., CpGs located at specific tissue-specific transcription factors) or continuous (e.g., the local CpG density of CpGs). Additionally, the set of CpGs to which the test will be applied can be categorical or continuous as well.

The set of CpGs that will be tested for enrichment is called the query set, and the set of CpGs that will be used to determine enrichment is called the database set. A query set, for example, might be the results of a differential methylation analysis or from an epigenome-wide association study. We have taken the time to curate our own database sets from a variety of sources that describe different categorical and continuous features such as technical characterization of the probes, CpGs associated with certain chromatin states, gene association, transcription factor binding sites, CpG density, etc.

Additionally, knowYourCG has support for feature selection and feature engineering, which is currently in development.

The following commands prepares the use of KnowYourCG:

library(sesame)
sesameDataCache()

Our example uses a specific mouse design group as input (PGCMeth, methylated in primoridal germ cells). First get the CG list using the following code:

query <- KYCG_getDBs("MM285.designGroup")[["PGCMeth"]]
head(query)
## [1] "cg36615889_TC11" "cg36646136_BC21" "cg36647910_BC11" "cg36857173_TC21"
## [5] "cg36877289_BC21" "cg36899653_BC21"

Now test the enrichment over database groups. By default, KYCG will select all the categorical groups and overlapping genes (CpGs associated with a gene).

results_pgc <- testEnrichment(query, platform="MM285")
head(results_pgc)

We can visualize the result of this test using the KYCG_plotEnrichAll function:

KYCG_plotEnrichAll(results_pgc)
## Loading required namespace: ggrepel

This plot groups different database sets along the x-axis and plot -log10(FDR) on the y-axis. As expected, the PGCMeth group itself appear on the top of the list. But one can also find histone H3K9me3, chromHMM Het and transcription factor Trim28 binding enriched in this CG group.

4 Testing Scenarios

There are four testing scenarios depending on the type format of the query set and database sets. They are shown with the respective testing scenario in the table below. testEnrichment, testEnrichmentSEA are for Fisher’s exact test and Set Enrichment Analysis respectively.

Four KnowYourCG Testing Scenarios
Continuous.DB Discrete.DB
Continuous Query Correlation-based Set Enrichment Analysis
Discrete Query Set Enrichment Analysis Fisher’s Exact Test

Set Enrichment Analysis

The main work horse function for test enrichment of a categorical query against categorical databases is the testEnrichment function. This function calculates the extent of overlap and apply different statistics for enrichment testing. The testEnrichment() will perform Fisher’s exact test (one-tailed by default, but two-tailed optionally) and report metrics about each of the the loaded database sets.

Choice of universal set: Universal set is the set of all probes for a given platform. It can either be passed in as an argument called universeSet or the platform name can be passed with argument platform. If neither of these are supplied, the universe set will be implied from the probes.

library(SummarizedExperiment)

## prepare a query
df <- rowData(sesameDataGet('MM285.tissueSignature'))
query <- df$Probe_ID[df$branch == "fetal_brain" & df$type == "Hypo"]

results <- testEnrichment(query, "TFBS", platform="MM285")
results %>% dplyr::filter(overlap>10) %>% head
## prepare another query
query <- df$Probe_ID[df$branch == "fetal_liver" & df$type == "Hypo"]
results <- testEnrichment(query, "TFBS", platform="MM285")
results %>% dplyr::filter(overlap>10) %>%
    dplyr::select(dbname, estimate, test, FDR) %>% head

The output of each test contains at least four variables: the estimate (fold enrichment, not the test statistics), p-value, type of test, and whether meta data is included in the tested database set (hasMeta), as well as the name of the database set and the database group. By default, the estimate column is sorted.

It should be noted that the estimate (or test statistic) is test dependent and comparison between p-values should be limited to within the same type of test. For instance, the test statistics for Fisher’s exact test and SEA are log fold change and the test statistic for Spearman’s test is simply the rank order correlation coefficient. For simplicity, we report all of the test types in one data frame.

The nQ and nD columns identify the length of the query set and the database set, respectively. Often, it’s important to examine the extent of overlap between the two sets, so that metric is reported as well in the overlap column.

See Supplemental Vignette for other ways of visualizing enrichment results.

Database Sets

The success of enrichment testing depends critically on the availability of biologically-relevant databases. To reflect the biological meaning of databases and facilitate selective testing, we have organized our database sets into different groups. Each group contains one or multiple databases. Here is how to find the names of pre-built database groups:

KYCG_listDBGroups("MM285")

The KYCG_listDBGroups() function returns a data frame containing information of these databases. The Title column is the accession key one needs for the testEnrichment function. With the accessions, one can either directly use them in the testEnrichment function or explicitly call the KYCG_getDBs() function to retrieve databases themselves. Caching these databases on the local machine is important, for two reasons: it limits the number of requests sent to the Bioconductor server, and secondly it limits the amount of time the user needs to wait when re-downloading database sets. For this reason, one should run sesameDataCache() before loading in any database sets. This will take some time to download all of the database sets but this only needs to be done once per installation. During the analysis the database sets can be identified using these accessions. Sesame also does some guessing when a unique substring is given. For example, the following “MM285.designGroup” retrieves the “KYCG.MM285.designGroup.20210210” database. Let’s look at the database group which we had used as the query (query and database are reciprocal) in our first example:

dbs <- KYCG_getDBs("MM285.design")
## Selected the following database groups:
## 1. KYCG.MM285.designGroup.20210210

In total, 32 datasets have been loaded for this group. We can get the “PGCMeth” as an element of the list:

str(dbs[["PGCMeth"]])
##  chr [1:474] "cg36615889_TC11" "cg36646136_BC21" "cg36647910_BC11" ...
##  - attr(*, "group")= chr "KYCG.MM285.designGroup.20210210"
##  - attr(*, "dbname")= chr "PGCMeth"

On subsequent runs of the KYCG_getDBs() function, the database loading can be faster thanks to the sesameData in-memory caching, if the corresponding database has been loaded.

Query Set(s)

A query set represents probes of interest. It may either be in the form of a character vector where the values correspond to probe IDs or a named numeric vector where the names correspond to probe IDs. The query and database definition is rather arbitrary. One can regard a database as a query and turn a query into a database, like in our first example. In real world scenario, query can come from differential methylation testing, unsupervised clustering, correlation with a phenotypic trait, and many others. For example, we could consider CpGs that show tissue-specific methylation as the query. We are getting the B-cell-specific hypomethylation.

df <- rowData(sesameDataGet('MM285.tissueSignature'))
query <- df$Probe_ID[df$branch == "B_cell"]
head(query)
## [1] "cg32668003_TC11" "cg45118317_TC11" "cg37563895_TC11" "cg46105105_BC11"
## [5] "cg47206675_TC21" "cg38855216_TC21"

This query set represents hypomethylated probes in Mouse B-cells from the MM285 platform. This specific query set has 168 probes.

Gene Enrichment

A special case of set enrichment is to test whether CpGs are associated with specific genes. Automating the enrichment test process only works when the number of database sets is small. This is important when targeting all genes as there are tens of thousands of genes on each platform. By testing only those genes that overlap with the query set, we can greatly reduce the number of tests. For this reason, the gene enrichment analysis is a special case of these enrichment tests. We can perform this analysis using the KYCG_buildGeneDBs() function.

query <- names(sesameData_getProbesByGene("Dnmt3a", "MM285"))
results <- testEnrichment(query, 
    KYCG_buildGeneDBs(query, max_distance=100000, platform="MM285"),
    platform="MM285")
results[,c("dbname","estimate","gene_name","FDR", "nQ", "nD", "overlap")]

Using these sample results, we can plot a volcano plot and lollipop plot.

KYCG_plotLollipop(results, label="gene_name")

As expected, we recover our targeted gene (Dnmt3a).

Get more database Sets

Some commonly used database sets are stored in ExperimentHub/sesameData package. But more database sets can be found here. You can use a convenience function to download these database sets to a local folder.

KYCG_listDBGroups(path = "~/Downloads/KYCG_knowledgebase_EPICv2")
##  [1] "ABCompartment.20220911.gz" "Blacklist.20220304.gz"    
##  [3] "CGI.20220904.gz"           "ChromHMM.20220303.gz"     
##  [5] "CTCFbind.20220911.gz"      "HM.20221013.gz"           
##  [7] "ImprintingDMR.20220818.gz" "MetagenePC.20220911.gz"   
##  [9] "nFlankCG.20220321.gz"      "PMD.20220911.gz"          
## [11] "ProbeType.gz"              "REMCChromHMM.20220911.gz" 
## [13] "rmsk1.20220307.gz"         "rmsk2.20220321.gz"        
## [15] "Tetranuc2.20220321.gz"     "TFBS.20220921.gz"         
## [17] "TFBSrm.20221005.gz"
## load all database files in the folder
dbs <- KYCG_loadDBs("~/Downloads/KYCG_knowledgebase_EPICv2/")
## or one database file
dbs <- KYCG_loadDBs("~/Downloads/KYCG_knowledgebase_EPICv2/hg38/CGI.20220904.gz")

GO/Pathway Enrichment

One can get all the genes associated with a probe set by

df <- rowData(sesameDataGet('MM285.tissueSignature'))
query <- df$Probe_ID[df$branch == "fetal_liver" & df$type == "Hypo"]
genes <- sesameData_getGenesByProbes(query, platform="MM285")
genes
## GRanges object with 168 ranges and 2 metadata columns:
##                         seqnames              ranges strand |   gene_name
##                            <Rle>           <IRanges>  <Rle> | <character>
##   ENSMUSG00000026069.15     chr1   40429570-40465415      + |      Il1rl1
##    ENSMUSG00000101923.1     chr1   43409584-43409963      + |     Gm29041
##   ENSMUSG00000026782.15     chr1   60409619-60481158      + |        Abi2
##   ENSMUSG00000026288.14     chr1   87620312-87720507      + |      Inpp5d
##   ENSMUSG00000026425.15     chr1 131285251-131527352      - |      Srgap2
##                     ...      ...                 ...    ... .         ...
##    ENSMUSG00000031119.4     chrX   52053021-52165252      - |        Gpc4
##   ENSMUSG00000016382.15     chrX   75785654-75875182      - |        Pls3
##    ENSMUSG00000071680.6     chrX 142412755-142412967      + |      Gm7123
##   ENSMUSG00000045180.13     chrX 152609509-152769465      - |     Shroom2
##   ENSMUSG00000031298.15     chrX 160390690-160498070      + |      Adgrg2
##                                    gene_type
##                                  <character>
##   ENSMUSG00000026069.15       protein_coding
##    ENSMUSG00000101923.1 processed_pseudogene
##   ENSMUSG00000026782.15       protein_coding
##   ENSMUSG00000026288.14       protein_coding
##   ENSMUSG00000026425.15       protein_coding
##                     ...                  ...
##    ENSMUSG00000031119.4       protein_coding
##   ENSMUSG00000016382.15       protein_coding
##    ENSMUSG00000071680.6 processed_pseudogene
##   ENSMUSG00000045180.13       protein_coding
##   ENSMUSG00000031298.15       protein_coding
##   -------
##   seqinfo: 22 sequences from an unspecified genome; no seqlengths

Here we demonstrate the use of g:Profiler2 to perform Gene ontology enrichment analysis:

library(gprofiler2)

## use gene name
gostres <- gost(genes$gene_name, organism = "mmusculus")
gostres$result[order(gostres$result$p_value),]
gostplot(gostres)

## use Ensembl gene ID, note we need to remove the version suffix
gene_ids <- sapply(strsplit(names(genes),"\\."), function(x) x[1])
gostres <- gost(gene_ids, organism = "mmusculus")
gostres$result[order(gostres$result$p_value),]
gostplot(gostres)

Set Enrichment Analysis

The query may be a named continuous vector. In that case, either a gene enrichment score will be calculated (if the database is discrete) or a Spearman correlation will be calculated (if the database is continuous as well). The three other cases are shown below using biologically relevant examples.

To display this functionality, let’s load two numeric database sets individually. One is a database set for CpG density and the other is a database set corresponding to the distance of the nearest transcriptional start site (TSS) to each probe.

query <- KYCG_getDBs("KYCG.MM285.designGroup")[["TSS"]]
res <- testEnrichmentSEA(query, "MM285.seqContextN")
res[, c("dbname", "test", "estimate", "FDR", "nQ", "nD", "overlap")]

The estimate here is enrichment score.

NOTE: Negative enrichment score suggests enrichment of the categorical database with the higher values (in the numerical database). Positive enrichment score represent enrichment with the smaller values. As expected, the designed TSS CpGs are significantly enriched in smaller TSS distance and higher CpG density.

One can plot the set enrichment analysis result by prepPlot=TRUE command followed by calling the KYCG_plotSetEnrichment() function.

query <- KYCG_getDBs("KYCG.MM285.designGroup")[["TSS"]]
db <- KYCG_getDBs("MM285.seqContextN", "distToTSS")
res <- testEnrichmentSEA(query, db, prepPlot = TRUE)
KYCG_plotSetEnrichment(res[[1]])

Alternatively one can test the enrichment of a continuous query with discrete databases. Here we will use the methylation level from a sample as the query and test it against the chromHMM chromatin states.

beta_values <- getBetas(sesameDataGet("MM285.1.SigDF"))
res <- testEnrichmentSEA(beta_values, "MM285.chromHMM")
res[, c("dbname", "test", "estimate", "FDR", "nQ", "nD", "overlap")]

As expected, chromatin states Tss, Enh has negative enrichment score, meaning these databases are associated with small values of the query (DNA methylation level). On the contrary, Quies states are associated with high methylation level.

Correlation Network Analysis

Methylation Correlation Network Analysis is motivated by gene expression methods that seek to identify modules and networks from expression data. Genes that are co-expressed behave similarly over different environments and thus may share similar biological function and participate in functional networks. Similarly, CpGs that have highly correlated methylation fractions across different environments may be involved in common epigenetic and biological pathways.

Two highly correlated CpGs belonging to the same module. Across 256 samples from different tissues, ages and sexes, methylation changes at one CpG is accompanied by similar changes in a separate co-methylated CpG

Session Info

sessionInfo()
## R version 4.3.1 (2023-06-16)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 22.04.3 LTS
## 
## Matrix products: default
## BLAS:   /home/biocbuild/bbs-3.18-bioc/R/lib/libRblas.so 
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_GB              LC_COLLATE=C              
##  [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: America/New_York
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] ggplot2_3.4.4               tibble_3.2.1               
##  [3] SummarizedExperiment_1.32.0 Biobase_2.62.0             
##  [5] MatrixGenerics_1.14.0       matrixStats_1.0.0          
##  [7] knitr_1.44                  S4Vectors_0.40.0           
##  [9] IRanges_2.36.0              GenomicRanges_1.54.0       
## [11] sesame_1.20.0               sesameData_1.19.0          
## [13] ExperimentHub_2.10.0        AnnotationHub_3.10.0       
## [15] BiocFileCache_2.10.0        dbplyr_2.3.4               
## [17] BiocGenerics_0.48.0        
## 
## loaded via a namespace (and not attached):
##  [1] DBI_1.1.3                     bitops_1.0-7                 
##  [3] rlang_1.1.1                   magrittr_2.0.3               
##  [5] compiler_4.3.1                RSQLite_2.3.1                
##  [7] png_0.1-8                     vctrs_0.6.4                  
##  [9] reshape2_1.4.4                stringr_1.5.0                
## [11] pkgconfig_2.0.3               crayon_1.5.2                 
## [13] fastmap_1.1.1                 XVector_0.42.0               
## [15] ellipsis_0.3.2                labeling_0.4.3               
## [17] utf8_1.2.4                    promises_1.2.1               
## [19] rmarkdown_2.25                tzdb_0.4.0                   
## [21] preprocessCore_1.64.0         purrr_1.0.2                  
## [23] bit_4.0.5                     xfun_0.40                    
## [25] zlibbioc_1.48.0               cachem_1.0.8                 
## [27] GenomeInfoDb_1.38.0           jsonlite_1.8.7               
## [29] blob_1.2.4                    later_1.3.1                  
## [31] DelayedArray_0.28.0           BiocParallel_1.36.0          
## [33] interactiveDisplayBase_1.40.0 parallel_4.3.1               
## [35] R6_2.5.1                      bslib_0.5.1                  
## [37] stringi_1.7.12                RColorBrewer_1.1-3           
## [39] jquerylib_0.1.4               Rcpp_1.0.11                  
## [41] wheatmap_0.2.0                readr_2.1.4                  
## [43] httpuv_1.6.12                 Matrix_1.6-1.1               
## [45] tidyselect_1.2.0              abind_1.4-5                  
## [47] yaml_2.3.7                    codetools_0.2-19             
## [49] curl_5.1.0                    lattice_0.22-5               
## [51] plyr_1.8.9                    shiny_1.7.5.1                
## [53] withr_2.5.1                   KEGGREST_1.42.0              
## [55] evaluate_0.22                 Biostrings_2.70.0            
## [57] pillar_1.9.0                  BiocManager_1.30.22          
## [59] filelock_1.0.2                stats4_4.3.1                 
## [61] generics_0.1.3                RCurl_1.98-1.12              
## [63] BiocVersion_3.18.0            hms_1.1.3                    
## [65] munsell_0.5.0                 scales_1.2.1                 
## [67] xtable_1.8-4                  glue_1.6.2                   
## [69] tools_4.3.1                   grid_4.3.1                   
## [71] AnnotationDbi_1.64.0          colorspace_2.1-0             
## [73] GenomeInfoDbData_1.2.11       cli_3.6.1                    
## [75] rappdirs_0.3.3                fansi_1.0.5                  
## [77] S4Arrays_1.2.0                dplyr_1.1.3                  
## [79] gtable_0.3.4                  sass_0.4.7                   
## [81] digest_0.6.33                 SparseArray_1.2.0            
## [83] ggrepel_0.9.4                 farver_2.1.1                 
## [85] memoise_2.0.1                 htmltools_0.5.6.1            
## [87] lifecycle_1.0.3               httr_1.4.7                   
## [89] mime_0.12                     bit64_4.0.5