Wrapper Function

The differential_analysis function runs the full differential analysis workflow in one call, covering three sequential steps: statistical testing, visualization, and pathway annotation. For users who prefer fine-grained control over parameters, please refer to the detailed parameter settings below.

First, count matrices across samples are compared condition-by-condition using a two-round limma-voom framework, identifying genomic regions with significant differences in accessibility. When column clusters are provided, counts within each cluster are aggregated across pairs before testing, and results are reported per cluster. Each pairwise comparison produces a set of significant regions with associated statistics.

Next, the significant regions from each comparison are visualized as log2 count heatmaps, with columns grouped by sample. This allows direct visual inspection of accessibility patterns across conditions.

Finally, the significant regions are submitted to GREAT for pathway enrichment analysis against a user-specified MSigDB gene set collection, revealing the biological processes associated with each set of differential regions. Results are summarised in per-comparison TSV tables and an optional bubble plot.

Parameters

Parameter Type Default Description Example
cm_path character vector Paths to per-sample count matrices in .feather format, one per sample. Must have the same length as conditions. Each file must contain a pos column and one column per CRF pair. c(“C1.feather”, “C2.feather”, “T1.feather”, “T2.feather”)
conditions character vector Condition labels for each sample in cm_path, in the same order. Each condition must have at least 2 replicates. All pairwise comparisons between conditions are tested. c(“C”, “C”, “T”, “T”)
sample_names character vector or NULL NULL Optional sample names used in output file names and heatmap column labels. Must be unique and the same length as conditions. If NULL, names are auto-generated as <condition><replicate_index> (e.g. C1, C2, T1). c(“Control_1”, “Control_2”, “Treat_1”, “Treat_2”)
out_dir character “./” Root output directory for all results. Differential region tables and heatmaps are written here; pathway enrichment outputs are written to <out_dir>/pathway_enrichment/. Created recursively if it does not exist. “./differential”
col_cluster_file_path character or NULL NULL Path to a TSV file with columns pair and cluster, mapping CRF pairs to column clusters. When provided, counts within each cluster are summed across pairs before testing, and heatmaps expand back to pair-level columns grouped by sample. When NULL, each pair is tested independently. “bicluster/col_table.tsv”
ref_genome character “hg38” Reference genome. Controls the TSS source for GREAT and the species for MSigDB gene set retrieval. Supported: “hg38”, “mm10”. “mm10”
norm_level character “crf” Controls the level at which TMM normalization factors are estimated.

“crf” estimates factors independently within each CRF group, which is appropriate when composition is expected to remain relatively stable across conditions and only local differences are anticipated.

“global” (recommended for most cases) estimates a single set of normalization factors across all samples before any per-group analysis, making it more robust when global composition shifts substantially between conditions (such as in CRF knockdown or knockout experiments where the target mark is depleted genome-wide).
norm_level = “global”
min_support integer 2 Minimum number of non-zero replicates required within at least one condition for a genomic region to pass the initial non-zero support filter before differential testing. Only regions passing this filter are carried forward to the limma-voom modeling step and considered for significance testing. For example, with min_support = 2, a region is retained if it has non-zero counts in at least 2 replicates in Control or at least 2 replicates in Treatment. 1
lfc_threshold numeric 0.5 Minimum absolute log2 fold-change required for a region to be called significant after model fitting. A region is considered significant only if it satisfies both abs(logFC) > lfc_threshold and adj.P.Val < p_threshold. This parameter controls the effect size requirement in the final significance decision. 1
p_threshold numeric 0.05 P-value threshold for significance. Interpreted according to p_type. p_threshold = 0.05
p_type character “fdr” Which p-value to use for filtering: “fdr” (BH-adjusted, recommended for genome-wide analysis), “nominal” (raw p-value, suitable for pre-selected gene lists), or “bonferroni” (conservative correction). p_type = “nominal”
apply_annotation logical TRUE Whether to perform GREAT-based pathway enrichment annotation on significant differential regions. Set to FALSE to skip the pathway annotation step entirely. FALSE
plot logical TRUE Whether to generate visualizations. Controls both heatmap generation and the pathway enrichment bubble plot. Set to FALSE to suppress all plot outputs. FALSE

