Using msigdf with enrichment tools
Enrique M. Toledo
enriquetoledo@gmail.com Source:vignettes/gsea-workflows.Rmd
gsea-workflows.RmdAbstract
Recipes for reshaping the msigdf tibbles into the
formats expected by common gene set enrichment tools:
fgsea, clusterProfiler, and anything
else that wants either a named list of gene sets or a two-column
TERM2GENE frame.
Scope
msigdf ships the gene sets only. It deliberately does
not depend on any enrichment package, so fgsea and
clusterProfiler are not installed
alongside it.
The chunks that reshape msigdf data are evaluated below,
so the output you see is real. The chunks that call an enrichment
package are marked eval=FALSE and are shown as recipes to
copy — install the relevant Bioconductor package first:
A named list of gene sets
fgsea::fgsea() and several other tools expect gene sets
as a named list: one element per gene set, each holding a character
vector of gene identifiers. split() produces exactly
that.
Using the human hallmark collection
(category_code == "h"):
hallmark <- msigdf.human %>%
filter(category_code == "h")
pathways <- split(hallmark$symbol, hallmark$geneset)
length(pathways)## [1] 50
## HALLMARK_ADIPOGENESIS HALLMARK_ALLOGRAFT_REJECTION
## 200 200
## HALLMARK_ANDROGEN_RESPONSE HALLMARK_ANGIOGENESIS
## 101 36
## HALLMARK_APICAL_JUNCTION HALLMARK_APICAL_SURFACE
## 200 44
## [1] "APH1A" "ARRB1" "CCND1" "CUL1" "DLL1" "DTX1" "DTX2" "DTX4"
## [9] "FBXW11" "FZD1"
Feed that straight to fgsea, together with your own
named vector of ranking statistics:
library(fgsea)
# `stats` is a named numeric vector: names are gene symbols, values are the
# ranking metric (log fold change, signed -log10 p-value, ...).
res <- fgsea(pathways = pathways, stats = stats)
res %>%
as_tibble() %>%
arrange(padj) %>%
select(pathway, pval, padj, NES, size)Any collection works the same way. To restrict to one sub-collection,
filter on category_subcode as well:
reactome <- msigdf.human %>%
filter(category_code == "c2", category_subcode == "cp.reactome")
reactome_pathways <- split(reactome$symbol, reactome$geneset)
length(reactome_pathways)## [1] 1839
A TERM2GENE data frame
clusterProfiler::enricher() and GSEA() take
a two-column data frame: gene set name first, gene identifier second.
That is the msigdf layout minus the collection columns.
term2gene <- msigdf.human %>%
filter(category_code == "h") %>%
select(term = geneset, gene = symbol)
term2gene## # A tibble: 7,322 × 2
## term gene
## <chr> <chr>
## 1 HALLMARK_ADIPOGENESIS ABCA1
## 2 HALLMARK_ADIPOGENESIS ABCB8
## 3 HALLMARK_ADIPOGENESIS ACAA2
## 4 HALLMARK_ADIPOGENESIS ACADL
## 5 HALLMARK_ADIPOGENESIS ACADM
## 6 HALLMARK_ADIPOGENESIS ACADS
## 7 HALLMARK_ADIPOGENESIS ACLY
## 8 HALLMARK_ADIPOGENESIS ACO2
## 9 HALLMARK_ADIPOGENESIS ACOX1
## 10 HALLMARK_ADIPOGENESIS ADCY6
## # ℹ 7,312 more rows
Then:
library(clusterProfiler)
# Over-representation on a character vector of gene symbols.
ora <- enricher(gene = my_genes, TERM2GENE = term2gene)
# Or GSEA on a sorted, named numeric vector.
gsea <- GSEA(geneList = sorted_stats, TERM2GENE = term2gene)Mouse
The mouse tibble has the same shape. Note the collection codes
differ: hallmark is mh rather than h, and the
numbered collections run m1-m8.
## # A tibble: 7 × 1
## category_code
## <chr>
## 1 m1
## 2 m2
## 3 m3
## 4 m5
## 5 m7
## 6 m8
## 7 mh
mouse_hallmark <- msigdf.mouse %>%
filter(category_code == "mh")
mouse_pathways <- split(mouse_hallmark$symbol, mouse_hallmark$geneset)
length(mouse_pathways)## [1] 50
## [1] "Aph1a" "Arrb1" "Ccnd1" "Cul1" "Dll1" "Dtx1" "Dtx2" "Dtx4"
## [9] "Fbxw11" "Fzd1"
Mouse gene sets carry mouse symbols, so no ortholog mapping is needed if your data is already mouse.
Linking results back to MSigDB
msigdf.urls and msigdf.mouse.urls map every
gene set name to its page on the MSigDB website. Join enrichment output
against them to get clickable links in a report.
top_sets <- msigdf.human %>%
filter(category_code == "h") %>%
count(geneset, name = "n_genes") %>%
arrange(n_genes) %>%
head(5)
top_sets %>%
inner_join(msigdf.urls, by = "geneset") %>%
select(geneset, n_genes, url)## # A tibble: 5 × 3
## geneset n_genes url
## <chr> <int> <chr>
## 1 HALLMARK_NOTCH_SIGNALING 32 http://software.broadinstitute.or…
## 2 HALLMARK_ANGIOGENESIS 36 http://software.broadinstitute.or…
## 3 HALLMARK_HEDGEHOG_SIGNALING 36 http://software.broadinstitute.or…
## 4 HALLMARK_PANCREAS_BETA_CELLS 40 http://software.broadinstitute.or…
## 5 HALLMARK_WNT_BETA_CATENIN_SIGNALING 42 http://software.broadinstitute.or…
The same join applied to an fgsea result:
Session info
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Tahoe 26.6.2
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] C.UTF-8/C.UTF-8/C.UTF-8/C/C.UTF-8/C.UTF-8
##
## time zone: Europe/London
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] msigdf_2026.1 dplyr_1.2.1 knitr_1.51
##
## loaded via a namespace (and not attached):
## [1] vctrs_0.7.3 cli_3.6.6 rlang_1.3.0 xfun_0.60
## [5] otel_0.2.0 generics_0.1.4 textshaping_1.0.5 jsonlite_2.0.0
## [9] glue_1.8.1 htmltools_0.5.9 ragg_1.5.2 sass_0.4.10
## [13] rmarkdown_2.31 evaluate_1.0.5 jquerylib_0.1.4 tibble_3.3.1
## [17] fastmap_1.2.0 yaml_2.3.12 lifecycle_1.0.5 compiler_4.6.1
## [21] fs_2.1.0 htmlwidgets_1.6.4 pkgconfig_2.0.3 systemfonts_1.3.2
## [25] digest_0.6.39 R6_2.6.1 utf8_1.2.6 tidyselect_1.2.1
## [29] pillar_1.11.1 magrittr_2.0.5 bslib_0.12.0 withr_3.0.3
## [33] tools_4.6.1 pkgdown_2.2.1 cachem_1.1.0 desc_1.4.3