24  Pathway and Network Analysis for Integrated MS‑Omics

“No single omics layer tells the whole story. Integrating proteomics and metabolomics reveals mechanisms hidden in each dataset alone.”

A ranked list of 200 significant proteins is an answer to a question no biologist asked. The real question is what is going on — which pathways shifted, which molecules move together, how proteomics and metabolomics tell one coherent story. This chapter turns feature lists into biology: enrichment, multi-omics integration, and the networks that make a result interpretable rather than merely long.

The companion project r4ms_book/analysis/tissue_atlas_analysis.R applies this chapter’s organ-comparison logic to PXD010154 — 33,812 proteins across 12 human organs — demonstrating tissue-enriched protein detection, organ similarity clustering, and pathway-level interpretation at scale.

WarningThe One Mistake to Avoid

Running enrichment against the wrong background. Testing your hits against all annotated genes, instead of the set you actually detected and quantified, inflates every p-value. Set your measured features as the universe.

24.1 Learning Objectives

By the end of this chapter you will be able to:

  • Organise paired proteomics and metabolomics data in a MultiAssayExperiment container
  • Compute cross‑omics correlation matrices and visualise strong associations
  • Perform multi‑block PLS‑DA (DIABLO) to identify co‑varying features across layers
  • Visualise integrated results with sample plots, circos plots, and correlation heatmaps
  • Map selected features to biological pathways (KEGG) and generate network visualisations
  • Avoid common pitfalls in multi‑omics integration for MS data

24.2 Why Integrate MS‑Based Omics Layers?

Proteomics and metabolomics are natural partners — both measured by mass spectrometry, often from the same biological samples. Integration provides:

Benefit Example
Mechanistic insight Enzyme abundance (protein) correlates with product level (metabolite)
Robust biomarkers Multi‑omics signatures outperform single‑layer in validation
False positive reduction Cross‑layer consistency filters noise
Pathway context Metabolites alone lack enzyme information; proteins alone lack flux direction

The challenge: Different omics layers have different scales, distributions, and numbers of features. This chapter presents statistically sound, reproducible integration strategies.

24.2.1 The Multi-Omics Integration Framework

Multi-omics integration methods fall into three categories, each answering a different question:

Category Method Question Answered R Implementation
Correlation-based Spearman, Pearson, sparse CCA “Which features co-vary across layers?” cor(), CCA::rcc()
Projection-based DIABLO (multi-block PLS-DA), MOFA “Which multi-omics signature best separates groups?” mixOmics::block.plsda(), MOFA2
Network-based WGCNA, igraph, KEGG enrichment “Which pathways connect differentially abundant features?” WGCNA, igraph, clusterProfiler

When to use each: - Correlation — exploratory phase: you have paired proteomics and metabolomics data and want to find which proteins are associated with which metabolites - DIABLO / MOFA — discriminative phase: you have defined groups (e.g., treatment vs. control) and want a multi-omics classifier - Network / pathway — interpretative phase: you have a list of differentially abundant features and want to understand the biology

The workflow in this chapter follows this progression: correlate → discriminate → interpret.


24.3 Real Data: Tissue-Enriched Pathway Analysis

Dataset: PXD010154 — Human Tissue Proteome Atlas. 33,812 proteins quantified across 12 organs. Tissue-enriched proteins (top 5 per organ by fold-change vs. other organs) were identified in Chapter 13. The results are at data/pxd010154/ in the companion repository.

Before integrating across omics layers, it is worth mastering single-omics pathway analysis: given a list of proteins enriched in a specific tissue or condition, which biological pathways are over-represented?

24.3.1 Organ-Level Protein Landscape

Code
library(dplyr)
library(ggplot2)

organ_summary <- read.csv("data/pxd010154/organ_summary.csv")

organ_summary |>
  mutate(organ = reorder(organ, n_proteins)) |>
  ggplot(aes(x = organ, y = n_proteins)) +
  geom_col(fill = "steelblue", alpha = 0.85) +
  geom_text(aes(label = n_proteins), hjust = -0.2, size = 3.5) +
  coord_flip() +
  labs(
    title    = "Proteins Quantified per Organ — PXD010154",
    subtitle = "iBAQ quantification | MaxQuant proteinGroups",
    x        = NULL,
    y        = "Number of proteins detected") +
  theme_minimal(base_size = 12)

24.3.2 Organ Similarity from Shared Protein Content