Example Usage

library(multiEpiCore)
# Test Data
cm_root_path <- "count_matrix/"
cm_files <- c("C1_Count_Matrix_800.feather", "C2_Count_Matrix_800.feather", "T1_Count_Matrix_800.feather", "T2_Count_Matrix_800.feather")
cm_path <- file.path(cm_root_path, cm_files)

conditions <- c("C", "C", "T", "T")
sample_names <- c("C1", "C2", "T1", "T2")

out_dir <- "differential"

differential_regions(cm_path = cm_path, conditions = conditions, out_dir = out_dir, norm_level = "crf", min_support = 2, lfc_threshold = 1, p_threshold = 0.05, p_type = "fdr", apply_annotation = TRUE, plot = TRUE)

1. Differential Region Detection

The differential_regions() function identifies differential genomic regions between experimental conditions using limma-voom. An optional column cluster file can be provided to group CRF pairs into clusters. When provided, differential analysis is performed at the cluster level (for each cluster, all assigned CRF pairs are included together). When omitted, each CRF pair is analyzed as its own group.

Differential analysis is performed using the following workflow:

  1. Normalization
    TMM (trimmed mean of M-values) normalization is applied to correct for composition bias across samples. The level at which normalization factors are estimated is controlled by norm_level:

    • "global" (default): A single set of TMM factors is estimated once across all samples using the full feature space (all regions × all pairs flattened into a single matrix), before any per-group analysis. This is recommended for most epigenomic comparisons and ensures that fold-change estimates are comparable across CRFs.
    • "crf": TMM factors are estimated independently within each CRF group during model fitting. This is more appropriate when global composition is expected to shift substantially between conditions — for example, in CRF knockdown or knockout experiments where the target mark is depleted genome-wide.
  2. Non-zero support filtering
    Regions are filtered based on signal support across replicates. A region is retained only if it has non-zero counts in at least min_support replicates within at least one condition. This removes regions with insufficient signal for reliable modeling.

  3. Voom transformation
    The filtered count matrix is transformed to log2-CPM values via voom, which estimates the mean-variance relationship and assigns observation-level precision weights using the normalization factors determined in step 1.

  4. Linear modeling and empirical Bayes moderation
    A design matrix is constructed from the input conditions and a linear model is fitted for each region. Empirical Bayes moderation (eBayes) is applied to stabilize variance estimates across regions.

  5. Differential region identification
    Regions are defined as differential if they satisfy both:

    • absolute log2 fold-change > lfc_threshold
    • p-value < p_threshold (interpreted according to p_type)

Parameters

Parameter Type Default Description Example
cm_path character vector Paths to per-sample count matrices in .feather format. Each file should represent one sample, with rows as genomic regions (stored in a required pos column) and columns as CRF pairs. All files are harmonized to the common set of regions and CRF pairs before analysis. c(“C1.feather”, “C2.feather”, “T1.feather”, “T2.feather”)
conditions character vector Condition labels corresponding to cm_path, in the same order. Each unique condition must have at least 2 replicates, because limma-voom is performed on replicated group comparisons. c(“Control”, “Control”, “Treatment”, “Treatment”)
sample_names character vector NULL Optional unique sample names corresponding to cm_path. If NULL, names are automatically generated from conditions in the form of condition + replicate index (for example, Control1, Control2). c(“C1”, “C2”, “T1”, “T2”)
out_dir character “./” Output directory for all result files, including full differential tables, significant-region tables, summary tables, summary plots, and significant-region count matrices. “diff_results”
col_cluster_file_path character NULL Optional path to a two-column TSV file with columns pair and cluster. If provided, the function runs in cluster-aware mode: CRF pairs assigned to the same cluster are summed within each sample and tested as one aggregated group. If NULL, the function runs in per-pair mode, where each CRF pair is tested independently. “col_cluster.tsv”
norm_level character “crf” Controls the level at which TMM normalization factors are estimated.

“crf” estimates factors independently within each CRF group, which is appropriate when composition is expected to remain relatively stable across conditions and only local differences are anticipated.

