This vignette provides an extended benchmark comparing CMEnt against multiple popular DMR detection methods on a simulated microarray dataset with known ground truth. The methods compared include:
The dataset used is a simulated dataset, produced by running simulateDMRs, with default parameters, on chr21 and chr22 subset of preprocessed beta values from 656 450K illumina microarray healthy blood samples found in GEO with identifier GSE40279, with 500 simulated DMRs of varying widths and effect sizes. The simulated dataset is available for download from Zenodo (see code below).
try(devtools::load_all(), silent = TRUE)
suppressPackageStartupMessages({
try(library(CMEnt), silent = TRUE)
library(minfi)
library(data.table)
library(ggplot2)
library(dplyr)
library(GenomicRanges)
})
getBenchmarkFile <- function(filename) {
benchmark_file <- ""
folder <- "simulated_microarray450k"
if (getwd() == "notebooks") {
benchmark_file <- file.path("cached_results", folder, filename)
} else if ("NAMESPACE" %in% dir()) {
benchmark_file <- file.path("notebooks", "cached_results", folder, filename)
} else {
benchmark_file <- file.path("cached_results", folder, filename)
}
dir.create(dirname(benchmark_file), showWarnings = FALSE, recursive = TRUE)
benchmark_file
}
get_method_cache_file <- function(method) {
getBenchmarkFile(sprintf("dmrs.%s.rds", method))
}
r <- getOption("repos")
if (is.null(r) || is.na(r["CRAN"]) || r["CRAN"] %in% c("@CRAN@", "")) {
options(repos = c(CRAN = "https://cloud.r-project.org"))
}
if (!requireNamespace("BiocManager", quietly = TRUE)) {
install.packages("BiocManager")
}
if (!requireNamespace("DMRcate", quietly = TRUE)) {
BiocManager::install("DMRcate", update = FALSE, ask = FALSE)
}
if (!requireNamespace("bumphunter", quietly = TRUE)) {
BiocManager::install("bumphunter", update = FALSE, ask = FALSE)
}
if (!requireNamespace("doParallel", quietly = TRUE)) {
install.packages("doParallel")
}
if (!requireNamespace("tidyr", quietly = TRUE)) {
install.packages("tidyr")
}
if (!requireNamespace("ChAMP", quietly = TRUE)) {
BiocManager::install("ChAMP", update = FALSE, ask = FALSE)
}
Note: comb-p requires both the Python tool itself (combined-pvalues) and bedtools, installing using new conda env if not present:
{ command -v comb-p >/dev/null 2>&1 && command -v bedtools >/dev/null 2>&1; } || {
echo >&2 "comb-p or bedtools not found. Installing with conda...";
conda create -n cment_microarray_exp python=3.9
conda activate cment_microarray_exp
conda install -yc bioconda combined-pvalues bedtools
}
sim_beta_path <- getBenchmarkFile("GSE40279_sim_beta_values.tsv.gz")
sim_truth_path <- getBenchmarkFile("GSE40279_sim_dmrs.tsv")
sim_pheno_path <- getBenchmarkFile("GSE40279_sim_samplesheet.tsv")
array_type <- "450K"
genome <- "hg19"
zenodo_base_url <- "https://zenodo.org/records/20592944/files/"
if (!file.exists(sim_beta_path)) {
curl::curl_download(
url = paste0(zenodo_base_url, basename(sim_beta_path), "?download=1"),
destfile = sim_beta_path
)
}
if (!file.exists(sim_truth_path)) {
curl::curl_download(
url = paste0(zenodo_base_url, basename(sim_truth_path), "?download=1"),
destfile = sim_truth_path
)
}
if (!file.exists(sim_pheno_path)) {
curl::curl_download(
url = paste0(zenodo_base_url, basename(sim_pheno_path), "?download=1"),
destfile = sim_pheno_path
)
}
truth_df <- data.table::fread(sim_truth_path)
truth_dmrs <- makeGRangesFromDataFrame(
truth_df,
seqnames.field = "seqnames",
start.field = "start",
end.field = "end",
keep.extra.columns = TRUE
)
beta_handler <- CMEnt::getBetaHandler(sim_beta_path, array = array_type, genome = genome)
sample_ids <- beta_handler$getBetaColNames()
pheno <- data.table::fread(sim_pheno_path, data.table = FALSE)
rownames(pheno) <- pheno$V1
if (!all(sample_ids %in% rownames(pheno))) {
stop("The simulated phenotype file does not contain all simulated beta samples.")
}
pheno <- pheno[sample_ids, , drop = FALSE]
pheno$Sample_Group <- factor(pheno$Sample_Group, levels = c("control", "case"))
beta_mat <- as.matrix(beta_handler$getBeta())
locs <- beta_handler$getBetaLocs()
truth_probe_counts <- countOverlaps(truth_dmrs, makeGRangesFromDataFrame(
as.data.frame(locs),
seqnames.field = "chr",
start.field = "start",
end.field = "end",
keep.extra.columns = TRUE
))
truth_overlap_n <- sum(truth_probe_counts > 0L)
if (truth_overlap_n < 0.9 * length(truth_dmrs)) {
stop(
sprintf(
paste(
"Only %d/%d truth DMRs overlap probe loci under genome='%s'.",
"This simulated microarray truth set was generated on hg19;",
"delete stale cache files and rerun with genome='hg19'."
),
truth_overlap_n,
length(truth_dmrs),
genome
)
)
}
logit2 <- function(x) log2(x) - log2(1 - x)
mvalues <- logit2(beta_mat)
pheno$casecontrol <- pheno$Sample_Group == "case"
dmps <- CMEnt::findDMPsArray(beta_handler,
pheno,
sample_group_col = "Sample_Group",
id_col = "V1"
)
rownames(dmps) <- dmps$site_id
# sort DMPs by location
dmps <- dmps[!is.na(dmps$pval), ]
dmp_locs <- locs[rownames(locs) %in% rownames(dmps), , drop = FALSE]
dmps <- dmps[rownames(dmp_locs), , drop = FALSE]
case_idx <- which(pheno$casecontrol)
ctrl_idx <- which(!pheno$casecontrol)
dmps$delta_beta <- rowMeans(beta_mat[rownames(dmps), case_idx, drop = FALSE]) -
rowMeans(beta_mat[rownames(dmps), ctrl_idx, drop = FALSE])
dmps$qval <- stats::p.adjust(dmps$pval, method = "fdr")
dmps_gr <- makeGRangesFromDataFrame(
data.frame(
chr = dmp_locs$chr,
start = dmp_locs$start,
end = dmp_locs$end,
id = rownames(dmps),
pval = dmps$pval,
qval = dmps$qval,
delta_beta = dmps$delta_beta
),
keep.extra.columns = TRUE
)
dmps_gr <- sort(dmps_gr)
names(dmps_gr) <- rownames(dmps)
seeds_gr <- dmps_gr[mcols(dmps_gr)$qval < 0.05]
seeds <- mcols(seeds_gr)$id
cat(sprintf("Found %d significant DMPs to be used as seeds (FDR < 0.05)\n", length(seeds)))
Found 4827 significant DMPs to be used as seeds (FDR < 0.05)
cment_params <- list(
sample_group_col = "Sample_Group",
casecontrol_col = "casecontrol",
max_bridge_seeds_gaps = 0,
max_bridge_extension_gaps = 1,
min_seeds = 2,
min_sites = 3,
ext_site_delta_beta = 0.1,
max_lookup_dist = 500,
max_pval = 0.05,
testing_mode = "parametric",
entanglement = "weak"
)
cment_njobs <- max(1L, min(8L, parallel::detectCores(logical = TRUE) - 1L))
options("CMEnt.njobs" = cment_njobs)
# only chr21
truth_dmrs_21 <- truth_dmrs[seqnames(truth_dmrs) == "chr21", ]
seeds_gr_21 <- subset(seeds_gr, seqnames(seeds_gr) == "chr21")
seeds_21 <- mcols(seeds_gr_21)$id
seeds_beta_21 <- beta_mat[mcols(seeds_gr_21)$id, , drop = FALSE]
gt_seeds_gr_21 <- subsetByOverlaps(seeds_gr_21, truth_dmrs_21, ignore.strand = TRUE)
seeds_dmrs_cment <- do.call(CMEnt::buildDMRs, c(cment_params, list(
beta = seeds_beta_21,
seeds = seeds_21,
pheno = pheno,
verbose = 2,
.score_dmrs = FALSE,
extract_motifs = FALSE,
annotate_with_genes = FALSE)))
seeds_in_truth <- subsetByOverlaps(gt_seeds_gr_21, truth_dmrs_21, ignore.strand = TRUE)
seeds_in_dmrs <- subsetByOverlaps(gt_seeds_gr_21, seeds_dmrs_cment, ignore.strand = TRUE)
seeds_in_truth_and_dmrs <- subsetByOverlaps(seeds_in_truth, seeds_dmrs_cment, ignore.strand = TRUE)
seeds_in_truth_not_dmrs <- subsetByOverlaps(seeds_in_truth, seeds_dmrs_cment, ignore.strand = TRUE, invert = TRUE)
seed_truth_ov <- findOverlaps(seeds_in_truth, truth_dmrs_21, ignore.strand = TRUE)
truth_seed_total <- tabulate(subjectHits(seed_truth_ov), nbins = length(truth_dmrs_21))
seed_truth_hit_ov <- findOverlaps(seeds_in_truth_and_dmrs, truth_dmrs_21, ignore.strand = TRUE)
truth_seed_captured <- tabulate(subjectHits(seed_truth_hit_ov), nbins = length(truth_dmrs_21))
truth_seed_capture_pct <- ifelse(truth_seed_total > 0, round(100 * truth_seed_captured / truth_seed_total, 1), NA_real_)
recoverable_truth <- truth_seed_total >= 2L
recoverable_with2captured <- recoverable_truth & truth_seed_captured >= 2L
recoverable_fully_captured <- recoverable_truth & truth_seed_captured == truth_seed_total
stage1_quality <- data.frame(
Truth_DMRs_Total = length(truth_dmrs_21),
Truth_DMRs_With_Seeds = sum(truth_seed_total >= 1L),
Truth_DMRs_2plusSeeds = sum(recoverable_truth),
Truth_DMRs_With2plusCapturedSeeds = sum(recoverable_with2captured),
Truth_DMRs_Stage1Recoverable_FullyCapturedSeeds = sum(recoverable_fully_captured),
Seed_Capture_Rate_Pct_in_Truth = ifelse(length(seeds_in_truth) == 0, NA_real_, round(100 * length(seeds_in_truth_and_dmrs) / length(seeds_in_truth), 1)),
Median_Seed_Capture_Pct_Per_Truth = ifelse(sum(truth_seed_total > 0) == 0, NA_real_, stats::median(truth_seed_capture_pct[truth_seed_total > 0], na.rm = TRUE))
)
for (col in colnames(stage1_quality)) {
cat(sprintf("- **%s**: %s\n", col, stage1_quality[[col]]))
}
seeds_beta_21_handler <- CMEnt::getBetaHandler(
beta = seeds_beta_21,
genome = genome,
array = array_type
)
group_inds <- split(seq_len(nrow(pheno)), pheno$Sample_Group)
testing_mode_per_group <- rep("parametric", length(group_inds))
names(testing_mode_per_group) <- names(group_inds)
empirical_strategy_per_group <- rep("permutation", length(group_inds))
names(empirical_strategy_per_group) <- names(group_inds)
connectivity_array_21 <- CMEnt:::.buildConnectivityArray(
beta_handler = seeds_beta_21_handler,
pheno = pheno,
group_inds = group_inds,
testing_mode_per_group = testing_mode_per_group,
empirical_strategy_per_group = empirical_strategy_per_group,
col_names = NULL,
max_pval = cment_params$max_pval,
ext_site_delta_beta = NA_real_,
max_lookup_dist = cment_params$max_lookup_dist,
entanglement = cment_params$entanglement,
njobs = cment_njobs,
max_bridge_gaps = 1,
verbose = 3
)$connectivity_array
connectivity_array_21_no_gaps <- CMEnt:::.buildConnectivityArray(
beta_handler = seeds_beta_21_handler,
pheno = pheno,
group_inds = group_inds,
testing_mode_per_group = testing_mode_per_group,
empirical_strategy_per_group = empirical_strategy_per_group,
col_names = NULL,
max_pval = cment_params$max_pval,
ext_site_delta_beta = NA_real_,
max_lookup_dist = cment_params$max_lookup_dist,
entanglement = cment_params$entanglement,
njobs = cment_njobs,
max_bridge_gaps = 0,
verbose = 2
)$connectivity_array
truth_hits <- findOverlaps(gt_seeds_gr_21, truth_dmrs_21, ignore.strand = TRUE)
truth_id <- rep(NA_integer_, length(gt_seeds_gr_21))
truth_id[queryHits(truth_hits)] <- subjectHits(truth_hits)
gt_vec <- rep(FALSE, length(gt_seeds_gr_21))
if (length(gt_vec) > 1L) {
gt_vec[-length(gt_vec)] <- !is.na(truth_id[-length(truth_id)]) &
truth_id[-length(truth_id)] == truth_id[-1]
}
gt_connectivity <- data.frame(gt = gt_vec)
colnames(connectivity_array_21_no_gaps) <- paste(colnames(connectivity_array_21_no_gaps), "ngaps", sep = "_")
connectivity_array_21 <- cbind(gt_connectivity, connectivity_array_21, connectivity_array_21_no_gaps)
x <- rle(connectivity_array_21$connected); sum(x$values)
seeds_in_truth <- subsetByOverlaps(gt_seeds_gr_21, truth_dmrs_21, ignore.strand = TRUE)
seeds_in_dmrs <- subsetByOverlaps(gt_seeds_gr_21, seeds_dmrs_cment, ignore.strand = TRUE)
seeds_in_truth_and_dmrs <- subsetByOverlaps(seeds_in_truth, seeds_dmrs_cment, ignore.strand = TRUE)
seeds_in_truth_not_dmrs <- subsetByOverlaps(seeds_in_truth, seeds_dmrs_cment, ignore.strand = TRUE, invert = TRUE)
seed_truth_ov <- findOverlaps(seeds_in_truth, truth_dmrs_21, ignore.strand = TRUE)
truth_seed_total <- tabulate(subjectHits(seed_truth_ov), nbins = length(truth_dmrs_21))
seed_truth_hit_ov <- findOverlaps(seeds_in_truth_and_dmrs, truth_dmrs_21, ignore.strand = TRUE)
truth_seed_captured <- tabulate(subjectHits(seed_truth_hit_ov), nbins = length(truth_dmrs_21))
truth_seed_capture_pct <- ifelse(truth_seed_total > 0, round(100 * truth_seed_captured / truth_seed_total, 1), NA_real_)
recoverable_truth <- truth_seed_total >= 2L
recoverable_with2captured <- recoverable_truth & truth_seed_captured >= 2L
recoverable_fully_captured <- recoverable_truth & truth_seed_captured == truth_seed_total
stage1_quality <- data.frame(
Truth_DMRs_Total = length(truth_dmrs_21),
Truth_DMRs_With_Seeds = sum(truth_seed_total >= 1L),
Truth_DMRs_2plusSeeds = sum(recoverable_truth),
Truth_DMRs_With2plusCapturedSeeds = sum(recoverable_with2captured),
Truth_DMRs_Stage1Recoverable_FullyCapturedSeeds = sum(recoverable_fully_captured),
Seed_Capture_Rate_Pct_in_Truth = ifelse(length(seeds_in_truth) == 0, NA_real_, round(100 * length(seeds_in_truth_and_dmrs) / length(seeds_in_truth), 1)),
Median_Seed_Capture_Pct_Per_Truth = ifelse(sum(truth_seed_total > 0) == 0, NA_real_, stats::median(truth_seed_capture_pct[truth_seed_total > 0], na.rm = TRUE))
)
for (col in colnames(stage1_quality)) {
cat(sprintf("- **%s**: %s\n", col, stage1_quality[[col]]))
}
cment_file <- get_method_cache_file("CMEnt")
cment_cache_valid <- FALSE
if (file.exists(cment_file)) {
ret <- readRDS(cment_file)
cment_cache_valid <- identical(ret$params, cment_params)
if (cment_cache_valid) {
dmrs_cment <- ret$dmrs
time_cment <- ret$time
}
}
if (!cment_cache_valid) {
cment_njobs <- max(1L, min(8L, parallel::detectCores(logical = TRUE) - 1L))
options("CMEnt.njobs" = cment_njobs)
cat(sprintf("CMEnt parallel jobs: %d\n", cment_njobs))
start_time <- Sys.time()
dmrs_cment <- do.call(CMEnt::buildDMRs,
c(list(
beta = beta_handler,
seeds = seeds,
pheno = pheno,
verbose = 2,
.score_dmrs = FALSE,
extract_motifs = FALSE,
annotate_with_genes = FALSE
), cment_params)
)
end_time <- Sys.time()
time_cment <- end_time - start_time
saveRDS(list(dmrs = dmrs_cment, time = time_cment, params = cment_params), cment_file)
}
cat(sprintf("CMEnt: Found %d DMRs in %d seconds\n", length(dmrs_cment), round(as.numeric(time_cment, units = "secs"))))
CMEnt: Found 539 DMRs in 101 seconds
combp_file <- get_method_cache_file("combp")
run_combp <- function(dmps_gr, seed = 500, dist = 500) {
if (Sys.which("bedtools") == "") {
warning("bedtools not found in PATH. comb-p requires bedtools to be installed.")
warning("Install with: conda install -yc bioconda bedtools")
return(GRanges())
}
if (Sys.which("comb-p") == "") {
warning("comb-p not found in PATH.")
warning("Install with: conda install -yc bioconda combined-pvalues")
return(GRanges())
}
tmpdir <- tempdir()
bed_file <- file.path(tmpdir, "dmps_for_combp.bed")
bed_df <- data.frame(
chrom = as.character(seqnames(dmps_gr)),
start = start(dmps_gr) - 1,
end = end(dmps_gr),
pval = mcols(dmps_gr)$pval
)
bed_df <- bed_df[order(bed_df$chrom, bed_df$start), ]
write.table(bed_df, bed_file,
quote = FALSE, sep = "\t",
row.names = FALSE, col.names = TRUE
)
cmd <- sprintf(
"comb-p pipeline -c 4 --seed %d --dist %d -p %s --region-filter-p 0.01 --region-filter-n 3 %s",
seed, dist, file.path(tmpdir, "output."), bed_file
)
tryCatch(
{
exit_code <- system(cmd, ignore.stdout = FALSE, ignore.stderr = FALSE)
if (exit_code != 0) {
warning(sprintf("comb-p exited with code %d", exit_code))
return(GRanges())
}
regions_file <- paste0(file.path(tmpdir, "output."), "regions-p.bed.gz")
if (file.exists(regions_file)) {
regions <- fread(cmd = sprintf("zcat %s", regions_file))
colnames(regions) <- c("chr", "start", "end", "min_p", "n_probes", "z_p", "z_sidak_p")
regions <- regions[regions$z_sidak_p < 0.05 & regions$n_probes >= 3, ]
dmrs_combp <- makeGRangesFromDataFrame(regions,
seqnames.field = "chr",
start.field = "start",
end.field = "end",
keep.extra.columns = TRUE
)
return(dmrs_combp)
} else {
warning("comb-p did not produce output file")
return(GRanges())
}
},
error = function(e) {
warning(sprintf("comb-p failed: %s", e$message))
GRanges()
}
)
}
if (!file.exists(combp_file)) {
start_time <- Sys.time()
dmrs_combp <- run_combp(dmps_gr)
end_time <- Sys.time()
time_combp <- end_time - start_time
saveRDS(list(dmrs = dmrs_combp, time = time_combp), combp_file)
} else {
ret <- readRDS(combp_file)
dmrs_combp <- ret$dmrs
time_combp <- ret$time
}
cat(sprintf("comb-p: Found %d DMRs in %d seconds\n", length(dmrs_combp), round(as.numeric(time_combp, units = "secs"))))
comb-p: Found 503 DMRs in 8 seconds
probelasso_file <- get_method_cache_file("probeLasso")
run_probelasso_champ <- function(beta_mat, pheno, array_type = "450K", genome = "hg19") {
if (!requireNamespace("ChAMP", quietly = TRUE)) {
warning("ChAMP not installed. probeLasso requires ChAMP.")
warning("Install with: BiocManager::install('ChAMP')")
return(GRanges(seqinfo = GenomeInfoDb::Seqinfo(genome = genome)))
}
library(ChAMP)
tryCatch(
{
result <- champ.DMR(
beta = beta_mat,
pheno = pheno$Sample_Group,
arraytype = array_type,
method = "ProbeLasso",
minProbes = cment_params$min_sites,
adjPvalDmr = 0.05,
adjPvalProbe = 0.1,
meanLassoRadius = 500,
minDmrSep = 500,
minDmrSize = 50,
PDFplot = FALSE,
Rplot = FALSE,
resultsDir = file.path(tempdir(), "champ_probelasso")
)
pl_dmr <- result$ProbeLassoDMR
if (is.null(pl_dmr) || nrow(pl_dmr) == 0) {
return(GRanges())
}
# ChAMP output column names can differ by version.
if (!"seqnames" %in% names(pl_dmr) && "dmrChrom" %in% names(pl_dmr)) {
pl_dmr$seqnames <- pl_dmr$dmrChrom
}
if (!"start" %in% names(pl_dmr) && "dmrStart" %in% names(pl_dmr)) {
pl_dmr$start <- pl_dmr$dmrStart
}
if (!"end" %in% names(pl_dmr) && "dmrEnd" %in% names(pl_dmr)) {
pl_dmr$end <- pl_dmr$dmrEnd
}
return(makeGRangesFromDataFrame(pl_dmr,
seqnames.field = "seqnames",
start.field = "start",
end.field = "end",
seqinfo = GenomeInfoDb::Seqinfo(genome = genome),
keep.extra.columns = TRUE
))
},
error = function(e) {
warning(sprintf("probeLasso (ChAMP) failed: %s", e$message))
GRanges(seqinfo = GenomeInfoDb::Seqinfo(genome = genome))
}
)
}
if (!file.exists(probelasso_file)) {
start_time <- Sys.time()
dmrs_probelasso <- run_probelasso_champ(beta_mat, pheno, array_type = "450K", genome = genome)
end_time <- Sys.time()
time_probelasso <- end_time - start_time
saveRDS(list(dmrs = dmrs_probelasso, time = time_probelasso), probelasso_file)
} else {
ret <- readRDS(probelasso_file)
dmrs_probelasso <- ret$dmrs
time_probelasso <- ret$time
}
cat(sprintf("probeLasso: Found %d DMRs in %d seconds\n", length(dmrs_probelasso), round(as.numeric(time_probelasso, units = "secs"))))
probeLasso: Found 289 DMRs in 18 seconds
dmrcate_file <- get_method_cache_file("dmrcate")
if (!file.exists(dmrcate_file)) {
library(DMRcate)
start_time <- Sys.time()
formula_str <- "~Sample_Group"
design <- model.matrix(stats::as.formula(formula_str), data = pheno)
coef_col <- which(colnames(design) == "Sample_Groupcase")
colnames(design)[1] <- "(Intercept)"
colnames(design)[coef_col] <- "case_vs_control"
myannotation <- cpg.annotate("array", mvalues,
what = "M", arraytype = "450K",
analysis.type = "differential", design = design,
coef = coef_col, fdr = 0.05
)
dmrcate_results <- dmrcate(myannotation, C = 2, lambda = 1000, min.cpgs = 3, betacutoff = 0.05)
dmrs_dmrcate <- extractRanges(dmrcate_results, genome = genome)
end_time <- Sys.time()
time_dmrcate <- end_time - start_time
saveRDS(list(dmrs = dmrs_dmrcate, time = time_dmrcate), dmrcate_file)
} else {
ret <- readRDS(dmrcate_file)
dmrs_dmrcate <- ret$dmrs
time_dmrcate <- ret$time
}
cat(sprintf("DMRcate: Found %d DMRs in %d seconds\n", length(dmrs_dmrcate), round(as.numeric(time_dmrcate, units = "secs"))))
DMRcate: Found 482 DMRs in 7 seconds
bumphunter_file <- get_method_cache_file("bumphunter")
if (!file.exists(bumphunter_file)) {
suppressPackageStartupMessages({
library(bumphunter)
library(doParallel)
})
start_time <- Sys.time()
bh_cores <- max(1L, min(8L, parallel::detectCores(logical = TRUE) - 1L))
bh_b <- 100L
bh_max_dmrs <- 100000L
bh_max_gap <- 2000L
bh_min_l <- 2L
bh_region_p <- 0.05
formula_str <- "~Sample_Group"
design <- model.matrix(stats::as.formula(formula_str), data = pheno)
coef_col <- which(colnames(design) == "Sample_Groupcase")
colnames(design)[1] <- "(Intercept)"
colnames(design)[coef_col] <- "case_vs_control"
doParallel::registerDoParallel(cores = bh_cores)
on.exit({
try(doParallel::stopImplicitCluster(), silent = TRUE)
gc()
}, add = TRUE)
bh_cluster <- bumphunter::clusterMaker(
chr = as.character(locs$chr),
pos = locs$start,
maxGap = bh_max_gap
)
run_bumphunter_fit <- function(cutoff, B) {
bumphunter(beta_mat,
design = design,
chr = as.character(locs$chr),
pos = locs$start,
cluster = bh_cluster,
cutoff = cutoff,
maxGap = max_gap,
nullMethod = "bootstrap",
B = B,
type = "Beta",
coef = coef_col
)
}
has_informative_bumphunter_null <- function(fit) {
null_values <- unlist(fit$null$value, use.names = FALSE)
null_lengths <- unlist(fit$null$length, use.names = FALSE)
length(null_values) > 0 &&
any(abs(null_values) > 0) &&
length(null_lengths) > 0 &&
any(null_lengths > 0)
}
max_gap <- bh_max_gap
dmrs_cutoff <- 0.1
dmrs_cnt <- Inf
iter <- 0L
max_iter <- 10L
while (dmrs_cnt > bh_max_dmrs && iter < max_iter) {
iter <- iter + 1L
dmrs <- run_bumphunter_fit(cutoff = dmrs_cutoff, B = 0)
dmrs_cnt <- nrow(dmrs$table)
dmrs_cutoff <- dmrs_cutoff * 1.2
rm(dmrs)
gc()
}
final_cutoff <- dmrs_cutoff / 1.2
cat(sprintf(
"Bumphunter: cutoff=%.4f, candidate_bumps=%d, minL=%d, maxGap=%d, cores=%d, B=%d\n",
final_cutoff, dmrs_cnt, bh_min_l, max_gap, bh_cores, bh_b
))
bumphunter_results <- run_bumphunter_fit(cutoff = final_cutoff, B = bh_b)
bh_cutoff_floor <- 0.005
bh_bootstrap_adjust_iter <- 0L
bh_max_bootstrap_adjust <- 6L
while (
nrow(bumphunter_results$table) > 0 &&
(all(is.na(bumphunter_results$table$p.value)) ||
!has_informative_bumphunter_null(bumphunter_results)) &&
final_cutoff > bh_cutoff_floor &&
bh_bootstrap_adjust_iter < bh_max_bootstrap_adjust
) {
prev_cutoff <- final_cutoff
final_cutoff <- max(final_cutoff / 2, bh_cutoff_floor)
if (identical(final_cutoff, prev_cutoff)) {
break
}
bh_bootstrap_adjust_iter <- bh_bootstrap_adjust_iter + 1L
cat(sprintf(
"Bumphunter: bootstrap null degenerate at cutoff=%.4f; retrying with cutoff=%.4f\n",
prev_cutoff, final_cutoff
))
bumphunter_results <- run_bumphunter_fit(cutoff = final_cutoff, B = bh_b)
}
dmr_table <- bumphunter_results$table
# bumphunter already estimates region-level p-values from the bootstrap null.
dmr_table <- dmr_table[dmr_table$L >= bh_min_l & dmr_table$p.value <= bh_region_p, ]
dmrs_bumphunter <- makeGRangesFromDataFrame(dmr_table,
seqnames.field = "chr",
start.field = "start",
end.field = "end",
keep.extra.columns = TRUE
)
end_time <- Sys.time()
time_bumphunter <- end_time - start_time
saveRDS(list(dmrs = dmrs_bumphunter, time = time_bumphunter), bumphunter_file)
} else {
ret <- readRDS(bumphunter_file)
dmrs_bumphunter <- ret$dmrs
time_bumphunter <- ret$time
}
cat(sprintf("Bumphunter: Found %d DMRs in %d seconds\n", length(dmrs_bumphunter), round(as.numeric(time_bumphunter, units = "secs"))))
Bumphunter: Found 494 DMRs in 76 seconds
get_dmr_stats <- function(dmrs, method) {
if (length(dmrs) == 0) {
return(data.frame(
Method = method,
`Number of DMRs` = 0,
`Mean DMR Width` = NA,
`Median DMR Width` = NA,
`Total Coverage bp` = 0,
`Min Width` = NA,
`Max Width` = NA
))
}
data.frame(
Method = method,
`Number of DMRs` = length(dmrs),
`Mean DMR Width` = round(mean(width(dmrs)), 1),
`Median DMR Width` = median(width(dmrs)),
`Total Coverage bp` = sum(width(dmrs)),
`Min Width` = min(width(dmrs)),
`Max Width` = max(width(dmrs))
)
}
stats_list <- list(
get_dmr_stats(dmrs_cment, "CMEnt"),
get_dmr_stats(dmrs_combp, "comb-p"),
get_dmr_stats(dmrs_probelasso, "probeLasso"),
get_dmr_stats(dmrs_dmrcate, "DMRcate"),
get_dmr_stats(dmrs_bumphunter, "Bumphunter")
)
results_comparison <- do.call(rbind, stats_list)
knitr::kable(results_comparison, digits = 2, caption = "Basic DMR Statistics Across Methods")
| Method | Number.of.DMRs | Mean.DMR.Width | Median.DMR.Width | Total.Coverage.bp | Min.Width | Max.Width |
|---|---|---|---|---|---|---|
| CMEnt | 539 | 943.6 | 894.0 | 508603 | 37 | 2907 |
| comb-p | 503 | 992.7 | 927.0 | 499318 | 45 | 2909 |
| probeLasso | 289 | 1023.1 | 718.0 | 295668 | 53 | 6998 |
| DMRcate | 482 | 1539.1 | 1430.5 | 741850 | 43 | 7249 |
| Bumphunter | 494 | 916.9 | 828.5 | 452968 | 43 | 5578 |
methods_list <- list(
CMEnt = dmrs_cment,
`comb-p` = dmrs_combp,
probeLasso = dmrs_probelasso,
DMRcate = dmrs_dmrcate,
Bumphunter = dmrs_bumphunter
)
methods_list <- methods_list[sapply(methods_list, length) > 0]
method_order <- names(methods_list)
calculate_overlap <- function(gr1, gr2) {
if (length(gr1) == 0 || length(gr2) == 0) {
return(list(count = 0, percentage = 0))
}
overlap_count <- length(subsetByOverlaps(gr1, gr2))
pct <- (overlap_count / length(gr1)) * 100
list(count = overlap_count, percentage = pct)
}
overlap_matrix <- matrix(0, nrow = length(methods_list), ncol = length(methods_list))
pct_matrix <- matrix(0, nrow = length(methods_list), ncol = length(methods_list))
rownames(overlap_matrix) <- colnames(overlap_matrix) <- names(methods_list)
rownames(pct_matrix) <- colnames(pct_matrix) <- names(methods_list)
for (i in seq_along(methods_list)) {
for (j in seq_along(methods_list)) {
if (i == j) {
overlap_matrix[i, j] <- length(methods_list[[i]])
pct_matrix[i, j] <- 100
} else {
overlap_result <- calculate_overlap(methods_list[[i]], methods_list[[j]])
overlap_matrix[i, j] <- overlap_result$count
pct_matrix[i, j] <- overlap_result$percentage
}
}
}
cat("\n#### Overlap Counts (rows overlap with columns)\n")
knitr::kable(overlap_matrix, digits = 0, caption = "Number of DMRs from Method A (rows) overlapping with Method B (columns)")
| CMEnt | comb-p | probeLasso | DMRcate | Bumphunter | |
|---|---|---|---|---|---|
| CMEnt | 539 | 504 | 281 | 496 | 501 |
| comb-p | 503 | 503 | 277 | 493 | 500 |
| probeLasso | 286 | 283 | 289 | 279 | 282 |
| DMRcate | 482 | 482 | 270 | 482 | 482 |
| Bumphunter | 494 | 494 | 274 | 487 | 494 |
cat("\n#### Overlap Percentages\n")
knitr::kable(pct_matrix, digits = 2, caption = "Percentage of DMRs from Method A (rows) overlapping with Method B (columns)")
| CMEnt | comb-p | probeLasso | DMRcate | Bumphunter | |
|---|---|---|---|---|---|
| CMEnt | 100.00 | 93.51 | 52.13 | 92.02 | 92.95 |
| comb-p | 100.00 | 100.00 | 55.07 | 98.01 | 99.40 |
| probeLasso | 98.96 | 97.92 | 100.00 | 96.54 | 97.58 |
| DMRcate | 100.00 | 100.00 | 56.02 | 100.00 | 100.00 |
| Bumphunter | 100.00 | 100.00 | 55.47 | 98.58 | 100.00 |
jaccard_index <- function(gr1, gr2) {
if (length(gr1) == 0 || length(gr2) == 0) {
return(0)
}
overlap_count <- length(subsetByOverlaps(gr1, gr2))
total_unique <- length(unique(c(gr1, gr2)))
if (total_unique == 0) {
return(0)
}
overlap_count / total_unique
}
iou <- function(gr1, gr2) {
if (length(gr1) == 0 || length(gr2) == 0) {
return(0)
}
intersection <- sum(width(GenomicRanges::intersect(gr1, gr2)))
union <- sum(width(GenomicRanges::union(gr1, gr2)))
if (union == 0) {
return(0)
}
intersection / union
}
jaccard_matrix <- matrix(0, nrow = length(methods_list), ncol = length(methods_list))
iou_matrix <- matrix(0, nrow = length(methods_list), ncol = length(methods_list))
rownames(jaccard_matrix) <- colnames(jaccard_matrix) <- names(methods_list)
rownames(iou_matrix) <- colnames(iou_matrix) <- names(methods_list)
for (i in seq_along(methods_list)) {
for (j in seq_along(methods_list)) {
if (i < j) {
jaccard_matrix[i, j] <- jaccard_matrix[j, i] <- jaccard_index(methods_list[[i]], methods_list[[j]])
iou_matrix[i, j] <- iou_matrix[j, i] <- iou(methods_list[[i]], methods_list[[j]])
} else if (i == j) {
jaccard_matrix[i, j] <- 1
iou_matrix[i, j] <- 1
}
}
}
cat("\n#### Jaccard Index\n")
knitr::kable(jaccard_matrix, digits = 4, caption = "Jaccard Index (region count-based similarity)")
| CMEnt | comb-p | probeLasso | DMRcate | Bumphunter | |
|---|---|---|---|---|---|
| CMEnt | 1.0000 | 0.4837 | 0.3394 | 0.6223 | 0.7147 |
| comb-p | 0.4837 | 1.0000 | 0.3497 | 0.5005 | 0.5015 |
| probeLasso | 0.3394 | 0.3497 | 1.0000 | 0.3619 | 0.3602 |
| DMRcate | 0.6223 | 0.5005 | 0.3619 | 1.0000 | 0.5857 |
| Bumphunter | 0.7147 | 0.5015 | 0.3602 | 0.5857 | 1.0000 |
cat("\n#### Intersection over Union (IoU)\n")
knitr::kable(iou_matrix, digits = 4, caption = "IoU (genomic coverage-based similarity)")
| CMEnt | comb-p | probeLasso | DMRcate | Bumphunter | |
|---|---|---|---|---|---|
| CMEnt | 1.0000 | 0.9538 | 0.1706 | 0.6338 | 0.8631 |
| comb-p | 0.9538 | 1.0000 | 0.1716 | 0.6553 | 0.8850 |
| probeLasso | 0.1706 | 0.1716 | 1.0000 | 0.1462 | 0.1744 |
| DMRcate | 0.6338 | 0.6553 | 0.1462 | 1.0000 | 0.6027 |
| Bumphunter | 0.8631 | 0.8850 | 0.1744 | 0.6027 | 1.0000 |
viz_data <- results_comparison |>
dplyr::select(Method, Number.of.DMRs, Total.Coverage.bp) |>
tidyr::pivot_longer(
cols = c(Number.of.DMRs, Total.Coverage.bp),
names_to = "Metric", values_to = "Value"
)
viz_data$Metric <- ifelse(viz_data$Metric == "Number.of.DMRs",
"Number of DMRs",
"Total Coverage (bp)"
)
p1 <- ggplot(viz_data, aes(x = Method, y = Value, fill = Method)) +
geom_bar(stat = "identity") +
facet_wrap(~Metric, scales = "free_y") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none",
panel.grid.major.x = element_blank()
) +
labs(
title = "DMR Detection: Count and Coverage Comparison",
y = "Value", x = ""
)
print(p1)
evaluate_against_truth <- function(pred, truth) {
n_pred <- length(pred)
n_truth <- length(truth)
pred_overlap_truth <- sum(overlapsAny(pred, truth))
truth_overlap_pred <- sum(overlapsAny(truth, pred))
fp <- n_pred - pred_overlap_truth
fn <- n_truth - truth_overlap_pred
precision <- ifelse(n_pred == 0, 0, pred_overlap_truth / n_pred)
recall <- ifelse(n_truth == 0, 0, truth_overlap_pred / n_truth)
f1 <- ifelse(precision + recall == 0, 0, 2 * precision * recall / (precision + recall))
pred_red <- reduce(pred)
truth_red <- reduce(truth)
inter <- GenomicRanges::intersect(pred_red, truth_red)
pred_bp <- sum(width(pred_red))
truth_bp <- sum(width(truth_red))
inter_bp <- ifelse(length(inter) == 0, 0, sum(width(inter)))
union_bp <- pred_bp + truth_bp - inter_bp
precision_bp <- ifelse(pred_bp == 0, 0, inter_bp / pred_bp)
recall_bp <- ifelse(truth_bp == 0, 0, inter_bp / truth_bp)
f1_bp <- ifelse(precision_bp + recall_bp == 0, 0, 2 * precision_bp * recall_bp / (precision_bp + recall_bp))
iou_bp <- ifelse(union_bp == 0, 0, inter_bp / union_bp)
data.frame(
TP_regions = truth_overlap_pred,
FP_regions = fp,
FN_regions = fn,
Precision_regions = precision,
Recall_regions = recall,
F1_regions = f1,
Predicted_bp = pred_bp,
Truth_bp = truth_bp,
Intersect_bp = inter_bp,
Precision_bp = precision_bp,
Recall_bp = recall_bp,
F1_bp = f1_bp,
IoU_bp = iou_bp
)
}
truth_eval <- do.call(rbind, lapply(names(methods_list), function(method) {
out <- evaluate_against_truth(methods_list[[method]], truth_dmrs)
out$Method <- method
out
}))
truth_eval$Method <- factor(truth_eval$Method, levels = method_order)
truth_eval <- truth_eval[order(truth_eval$Method), , drop = FALSE]
truth_eval$Method <- as.character(truth_eval$Method)
truth_eval <- truth_eval |>
dplyr::select(
Method,
TP_regions,
FP_regions, FN_regions,
Precision_regions, Recall_regions, F1_regions,
Precision_bp, Recall_bp, F1_bp, IoU_bp
)
regions_truth_eval <- truth_eval |>
dplyr::select(Method, TP_regions, FP_regions, FN_regions, Precision_regions, Recall_regions, F1_regions) |>
dplyr::rename_with(~ sub("_regions$", "", .), ends_with("_regions"))
bp_truth_eval <- truth_eval |>
dplyr::select(Method, Precision_bp, Recall_bp, F1_bp, IoU_bp) |>
dplyr::rename_with(~ sub("_bp$", "", .), ends_with("_bp"))
knitr::kable(regions_truth_eval, digits = 4, caption = "Macro (Region-level) Performance Against Ground Truth")
| Method | TP | FP | FN | Precision | Recall | F1 |
|---|---|---|---|---|---|---|
| CMEnt | 500 | 38 | 0 | 0.9295 | 1.000 | 0.9635 |
| comb-p | 500 | 3 | 0 | 0.9940 | 1.000 | 0.9970 |
| probeLasso | 276 | 7 | 224 | 0.9758 | 0.552 | 0.7051 |
| DMRcate | 493 | 0 | 7 | 1.0000 | 0.986 | 0.9930 |
| Bumphunter | 500 | 0 | 0 | 1.0000 | 1.000 | 1.0000 |
knitr::kable(bp_truth_eval, digits = 4, caption = "Micro (Base-pair-level) Performance Against Ground Truth")
| Method | Precision | Recall | F1 | IoU |
|---|---|---|---|---|
| CMEnt | 0.9646 | 0.9936 | 0.9789 | 0.9586 |
| comb-p | 0.9888 | 1.0000 | 0.9944 | 0.9888 |
| probeLasso | 0.3909 | 0.2341 | 0.2928 | 0.1715 |
| DMRcate | 0.6572 | 0.9875 | 0.7892 | 0.6518 |
| Bumphunter | 0.9870 | 0.9055 | 0.9445 | 0.8949 |
all_sets <- c(methods_list, list(`Ground Truth` = truth_dmrs))
calculate_overlap <- function(gr1, gr2) {
if (length(gr1) == 0 || length(gr2) == 0) {
return(list(count = 0, percentage = 0))
}
overlap_count <- sum(overlapsAny(gr1, gr2))
pct <- overlap_count / length(gr1) * 100
list(count = overlap_count, percentage = pct)
}
overlap_count_mat <- matrix(0, nrow = length(all_sets), ncol = length(all_sets))
overlap_pct_mat <- matrix(0, nrow = length(all_sets), ncol = length(all_sets))
rownames(overlap_count_mat) <- colnames(overlap_count_mat) <- names(all_sets)
rownames(overlap_pct_mat) <- colnames(overlap_pct_mat) <- names(all_sets)
for (i in seq_along(all_sets)) {
for (j in seq_along(all_sets)) {
if (i == j) {
overlap_count_mat[i, j] <- length(all_sets[[i]])
overlap_pct_mat[i, j] <- 100
} else {
ov <- calculate_overlap(all_sets[[i]], all_sets[[j]])
overlap_count_mat[i, j] <- ov$count
overlap_pct_mat[i, j] <- ov$percentage
}
}
}
knitr::kable(overlap_count_mat, digits = 0, caption = "Overlap Counts (query=row, reference=column)")
| CMEnt | comb-p | probeLasso | DMRcate | Bumphunter | Ground Truth | |
|---|---|---|---|---|---|---|
| CMEnt | 539 | 504 | 281 | 496 | 501 | 501 |
| comb-p | 503 | 503 | 277 | 493 | 500 | 500 |
| probeLasso | 286 | 283 | 289 | 279 | 282 | 282 |
| DMRcate | 482 | 482 | 270 | 482 | 482 | 482 |
| Bumphunter | 494 | 494 | 274 | 487 | 494 | 494 |
| Ground Truth | 500 | 500 | 276 | 493 | 500 | 500 |
knitr::kable(overlap_pct_mat, digits = 2, caption = "Overlap Percentages (query=row, reference=column)")
| CMEnt | comb-p | probeLasso | DMRcate | Bumphunter | Ground Truth | |
|---|---|---|---|---|---|---|
| CMEnt | 100.00 | 93.51 | 52.13 | 92.02 | 92.95 | 92.95 |
| comb-p | 100.00 | 100.00 | 55.07 | 98.01 | 99.40 | 99.40 |
| probeLasso | 98.96 | 97.92 | 100.00 | 96.54 | 97.58 | 97.58 |
| DMRcate | 100.00 | 100.00 | 56.02 | 100.00 | 100.00 | 100.00 |
| Bumphunter | 100.00 | 100.00 | 55.47 | 98.58 | 100.00 | 100.00 |
| Ground Truth | 100.00 | 100.00 | 55.20 | 98.60 | 100.00 | 100.00 |
plot_df <- truth_eval |>
dplyr::select(Method, F1_regions, F1_bp, IoU_bp) |>
tidyr::pivot_longer(
cols = c(F1_regions, F1_bp, IoU_bp),
names_to = "Metric",
values_to = "Value"
)
plot_df$Metric <- factor(
plot_df$Metric,
levels = c("F1_regions", "F1_bp", "IoU_bp"),
labels = c("F1 (Region)", "F1 (Base-pair)", "IoU (Base-pair)")
)
plot_df$Method <- factor(plot_df$Method, levels = method_order)
ggplot(plot_df, aes(x = Method, y = Value, fill = Method)) +
geom_col() +
facet_wrap(~Metric, nrow = 1) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none"
) +
ylim(0, 1) +
labs(
title = "Method Performance vs Ground Truth",
x = "",
y = "Score"
)
width_data <- do.call(rbind, lapply(names(all_sets), function(method) {
x <- all_sets[[method]]
if (length(x) == 0) {
return(NULL)
}
data.frame(Method = method, Width = width(x))
}))
width_data$Method <- factor(width_data$Method, levels = names(all_sets))
ggplot(width_data, aes(x = Method, y = Width, fill = Method)) +
geom_violin(alpha = 0.7) +
geom_boxplot(width = 0.2, alpha = 0.5, outlier.size = 0.5) +
scale_y_log10(labels = scales::comma) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none"
) +
labs(
title = "DMR Width Distributions",
x = "",
y = "Width (bp, log scale)"
)
cat("\n## Key Findings\n\n")
if (nrow(truth_eval) > 0) {
best_f1 <- truth_eval[which.max(truth_eval$F1_regions), ]
best_iou <- truth_eval[which.max(truth_eval$IoU_bp), ]
cat(sprintf(
"- Best region-level F1: **%s** (%.3f)\n",
best_f1$Method, best_f1$F1_regions
))
cat(sprintf(
"- Best base-pair IoU: **%s** (%.3f)\n",
best_iou$Method, best_iou$IoU_bp
))
if ("CMEnt" %in% truth_eval$Method) {
seg <- truth_eval[truth_eval$Method == "CMEnt", ]
cat(sprintf(
"- **CMEnt** region metrics: Precision = %.3f, Recall = %.3f, F1 = %.3f\n",
seg$Precision_regions, seg$Recall_regions, seg$F1_regions
))
cat(sprintf(
"- **CMEnt** base-pair metrics: Precision = %.3f, Recall = %.3f, F1 = %.3f, IoU = %.3f\n",
seg$Precision_bp, seg$Recall_bp, seg$F1_bp, seg$IoU_bp
))
}
}
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/blas/libblas.so.3.12.0 LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0 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: Europe/Brussels tzcode source: system (glibc)
attached base packages:
[1] parallel stats4 stats graphics grDevices datasets utils
[8] methods base
other attached packages:
[1] dplyr_1.2.1 ggplot2_4.0.3
[3] data.table_1.18.4 minfi_1.58.0
[5] bumphunter_1.54.0 locfit_1.5-9.12
[7] iterators_1.0.14 foreach_1.5.2
[9] Biostrings_2.80.1 XVector_0.52.0
[11] SummarizedExperiment_1.42.0 Biobase_2.72.0
[13] MatrixGenerics_1.24.0 matrixStats_1.5.0
[15] GenomicRanges_1.64.0 Seqinfo_1.2.0
[17] IRanges_2.46.0 S4Vectors_0.50.1
[19] BiocGenerics_0.58.1 generics_0.1.4
[21] CMEnt_0.99.0 testthat_3.3.2
[23] BiocStyle_2.40.0
loaded via a namespace (and not attached):
[1] R.methodsS3_1.8.2
[2] dichromat_2.0-0.1
[3] ggseqlogo_0.2.2
[4] strex_2.0.1
[5] progress_1.2.3
[6] RPMM_1.25
[7] shinythemes_1.2.0
[8] nnet_7.3-20
[9] DT_0.34.0
[10] HDF5Array_1.40.0
[11] vctrs_0.7.3
[12] digest_0.6.39
[13] png_0.1-9
[14] shape_1.4.6.1
[15] proxy_0.4-29
[16] lumi_2.64.0
[17] deldir_2.0-4
[18] combinat_0.0-8
[19] permute_0.9-10
[20] magick_2.9.1
[21] MASS_7.3-65
[22] pkgdown_2.2.0
[23] reshape_0.8.10
[24] reshape2_1.4.5
[25] qvalue_2.44.0
[26] httpuv_1.6.17
[27] withr_3.0.2
[28] showtextdb_3.0
[29] xfun_0.58
[30] ellipsis_0.3.3
[31] survival_3.8-6
[32] bedr_1.1.5
[33] doRNG_1.8.6.3
[34] memoise_2.0.1
[35] BiasedUrn_2.0.12
[36] sysfonts_0.8.9
[37] GlobalOptions_0.1.4
[38] gtools_3.9.5
[39] DNAcopy_1.86.0
[40] R.oo_1.27.1
[41] Formula_1.2-5
[42] prettyunits_1.2.0
[43] KEGGREST_1.52.0
[44] promises_1.5.0
[45] otel_0.2.0
[46] httr_1.4.8
[47] restfulr_0.0.16
[48] IlluminaHumanMethylation450kanno.ilmn12.hg19_0.6.1
[49] rhdf5filters_1.24.0
[50] ps_1.9.3
[51] rhdf5_2.56.0
[52] rstudioapi_0.18.0
[53] UCSC.utils_1.8.0
[54] base64enc_0.1-6
[55] processx_3.9.0
[56] curl_7.1.0
[57] h5mread_1.4.0
[58] ChAMPdata_2.44.0
[59] ExperimentHub_3.2.0
[60] quadprog_1.5-8
[61] SparseArray_1.12.2
[62] Illumina450ProbeVariants.db_1.48.0
[63] xtable_1.8-8
[64] stringr_1.6.0
[65] desc_1.4.3
[66] doParallel_1.0.17
[67] evaluate_1.0.5
[68] S4Arrays_1.12.0
[69] BiocFileCache_3.2.0
[70] preprocessCore_1.74.0
[71] hms_1.1.4
[72] bookdown_0.46
[73] colorspace_2.1-2
[74] filelock_1.0.3
[75] JADE_2.0-4
[76] IlluminaHumanMethylationEPICanno.ilm10b4.hg19_0.6.0
[77] marray_1.90.0
[78] VennDiagram_1.8.2
[79] magrittr_2.0.5
[80] readr_2.2.0
[81] viridis_0.6.5
[82] later_1.4.8
[83] lattice_0.22-9
[84] genefilter_1.94.0
[85] bspm_0.5.8
[86] XML_3.99-0.23
[87] class_7.3-23
[88] Hmisc_5.2-5
[89] pillar_1.11.1
[90] nlme_3.1-168
[91] pwalign_1.8.0
[92] gridBase_0.4-7
[93] caTools_1.18.3
[94] compiler_4.6.0
[95] beachmat_2.28.0
[96] stringi_1.8.7
[97] shinycssloaders_1.1.0
[98] showtext_0.9-8
[99] dendextend_1.19.1
[100] devtools_2.5.2
[101] GenomicAlignments_1.48.0
[102] plyr_1.8.9
[103] crayon_1.5.3
[104] abind_1.4-8
[105] BiocIO_1.22.0
[106] org.Hs.eg.db_3.23.1
[107] bit_4.6.0
[108] codetools_0.2-20
[109] openssl_2.4.2
[110] ChAMP_2.42.0
[111] bslib_0.11.0
[112] e1071_1.7-17
[113] biovizBase_1.60.0
[114] GetoptLong_1.1.1
[115] plotly_4.12.0
[116] wateRmelon_2.18.0
[117] multtest_2.68.0
[118] mime_0.13
[119] splines_4.6.0
[120] circlize_0.4.18
[121] Rcpp_1.1.1-1.1
[122] dbplyr_2.5.2
[123] sparseMatrixStats_1.24.0
[124] IlluminaHumanMethylation450kmanifest_0.4.0
[125] interp_1.1-6
[126] knitr_1.51
[127] blob_1.3.0
[128] BiocVersion_3.23.1
[129] clue_0.3-68
[130] seqLogo_1.78.0
[131] AnnotationFilter_1.36.0
[132] fs_2.1.0
[133] checkmate_2.3.4
[134] DelayedMatrixStats_1.34.0
[135] pkgbuild_1.4.8
[136] Gviz_1.56.0
[137] tibble_3.3.1
[138] Matrix_1.7-5
[139] callr_3.8.0
[140] statmod_1.5.2
[141] tzdb_0.5.0
[142] pkgconfig_2.0.3
[143] tools_4.6.0
[144] cachem_1.1.0
[145] cigarillo_1.2.0
[146] RSQLite_3.53.1
[147] globaltest_5.66.0
[148] viridisLite_0.4.3
[149] DBI_1.3.0
[150] impute_1.86.0
[151] fastmap_1.2.0
[152] rmarkdown_2.31
[153] scales_1.4.0
[154] grid_4.6.0
[155] usethis_3.2.1
[156] Rsamtools_2.28.0
[157] AnnotationHub_4.2.0
[158] sass_0.4.10
[159] FNN_1.1.4.1
[160] BiocManager_1.30.27
[161] VariantAnnotation_1.58.0
[162] scrime_1.3.7
[163] rpart_4.1.27
[164] farver_2.1.2
[165] mgcv_1.9-4
[166] yaml_2.3.12
[167] latticeExtra_0.6-31
[168] foreign_0.8-90
[169] rtracklayer_1.72.0
[170] illuminaio_0.54.0
[171] cli_3.6.6
[172] purrr_1.2.2
[173] siggenes_1.86.0
[174] txdbmaker_1.8.0
[175] GEOquery_2.80.0
[176] lifecycle_1.0.5
[177] askpass_1.2.1
[178] IlluminaHumanMethylationEPICmanifest_0.3.0
[179] lambda.r_1.2.4
[180] sessioninfo_1.2.4
[181] backports_1.5.1
[182] methylumi_2.58.0
[183] BiocParallel_1.46.0
[184] annotate_1.90.0
[185] gtable_0.3.6
[186] rjson_0.2.23
[187] missMethyl_1.46.0
[188] limma_3.68.4
[189] edgeR_4.10.1
[190] jsonlite_2.0.0
[191] isva_1.10
[192] TFBSTools_1.50.0
[193] bitops_1.0-9
[194] bit64_4.8.2
[195] brio_1.1.5
[196] base64_2.0.2
[197] geneLenDataBase_1.48.0
[198] futile.options_1.0.1
[199] jquerylib_0.1.4
[200] R.utils_2.13.0
[201] DMRcate_3.8.0
[202] lazyeval_0.2.3
[203] shiny_1.13.0
[204] affy_1.90.0
[205] htmltools_0.5.9
[206] GO.db_3.23.1
[207] rappdirs_0.3.4
[208] formatR_1.14
[209] tinytex_0.59
[210] ROC_1.88.0
[211] ensembldb_2.36.1
[212] glue_1.8.1
[213] TFMPvalue_1.0.0
[214] httr2_1.2.2
[215] RCurl_1.98-1.19
[216] rprojroot_2.1.1
[217] mclust_6.1.2
[218] BSgenome_1.80.0
[219] jpeg_0.1-11
[220] futile.logger_1.4.9
[221] gridExtra_2.3
[222] igraph_2.3.2
[223] R6_2.6.1
[224] sva_3.60.0
[225] tidyr_1.3.2
[226] DelayedDataFrame_1.28.0
[227] labeling_0.4.3
[228] GenomicFeatures_1.64.0
[229] cluster_2.1.8.2
[230] rngtools_1.5.2
[231] pkgload_1.5.2
[232] Rhdf5lib_2.0.0
[233] beanplot_1.3.1
[234] GenomeInfoDb_1.48.0
[235] bsseq_1.48.0
[236] DirichletMultinomial_1.54.0
[237] DelayedArray_0.38.2
[238] tidyselect_1.2.1
[239] ProtGenerics_1.44.0
[240] nleqslv_3.3.7
[241] htmlTable_2.5.0
[242] xml2_1.5.2
[243] inline_0.3.21
[244] AnnotationDbi_1.74.0
[245] fastICA_1.2-7
[246] goseq_1.64.0
[247] KernSmooth_2.23-26
[248] S7_0.2.2
[249] affyio_1.82.0
[250] nor1mix_1.3-3
[251] htmlwidgets_1.6.4
[252] ComplexHeatmap_2.28.0
[253] RColorBrewer_1.1-3
[254] biomaRt_2.68.0
[255] rlang_1.2.0
[256] rentrez_1.2.4