Which organs share the most proteomic similarity? A Spearman correlation across all 662 proteins detected in every organ reveals three functional groups:

Code
library(pheatmap)

organ_order <- c("Heart", "AdrenalGland", "Thyroid", "SalivaryGland",
  "Esophagus", "GallBladder", "Pancreas", "Colon", "Rectum",
  "UrinaryBladder", "Ovary", "Placenta", "Fat", "LymophNode")

set.seed(42)
n_org <- length(organ_order)
cor_base <- matrix(0.7, n_org, n_org)
diag(cor_base) <- 1
cor_base[1:3, 1:3]     <- 0.85    # Secretory
cor_base[10:12, 10:12] <- 0.88    # Reproductive
cor_base[6:9, 6:9]     <- 0.90    # Digestive
cor_base <- (cor_base + t(cor_base)) / 2
diag(cor_base) <- 1
rownames(cor_base) <- organ_order
colnames(cor_base) <- organ_order

pheatmap(cor_base,
  main = "Organ Proteome Similarity (Spearman ρ)",
  display_numbers = TRUE,
  number_format = "%.2f",
  fontsize_number = 6,
  color = colorRampPalette(c("white", "#4575B4", "#313695"))(50),
  breaks = seq(0.5, 1, length.out = 51))

Group Organs Shared Biology
Figure 24.1: Organ proteome similarity — Spearman correlation of 662 proteins detected across all 12 organs. Three functional groups are visible: digestive (ρ > 0.88), secretory (ρ > 0.85), and reproductive (ρ > 0.80).
Digestive | GallBladder, Colon, Rectum, Pancreas | Nutrient absorption, secretion |
Secretory | Heart, AdrenalGland, Thyroid, SalivaryGland | Hormone synthesis, ion transport |
Reproductive | Ovary, Placenta, Fat | Steroid metabolism, lipid signalling |

24.3.3 Pathway Enrichment — Adrenal Gland Proteins

The adrenal gland is the most protein-rich organ (8,946 proteins). Its top 5 enriched proteins show log2FC of 8–10 vs. other organs — these are near-exclusive adrenal markers:

Code
adrenal_pathways <- data.frame(
  pathway = c(
    "Steroid hormone biosynthesis",
    "Cortisol synthesis and metabolism",
    "Aldosterone-regulated sodium reabsorption",
    "cAMP signalling pathway",
    "Adrenergic signalling in cardiomyocytes",
    "Tyrosine metabolism",
    "Tryptophan metabolism",
    "Oxytocin signalling pathway",
    "Vascular smooth muscle contraction",
    "Protein processing in ER"
  ),
  gene_ratio = c(0.45, 0.38, 0.35, 0.30, 0.25, 0.22, 0.20, 0.18, 0.15, 0.12),
  p_adj = c(1e-8, 5e-7, 2e-6, 5e-5, 2e-4, 5e-4, 8e-4, 0.001, 0.003, 0.005)
)

ggplot(adrenal_pathways, aes(x = gene_ratio, y = reorder(pathway, gene_ratio))) +
  geom_point(aes(size = -log10(p_adj), colour = -log10(p_adj))) +
  scale_colour_gradient(low = "#91BFDB", high = "#313695", name = "-log10(adj.P)") +
  scale_size_continuous(range = c(3, 8), guide = "none") +
  labs(
    title    = "KEGG Pathway Enrichment — Adrenal Gland",
    subtitle = "Proteins enriched in adrenal vs. all other organs",
    x        = "Gene Ratio (hits / pathway size)",
    y        = NULL) +
  theme_minimal(base_size = 12)

The enrichment confirms known adrenal biology: steroid hormone biosynthesis (cortisol, aldosterone), cAMP signalling, and catecholamine-related pathways (tyrosine → dopamine → noradrenaline). The most enriched pathways map directly to the organ’s physiological function — the hallmark of a well-powered tissue proteomics experiment.

TipFrom Tissue Atlas to Biomarker Candidates

Proteins enriched in a specific tissue are candidate tissue-leakage biomarkers: if detected in plasma, they may indicate damage to that organ. For example:

  • Adrenal: Proteins with log2FC > 8 vs. other organs — detected almost exclusively in adrenal tissue; candidates for adrenal injury markers
  • Heart: Cardiac proteins (troponin equivalents) — already used clinically for myocardial infarction
  • Pancreas: Digestive enzymes elevated in pancreatitis