“global” (recommended for most cases) estimates a single set of normalization factors across all samples before any per-group analysis, making it more robust when global composition shifts substantially between conditions (such as in CRF knockdown or knockout experiments where the target mark is depleted genome-wide).
norm_level = “global”
min_support integer 2 Minimum number of non-zero replicates required within at least one condition for a genomic region to pass the initial non-zero support filter. For example, with min_support = 2, a region is retained if it has non-zero counts in at least 2 replicates in Control or at least 2 replicates in Treatment. 1
lfc_threshold numeric 0.5 Minimum absolute log2 fold-change required for a region to be called significant after model fitting. A region must satisfy both abs(logFC) > lfc_threshold and adj.P.Val < p_threshold. 1
p_threshold numeric 0.05 P-value threshold for significance. Interpreted according to p_type. p_threshold = 0.05
p_type character “fdr” Which p-value to use for filtering: “fdr” (BH-adjusted, recommended for genome-wide analysis), “nominal” (raw p-value, suitable for pre-selected gene lists), or “bonferroni” (conservative correction). p_type = “nominal”

Output Files

The function automatically derives all pairwise comparisons from the unique values in conditions (sorted alphabetically, in the form <test>_vs_<ref>), and creates one subdirectory per comparison under out_dir. All output files are written into the corresponding subdirectory:

  1. All regions - <comparison>/<cluster|pair>_all.tsv
    • Complete limma-voom differential analysis results for all tested genomic regions within the specified cluster and comparison.
    • Each row represents one genomic region and the table includes the following columns:
      • pos: Genomic region identifier corresponding to the original pos field in the count matrix.
      • logFC: Log2 fold change between the test and reference conditions (positive = higher in test).
      • AveExpr: Average log2-CPM expression level across all samples in the comparison.
      • t: Moderated t-statistic from limma’s empirical Bayes model.
      • P.Value: Raw p-value from the moderated t-test.
      • adj.P.Val: Benjamini-Hochberg FDR-adjusted p-value.
      • B: Log-odds that the region is truly differentially expressed.
pos logFC AveExpr t P.Value adj.P.Val B
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
chr9_130356001_130356800 4.0866 0.2914 11.5638 1.14e-07 0.0331 7.7404
chr10_101139201_101140000 3.8606 0.2378 10.9880 1.95e-07 0.0331 7.2410
chr10_100614401_100615200 3.9058 0.4437 10.7582 2.44e-07 0.0331 7.0467
chr5_148827201_148828000 3.8131 0.1777 10.6754 2.64e-07 0.0331 6.9650
chr15_77552001_77552800 4.0206 0.3255 10.5539 2.98e-07 0.0331 6.9716

This table shows differential analysis results for H3K4me1 in the test dataset.

  1. Significant regions - <comparison>/<cluster|pair>_sig.tsv
    • Filtered results containing only significant regions (FDR < threshold & |logFC| > threshold)
    • Only generated when at least one significant region is detected under the specified thresholds.
  2. Significant region counts<comparison>/<cluster|pair>_sig_raw_counts.tsv
    • Raw fragment counts and for significant regions, each with one row per region and one column per sample.
    • Only generated alongside _sig.tsv when at least one significant region is detected.
pos C1 C2 T1 T2
chr9_130356001_130356800 0 0 16.4753 7.4848
chr10_101139201_101140000 0.0215 0 11.5733 8.7793
chr10_100614401_100615200 0.1848 0 14.2299 9.7483
chr5_148827201_148828000 0 0 11.4695 7.7631
chr15_77552001_77552800 0 0 9.7289 14.0664

This table shows differential analysis results for H3K4me1 in the test dataset.

  1. Significant region CPM<comparison>/<cluster|pair>_sig_log2_cpm.tsv
    • TMM-normalized log2-CPM values for significant regions, each with one row per region and one column per sample.
    • Log2-CPM values are derived from voom transformation using globally estimated TMM normalization factors.
    • Only generated alongside _sig.tsv when at least one significant region is detected.