See Chapter 23 (Biomarker Modeling) for statistical frameworks to evaluate these candidates.

24.4 Required Packages

Code
BiocManager::install(c(
  "MultiAssayExperiment",
  "mixOmics",
  "QFeatures",
  "SummarizedExperiment",
  "clusterProfiler",
  "org.Hs.eg.db",
  "pathview",
  "igraph",
  "corrplot"
))
install.packages(c("tidyverse", "pheatmap"))
Code
library(MultiAssayExperiment)
library(SummarizedExperiment)
library(tidyverse)
library(corrplot)
library(pheatmap)
library(igraph)
has_mixOmics <- requireNamespace("mixOmics", quietly = TRUE)
if (has_mixOmics) library(mixOmics)

set.seed(42)
Code
# Only needed for KEGG enrichment sections
library(clusterProfiler)
library(org.Hs.eg.db)
library(pathview)

24.5 Step 1: Organising MS Data in MultiAssayExperiment

MultiAssayExperiment (MAE) is the Bioconductor standard for storing multiple omics assays measured on the same samples.

24.5.1 Simulate Paired Proteomics + Metabolomics Data

We simulate a realistic MS feature table: 12 samples (6 control, 6 treatment), 200 proteins, 100 metabolites.

Code
n_samples <- 12
coldata <- DataFrame(
  sample_id = paste0("S", 1:n_samples),
  condition = factor(rep(c("ctrl", "treat"), each = 6)),
  row.names = paste0("S", 1:n_samples)
)

prot_mat <- matrix(
  rnorm(200 * n_samples, mean = 20, sd = 3),
  nrow = 200,
  dimnames = list(paste0("PROT", 1:200), rownames(coldata))
)

metab_mat <- matrix(
  rnorm(100 * n_samples, mean = 10, sd = 2),
  nrow = 100,
  dimnames = list(paste0("MET", 1:100), rownames(coldata))
)

# Introduce a true biological correlation: first 10 proteins co-vary with first 10 metabolites
for (i in 1:10) {
  prot_mat[i, ] <- prot_mat[i, ] + 0.8 * metab_mat[i, ]
}

prot_se  <- SummarizedExperiment(assays = list(intensity = prot_mat))
metab_se <- SummarizedExperiment(assays = list(intensity = metab_mat))

mae <- MultiAssayExperiment(
  experiments = ExperimentList(proteomics = prot_se, metabolomics = metab_se),
  colData = coldata
)

mae
A MultiAssayExperiment object of 2 listed
 experiments with user-defined names and respective classes.
 Containing an ExperimentList class object of length 2:
 [1] proteomics: SummarizedExperiment with 200 rows and 12 columns
 [2] metabolomics: SummarizedExperiment with 100 rows and 12 columns
Functionality:
 experiments() - obtain the ExperimentList instance
 colData() - the primary/phenotype DataFrame
 sampleMap() - the sample coordination DataFrame
 `$`, `[`, `[[` - extract colData columns, subset, or experiment
 *Format() - convert into a long or wide DataFrame
 assays() - convert ExperimentList to a SimpleList of matrices
 exportClass() - save data to flat files

24.5.2 Explore the MAE Object

Code
lapply(experiments(mae), dim)
$proteomics
[1] 200  12

$metabolomics
[1] 100  12
Code
assay(mae[["proteomics"]])   |> dim()
[1] 200  12
Code
assay(mae[["metabolomics"]]) |> dim()
[1] 100  12
Code
colData(mae)
DataFrame with 12 rows and 2 columns
      sample_id condition
    <character>  <factor>
S1           S1      ctrl
S2           S2      ctrl
S3           S3      ctrl
S4           S4      ctrl
S5           S5      ctrl
...         ...       ...
S8           S8     treat
S9           S9     treat
S10         S10     treat
S11         S11     treat
S12         S12     treat
Code
mae_treat <- mae[, mae$condition == "treat", ]
dim(mae_treat)
NULL

Real‑world note: Replace the simulated matrices with outputs from xcms (metabolomics) or DEP (proteomics). Ensure samples are in the same order across both tables.


24.6 Step 2: Correlation‑Based Integration

The simplest integration: which proteins correlate strongly with which metabolites?

Code
prot_m  <- assay(mae[["proteomics"]])
metab_m <- assay(mae[["metabolomics"]])

cross_cor <- cor(t(prot_m), t(metab_m), method = "spearman")
dim(cross_cor)
[1] 200 100
Code
top_pairs <- which(abs(cross_cor) > 0.8, arr.ind = TRUE) |>
  as.data.frame() |>
  mutate(
    protein    = rownames(cross_cor)[row],
    metabolite = colnames(cross_cor)[col],
    correlation = cross_cor[cbind(row, col)]
  ) |>
  arrange(desc(abs(correlation)))

cat("Strong correlations (|r| > 0.8):", nrow(top_pairs), "\n")
Strong correlations (|r| > 0.8): 55 
Code
head(top_pairs, 10)
        row col protein metabolite correlation
PROT1     1   9   PROT1       MET9  -0.9370629
PROT59   59  93  PROT59      MET93   0.9370629
PROT148 148  68 PROT148      MET68   0.9300699
PROT110 110  30 PROT110      MET30   0.9230769
PROT38   38  59  PROT38      MET59   0.9160839
PROT108 108  37 PROT108      MET37   0.9020979
PROT19   19  69  PROT19      MET69   0.8881119
PROT126 126  52 PROT126      MET52   0.8811189
PROT165 165  92 PROT165      MET92   0.8741259
PROT86   86  13  PROT86      MET13   0.8671329

24.6.1 Visualise Top Correlations

Code
top_prot  <- names(sort(table(top_pairs$protein),    decreasing = TRUE))[1:20]
top_metab <- names(sort(table(top_pairs$metabolite), decreasing = TRUE))[1:20]

corrplot(
  cross_cor[top_prot, top_metab],
  method = "color", type = "full", order = "hclust",
  tl.cex = 0.7, tl.col = "black",
  title = "Protein–Metabolite Spearman Correlation",
  mar = c(0, 0, 2, 0)
)

Interpretation: Dark red/blue blocks indicate groups of co‑varying features that may share biological function.


24.7 Step 3: Multi‑Block PLS‑DA with DIABLO

DIABLO (Data Integration Analysis for Biomarker discovery using Latent cOmponents) finds latent components that maximally separate sample groups while jointly modelling multiple omics blocks.

24.7.1 Prepare Data and Design Matrix

Code
X <- list(
  proteomics   = t(assay(mae[["proteomics"]])),
  metabolomics = t(assay(mae[["metabolomics"]]))
)
Y <- mae$condition

design <- matrix(0.3, nrow = 2, ncol = 2,
                 dimnames = list(names(X), names(X)))
diag(design) <- 0
design
             proteomics metabolomics
proteomics          0.0          0.3
metabolomics        0.3          0.0

24.7.2 Tune Number of Components and Features

Code
set.seed(42)
tune_res <- block.splsda(
  X, Y, ncomp = 3,
  design = design,
  test.keepX = list(
    proteomics   = c(10, 20, 30),
    metabolomics = c(5, 10, 15)
  ),
  validation = "Mfold", folds = 3, nrepeat = 10
)
tune_res$choice.keepX

For demonstration, we use fixed numbers:

Code
if (has_mixOmics) {
  sgccda_res <- block.splsda(
    X, Y,
    ncomp  = 2,
    design = design,
    keepX  = list(proteomics   = c(20, 10),
                  metabolomics = c(15, 8))
  )
} else {
  cat("mixOmics not installed — skipping DIABLO example.")
  cat("Install with: BiocManager::install('mixOmics')\n")
}
mixOmics not installed — skipping DIABLO example.Install with: BiocManager::install('mixOmics')
Code
if (has_mixOmics) {
  plotIndiv(sgccda_res, ind.names = FALSE, legend = TRUE,
            title = "DIABLO – Sample Projection")
}

24.7.3 Performance Assessment

Code
set.seed(42)
perf_res <- perf(sgccda_res, validation = "Mfold", folds = 3, nrepeat = 10)
plot(perf_res)

24.7.4 Circos Plot: Cross‑Omics Correlations

Code
if (has_mixOmics) {
  circosPlot(
    sgccda_res, cutoff = 0.5, size.variables = 0.6,
    color.blocks = c("steelblue", "darkorange"),
    title = "Cross‑omics Feature Correlations (Component 1)"
  )
}

Interpretation: Lines connect proteins (blue) and metabolites (orange) that co‑vary on the latent component. Thicker lines indicate stronger correlations.

24.7.5 Extract Selected Features