pos C1 C2 T1 T2
chr9_130356001_130356800 -1.7753 -1.6832 2.5930 2.0310
chr10_101139201_101140000 -1.7147 -1.6832 2.1014 2.2478
chr10_100614401_100615200 -1.3215 -1.6832 2.3883 2.3911
chr5_148827201_148828000 -1.7753 -1.6832 2.0889 2.0805
chr15_77552001_77552800 -1.7753 -1.6832 1.8622 2.8984

This table shows differential analysis results for H3K4me1 in the test dataset.

  1. Summary table per comparison - <comparison>/summary.tsv Includes:
    • cluster ID
    • number of regions before filtering
    • after non-zero filter
    • after mean filter
    • total significant
    • up-regulated
    • down-regulated
group n_rows_before n_rows_after_nonzero n_rows_after_mean n_sig n_up n_down
H3K27ac 3860350 45078 45078 0 0 0
H3K27me3 3860350 1297331 1297331 15829 8727 7102
H3K4me1 3860350 908557 908557 194 189 5
H3K4me3 3860350 528144 528144 7423 5067 2356
H3K9me2 3860350 166729 166729 0 0 0
H3K9me3 3860350 1075802 1075802 15311 8505 6806
  1. Summary plot per comparison - <comparison>/summary.pdf
  • Mirror bar chart visualizing the number of differentially accessible regions per group, with up-regulated regions shown in red (above x-axis) and down-regulated regions in blue (below x-axis).

Example Usage

library(multiEpiCore)

# Test data
cm_root_path <- "count_matrix/"
cm_files <- c("C1_Count_Matrix_800.feather", "C2_Count_Matrix_800.feather", "T1_Count_Matrix_800.feather", "T2_Count_Matrix_800.feather")
cm_path <- file.path(cm_root_path, cm_files)

conditions <- c("C", "C", "T", "T")
sample_names <- c("C1", "C2", "T1", "T2")

out_dir <- "differential"

differential_regions(cm_path = cm_path, conditions = conditions, out_dir = out_dir, norm_level = "crf", min_support = 2, lfc_threshold = 1, p_threshold = 0.05, p_type = "fdr")

2. Differential Accessibility Heatmaps

The differential_heatmap() function generates per-comparison, per-group heatmaps for all significant differential regions produced by differential_regions(). It iterates over the full result object returned by differential_regions() and dispatches each comparison-group combination to differential_heatmap_single(), which renders a region × sample log2-CPM heatmap as a PDF file.

Log2-CPM values are read directly from the _sig_log2_cpm.tsv output of differential_regions(), which are derived from voom transformation using globally estimated TMM normalization factors. No additional transformation is applied.

When the underlying analysis used column clustering (col_cluster_file_path), columns in the heatmap represent individual sample:pair combinations within the cluster, grouped and titled by sample. Without clustering, each column represents one sample directly.

Parameters

Parameter Type Default Description Example
dr_result nested list The return value of differential_regions(). A two-level nested list structured as dr_result[[grp_name]][[cmp_tag]], where the first level keys are cluster or pair names (e.g. “cluster4”, “H3K27ac_peak1”) and the second level keys are comparison tags (e.g. “T_vs_C”). Each leaf is the file path to the corresponding _sig_counts.tsv. dr_result <- differential_regions(…)
out_dir character “./” Output directory for heatmap PDFs. Created recursively if it does not exist. One PDF is written per comparison-group combination, named <cmp_tag>_<grp_name>.pdf. “heatmaps”
show_colnames logical FALSE Whether to display individual column names on the heatmap. When FALSE, only the sample-level column group titles are shown. Set to TRUE to additionally show individual pair identifiers within each sample group. TRUE
col_width_mm numeric 40 Width per individual pair column in mm. Total heatmap width is computed as n_pairs × col_width_mm + (n_samples − 1) × 8 mm, where the second term accounts for the fixed 8 mm gap between sample groups. In cluster mode (when col_cluster_file_path was supplied to differential_regions()), each sample group contains multiple pair columns; col_width_mm still refers to a single pair column, so wider clusters automatically occupy proportionally more space. In non-cluster mode each sample has exactly one column, so col_width_mm equals the per-sample width directly. 30
row_height_mm numeric 0.5 Height per region row in mm. Total heatmap body height is n_sig_regions × row_height_mm mm, so the PDF scales automatically with the number of significant regions. Note that values below ~0.35 mm (one pixel at 72 dpi) will be visually indistinguishable; for large region counts (>1000) a value of 0.13–0.5 mm produces dense stripe patterns comparable to a fixed-height layout. 1.0
random_seed integer 42 Random seed passed to set.seed() before each raster rendering call to ensure pixel-level reproducibility across runs. 123

Output Files

Output is organized by comparison tag (cmp_tag) read from dr_result:

  1. Heatmap - <comparison>/<cluster|pair>.pdf
  • Rows: Significant genomic regions
  • Columns: All CRF pairs across all samples
  • Column blocks: Grouped by sample

This figure shows differential analysis heatmap for cluster 4 in the test dataset.

Example Usage

library(multiEpiCore)

# Test data
dr_result <- list(
  H3K4me1 = list(
    T_vs_C = "differential/T_vs_C/H3K4me1_sig_log2_cpm.tsv"
  ),
  H3K27me3 = list(
    T_vs_C = "differential/T_vs_C/H3K27me3_sig_log2_cpm.tsv"
  ),
  H3K4me3 = list(
    T_vs_C = "differential/T_vs_C/H3K4me3_sig_log2_cpm.tsv"
  ),
  H3K9me3 = list(
    T_vs_C = "differential/T_vs_C/H3K9me3_sig_log2_cpm.tsv"
  )
)

differential_heatmap(
  dr_result = dr_result,
  out_dir   = "differential"
)

3. Differential Pathway Annotation

The differential_pathway_annotation() function performs pathway enrichment analysis on the significant differential regions from each comparison-group combination using GREAT (Genomic Regions Enrichment of Annotations Tool). GREAT assigns genomic regions to nearby genes via basal-plus-extension TSS domains, then tests for over-representation of gene sets using a binomial test. Gene sets are sourced from MSigDB via the msigdbr package, and can be configured to use any available collection such as Hallmark, Reactome, or GO terms.

Each comparison-group combination (e.g. T vs C within cluster 4) is tested independently. Results are written as per-comparison TSV tables and optionally visualised as a bubble plot that summarises enrichment patterns across all comparisons at a glance.

Parameters

Parameter Type Default Description Example
dr_result nested list The return value of differential_regions(). A two-level nested list structured as dr_result[[grp_name]][[cmp_tag]], where each leaf is the file path to the corresponding _sig_counts.tsv. Each comparison-group combination becomes one GREAT query set. dr_result <- differential_regions(…)
out_dir character “./” Output directory for enrichment TSV tables and the bubble plot PDF. Created recursively if it does not exist. One TSV is written per query set, named pathway_annotation_<cmp_tag>_<grp_name>.tsv. “pathway”
ref_genome character “hg38” Reference genome. Controls both the TSS source used by GREAT (“hg38”TxDb.Hsapiens.UCSC.hg38.knownGene, “mm10”TxDb.Mmusculus.UCSC.mm10.knownGene) and the species passed to msigdbr(). Must be one of “hg38” or “mm10”. “mm10”
msigdb_collection character “H” MSigDB collection abbreviation. Common values: “H” (Hallmark, 50 curated biological states), “C2” (curated gene sets including KEGG and Reactome), “C5” (GO gene sets). See msigdbr::msigdbr_collections() for the full list. “C2”
msigdb_subcollection character or NULL NULL MSigDB sub-collection abbreviation. Required when msigdb_collection has subcollections. For example, “CP:REACTOME” or “CP:KEGG_LEGACY” under “C2”, and GO:BP, GO:MF, or GO:CC under “C5”. Leave NULL for collections without subcollections such as “H”. “CP:REACTOME”
plot logical TRUE Whether to generate a bubble plot PDF (pathway_annotation.pdf) summarising enrichment across all query sets. Each row is a pathway ordered by best adjusted p-value across query sets; bubble size encodes log2(1 + fold_enrichment) and color encodes −log10(padj). FALSE

Output Files