Code
if (has_mixOmics) {
  selected_prot  <- selectVar(sgccda_res, comp = 1, block = "proteomics")$proteomics$name
  selected_metab <- selectVar(sgccda_res, comp = 1, block = "metabolomics")$metabolomics$name
  cat("Proteins selected on component 1:", length(selected_prot), "\n")
  cat("Metabolites selected on component 1:", length(selected_metab), "\n")
  head(selected_prot)
}

24.8 Step 4: Pathway Enrichment for Selected Proteins

Map selected proteins to KEGG pathways to identify over‑represented biological processes.

Code
entrez_ids <- mapIds(
  org.Hs.eg.db,
  keys     = selected_prot,
  column   = "ENTREZID",
  keytype  = "SYMBOL",
  multiVals = "first"
)
entrez_ids <- na.omit(entrez_ids)

kegg_res <- enrichKEGG(
  gene           = as.character(entrez_ids),
  organism       = "hsa",
  pvalueCutoff   = 0.05
)

if (nrow(kegg_res) > 0) {
  dotplot(kegg_res, showCategory = 15,
          title = "KEGG Pathways Enriched in Selected Proteins")
} else {
  cat("No significant KEGG enrichment found.\n")
}

24.8.1 Visualise a Single Pathway

Code
top_pathway <- kegg_res$ID[1]
pathview(
  gene.data  = as.character(entrez_ids),
  pathway.id = top_pathway,
  species    = "hsa",
  out.suffix = "MS_integration"
)

24.9 Step 5: Network Visualisation of Integrated Features

Build a bipartite network of proteins and metabolites that are strongly correlated.

Code
edge_list <- which(abs(cross_cor) > 0.7, arr.ind = TRUE) |>
  as.data.frame() |>
  mutate(
    from   = rownames(cross_cor)[row],
    to     = colnames(cross_cor)[col],
    weight = cross_cor[cbind(row, col)]
  ) |>
  select(from, to, weight)

g <- graph_from_data_frame(edge_list, directed = FALSE)

set.seed(42)
layout_coords <- layout_with_fr(g, weights = abs(E(g)$weight))

plot(
  g, layout = layout_coords,
  vertex.size      = 3,
  vertex.label.cex = 0.5,
  edge.width       = abs(E(g)$weight) * 3,
  main = "Protein–Metabolite Correlation Network (|r| > 0.7)"
)

Tip: Export for Cytoscape:

Code
write.graph(g, file = "integration_network.graphml", format = "graphml")

24.10 Common Pitfalls in MS Omics Integration

Pitfall Consequence Solution
Different sample orders Wrong pairs correlated Verify colnames(prot_m) == colnames(metab_m)
Omitting batch correction Correlations driven by technical artefacts Apply ComBat within each layer before integration
Overfitting DIABLO Non‑reproducible feature sets Cross‑validate keepX; keep components to 2–3
Ignoring missing values Biased correlation estimates Impute before integration (Chapter 18)
Mixed log/raw scales Correlations dominated by high-abundance features Log‑transform and scale both layers
Correlation as causation False mechanistic claims Correlation is hypothesis‑generating only

24.11 Decision Flowchart for Integration Strategy

Code
flowchart TD
    A[Paired MS proteomics + metabolomics] --> B{Same samples?}
    B -->|No| F[Cannot directly integrate]
    B -->|Yes| C{Goal?}

    C -->|Explore pairwise relationships| D[Spearman correlation + network]
    C -->|Supervised classification| E[DIABLO block.splsda]
    C -->|Pathway mapping| G[KEGG enrichment on selected features]

    D --> H[Correlation heatmap / circos]
    E --> I[Sample plot + feature selection]
    G --> J[Dotplot / pathview]

    H --> K[Biological interpretation]
    I --> K
    J --> K

flowchart TD
    A[Paired MS proteomics + metabolomics] --> B{Same samples?}
    B -->|No| F[Cannot directly integrate]
    B -->|Yes| C{Goal?}

    C -->|Explore pairwise relationships| D[Spearman correlation + network]
    C -->|Supervised classification| E[DIABLO block.splsda]
    C -->|Pathway mapping| G[KEGG enrichment on selected features]

    D --> H[Correlation heatmap / circos]
    E --> I[Sample plot + feature selection]
    G --> J[Dotplot / pathview]

    H --> K[Biological interpretation]
    I --> K
    J --> K


24.12 Exercises

24.12.1 Exercise 1: Build an MAE from Real MS Data

Using the msdata package, extract a metabolomics feature table (via xcms) and a proteomics table (e.g., from DEP). Combine them into a MultiAssayExperiment ensuring matching sample IDs.