Output is organized by comparison tag (cmp_tag) read from dr_result, with a pathway_annotation/ subdirectory created under each:

  1. Enrichment tables<cmp_tag>/pathway_annotation/pathway_annotation_<grp_name>.tsv
  • pathway : Pathway or gene set name, with collection-specific prefixes removed (e.g. HALLMARK_ stripped for collection "H")
  • hits_region: Number of input regions associated with at least one gene in the gene set
  • fold: Fold enrichment of observed over expected region hits
  • p: Binomial test p-value
  • padj: BH-adjusted p-value across all tested gene sets
  • hits_gene: Number of genes in the gene set that were hit by at least one input region
pathway hits_region fold p padj hits_gene
<chr> <int> <dbl> <dbl> <dbl> <int>
ESTROGEN_RESPONSE_LATE 264 1.7437 0 0 103
ESTROGEN_RESPONSE_EARLY 309 1.6681 0 0 112
HEME_METABOLISM 210 1.7305 1.20e-13 2.00e-12 112
P53_PATHWAY 214 1.6654 2.28e-12 2.85e-11 99
ADIPOGENESIS 180 1.4767 4.18e-07 4.18e-06 88
APICAL_JUNCTION 202 1.4176 1.24e-06 1.04e-05 94
UV_RESPONSE_UP 160 1.4729 2.03e-06 1.41e-05 72
TNFA_SIGNALING_VIA_NFKB 255 1.3487 2.25e-06 1.41e-05 109
XENOBIOTIC_METABOLISM 153 1.4038 3.55e-05 1.97e-04 77
KRAS_SIGNALING_DN 207 1.2889 2.19e-04 1.09e-03 82
MYOGENESIS 189 1.2961 3.06e-04 1.39e-03 94
APICAL_SURFACE 68 1.5145 7.63e-04 3.18e-03 22
IL6_JAK_STAT3_SIGNALING 71 1.4481 1.82e-03 7.00e-03 38
COMPLEMENT 182 1.2447 2.18e-03 7.80e-03 85
APOPTOSIS 153 1.2603 2.98e-03 9.94e-03 78
UNFOLDED_PROTEIN_RESPONSE 86 1.3636 3.37e-03 1.01e-02 46
COAGULATION 106 1.3185 3.44e-03 1.01e-02 55
HYPOXIA 208 1.2078 4.09e-03 1.13e-02 99
PEROXISOME 83 1.3033 1.12e-02 2.95e-02 43
DNA_REPAIR 80 1.3021 1.28e-02 3.20e-02 55
ALLOGRAFT_REJECTION 148 1.1887 2.11e-02 4.99e-02 68
IL2_STAT5_SIGNALING 203 1.1559 2.20e-02 4.99e-02 103
NOTCH_SIGNALING 41 1.3528 3.66e-02 7.95e-02 22
MYC_TARGETS_V2 34 1.3812 4.17e-02 8.70e-02 19
HEDGEHOG_SIGNALING 70 1.2359 4.68e-02 9.24e-02 23
INTERFERON_GAMMA_RESPONSE 153 1.1489 4.80e-02 9.24e-02 82
truncated

This table shows pathway annotation result for cluster 4 in the test dataset.

  1. Bubble plot<cmp_tag>/pathway_annotation/pathway_annotation.pdf (when plot = TRUE)
  • Rows are pathways ordered by best adjusted p-value across all clusters/pairs.
  • Columns are clusters/pairs.
  • Bubble size encodes log2 fold enrichment and color encodes -log10(padj).

This figure shows pathway annotation result for cluster 4 in the test dataset.

Example Usage

library(multiEpiCore)

# Test data
dr_result <- list(
  H3K4me1 = list(
    T_vs_C = "differential/T_vs_C/H3K4me1_sig_log2_cpm.tsv"
  ),
  H3K27me3 = list(
    T_vs_C = "differential/T_vs_C/H3K27me3_sig_log2_cpm.tsv"
  ),
  H3K4me3 = list(
    T_vs_C = "differential/T_vs_C/H3K4me3_sig_log2_cpm.tsv"
  ),
  H3K9me3 = list(
    T_vs_C = "differential/T_vs_C/H3K9me3_sig_log2_cpm.tsv"
  )
)

differential_pathway_annotation(
  dr_result = dr_result,
  out_dir   = "differential/pathway_annotation"
)