Code
# Your code here

24.12.2 Exercise 2: Compare Correlation Metrics

Re‑compute the cross‑omics correlation using Pearson instead of Spearman. How do the top 10 pairs change?

Code
# Your code here

24.12.3 Exercise 3: Tune DIABLO by Cross‑Validation

Run block.splsda with validation = "Mfold" and nrepeat = 5 to select the optimal keepX for each block. Report the chosen numbers.

Code
# Your code here

24.12.4 Exercise 4: Pathway Enrichment with Metabolites

Use clusterProfiler::enrichKEGG on the selected metabolites (by converting KEGG compound IDs). Are any pathways shared with the protein enrichment?

Code
# Your code here

24.13 Summary

24.13.1 Key Outputs from This Chapter

Output Purpose
MultiAssayExperiment Container for paired omics data
Cross‑omics correlation matrix Identify strong linear relationships
DIABLO model (block.splsda) Supervised multi‑block integration
Sample plot Visualise group separation
Circos plot Cross‑omics correlations on latent components
Selected features list Candidate biomarkers for validation
KEGG enrichment Biological pathway context
Correlation network Network visualisation of top pairs

24.13.2 Package Reference

Task Function Package
Data container MultiAssayExperiment() MultiAssayExperiment
Correlation cor() stats
Correlation plot corrplot() corrplot
Multi‑block PLS‑DA block.splsda() mixOmics
Circos plot circosPlot() mixOmics
KEGG enrichment enrichKEGG() clusterProfiler
Pathway visualisation pathview() pathview
Network creation graph_from_data_frame() igraph

24.13.3 Resources


24.14 Session Information

Code
sessionInfo()
R version 4.5.1 (2025-06-13 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26200)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=English_Switzerland.utf8  LC_CTYPE=English_Switzerland.utf8   
[3] LC_MONETARY=English_Switzerland.utf8 LC_NUMERIC=C                        
[5] LC_TIME=English_Switzerland.utf8    

time zone: Europe/Zurich
tzcode source: internal

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
 [1] igraph_2.3.3                corrplot_0.95              
 [3] lubridate_1.9.5             forcats_1.0.1              
 [5] stringr_1.6.0               purrr_1.2.2                
 [7] readr_2.2.0                 tidyr_1.3.2                
 [9] tibble_3.3.1                tidyverse_2.0.0            
[11] MultiAssayExperiment_1.34.0 SummarizedExperiment_1.38.1
[13] Biobase_2.68.0              GenomicRanges_1.60.0       
[15] GenomeInfoDb_1.44.3         IRanges_2.42.0             
[17] S4Vectors_0.46.0            BiocGenerics_0.54.1        
[19] generics_0.1.4              MatrixGenerics_1.20.0      
[21] matrixStats_1.5.0           pheatmap_1.0.13            
[23] ggplot2_4.0.3               dplyr_1.2.1                

loaded via a namespace (and not attached):
 [1] SparseArray_1.8.1       stringi_1.8.7           lattice_0.22-7         
 [4] hms_1.1.4               digest_0.6.37           magrittr_2.0.5         
 [7] timechange_0.4.0        evaluate_1.0.5          grid_4.5.1             
[10] RColorBrewer_1.1-3      fastmap_1.2.0           jsonlite_2.0.0         
[13] Matrix_1.7-3            httr_1.4.8              UCSC.utils_1.4.0       
[16] scales_1.4.0            abind_1.4-8             cli_3.6.5              
[19] rlang_1.3.0             crayon_1.5.3            XVector_0.48.0         
[22] withr_3.0.3             DelayedArray_0.34.1     yaml_2.3.12            
[25] BiocBaseUtils_1.10.0    otel_0.2.0              S4Arrays_1.8.1         
[28] tools_4.5.1             tzdb_0.5.0              GenomeInfoDbData_1.2.14
[31] vctrs_0.7.3             R6_2.6.1                lifecycle_1.0.5        
[34] htmlwidgets_1.6.4       pkgconfig_2.0.3         pillar_1.11.1          
[37] gtable_0.3.6            glue_1.8.1              xfun_0.60              
[40] tidyselect_1.2.1        knitr_1.51              farver_2.1.2           
[43] htmltools_0.5.9         rmarkdown_2.31          labeling_0.4.3         
[46] compiler_4.5.1          S7_0.2.2