te-geneset-gsea
TE-aware GSEA — runs clusterProfiler::GSEA on TE subfamily-ranked DE lists using the family/class GMTs produced by annotate-bulk-rnaseq-data (TE path). Use when running GSEA on TE subfamily ranks, whe
SKILL.mdskills/te-geneset-gsea/SKILL.md
GitHubTE Geneset GSEA (TE Family/Class GMT)
Overview
This skill covers GSEA on transposable-element expression data using the family/class GMT files produced by the annotate-bulk-rnaseq-data TE path. The ID space is TE subfamily (the gene_symbol column in the GMT), not Ensembl gene symbols — this is the critical distinction from bulk-rnaseq-gsea. The clusterProfiler::GSEA() machinery is shared at the algorithm level, but the T2G/T2N column names and the @geneSets slot fix differ (TE GMTs use gs_name/gene_symbol; bulk uses term/gene).
When to use this skill:
- Running GSEA against TE family or class gene sets from limma-voom DE results on the combined gene+TE DGEList
- The ranked list uses TE subfamily IDs as names (matching the GMT
gene_symbolcolumn) - Family GMT (
<id>_TE_family.gmt) or class GMT (<id>_TE_class.gmt) is available from the annotate step
When NOT to use this skill:
- Gene-symbol or MSigDB GSEA (Hallmark, KEGG, Reactome, GO) → use
bulk-rnaseq-gsea - You have Ensembl IDs as rownames → convert to subfamily IDs first (via
build_te_annotation)
Decision Tree
Running GSEA on TE expression data?
|
+-- Ranked list has TE subfamily IDs as names
| AND GMT is a TE family/class GMT from annotate-bulk-rnaseq-data?
| --> use this skill (te-geneset-gsea)
| |
| +-- Family-level sets (dozens of families)?
| | --> load <id>_TE_family.gmt (gs_name = family, gene_symbol = subfamily)
| |
| +-- Class-level sets (~6 classes, very few sets)?
| --> load <id>_TE_class.gmt (gs_name = class, gene_symbol = subfamily)
| NOTE: use relaxed min_size (e.g. 3) — default 5 drops most class sets
|
+-- Ranked list has gene symbols (Il2ra, Cd3e, ...)?
--> use bulk-rnaseq-gsea (MSigDB / custom-db path)
Quick Start
Minimal working example — family-level TE GSEA from limma-voom DE results.
library(clusterProfiler)
library(data.table)
# --- 1. Load GMT (gs_name = TE family, gene_symbol = TE subfamily) -----------
gmt_path <- "03_results/objects/EXP001_TE_family.gmt"
T2G <- fread(gmt_path, header = FALSE, col.names = c("gs_name", "gs_description", "gene_symbol"))
T2N <- unique(T2G[, .(gs_name, gs_description)])
# --- 2. Build ranked list (TE subfamilies as names, t-statistic as value) -----
de_te <- readRDS("03_results/objects/EXP001_combined_DGEList.rds")
# de_te$table must have is_te == TRUE rows; subfamily IDs are rownames
ranked <- sort(setNames(de_te$table$t[de_te$table$is_te], rownames(de_te$table)[de_te$table$is_te]),
decreasing = TRUE)
# --- 3. Overlap sanity check BEFORE running GSEA ----------------------------
overlap <- sum(T2G$gene_symbol %in% names(ranked))
message("Overlap: ", overlap, " / ", length(unique(T2G$gene_symbol)))
# If overlap < 30% of the GMT members, ID spaces don't match -> stop and debug
# --- 4. Run GSEA -------------------------------------------------------------
gsea_fam <- clusterProfiler::GSEA(
geneList = ranked,
TERM2GENE = T2G[, .(gs_name, gene_symbol)],
TERM2NAME = T2N,
pvalueCutoff = 1.0,
minGSSize = 5,
maxGSSize = 500,
seed = 123,
eps = 0
)
# --- 5. Fix @geneSets slot for enrichplot compatibility ----------------------
gsea_fam@geneSets <- split(T2G$gene_symbol, T2G$gs_name)
stopifnot(is(gsea_fam, "gseaResult"))
message("TE family GSEA: ", nrow(gsea_fam@result), " sets returned")
Verify it worked:
# ID-space check
stopifnot(sum(T2G$gene_symbol %in% names(ranked)) / length(unique(T2G$gene_symbol)) > 0.3)
# Non-empty result
stopifnot(nrow(gsea_fam@result) > 0)
# @geneSets slot populated (required for enrichplot::gseaplot2)
stopifnot(length(gsea_fam@geneSets) > 0)
Progressive Depth
Basic Usage
Load the GMT with fread or read.gmt. The three-column format is gs_name \t gs_description \t gene_symbol (one row per subfamily member). Use TERM2GENE = T2G[, .(gs_name, gene_symbol)] and TERM2NAME = T2N. Always run the overlap check before GSEA — if sum(T2G$gene_symbol %in% names(ranked)) == 0 the result is silently empty.
Intermediate Usage
Class-level GMT — relaxed min_size: The class GMT contains only ~6 sets (LINE, SINE, LTR, DNA, Simple_repeat, Low_complexity). The default minGSSize = 5 will retain the larger classes but drop small ones. Use minGSSize = 3 to keep all non-trivial class sets. Verify with nrow(gsea_fam@result) before reporting.
Normalizing results to CSV: run TE sets through bulkiRNA gs_test() and gs_to_master() (skill bulk-rnaseq-gsea). Label the provider "TE_FAMILY" or "TE_CLASS" with gsdb_register(database = ) so rows are distinguishable in the master table. Prefix pathway IDs with TE_FAMILY_ / TE_CLASS_ (SCREAMING_SNAKE) to avoid collisions with MSigDB IDs.
Advanced Usage
Combining TE and gene GSEA in one master table: Run bulk-rnaseq-gsea for gene pathways and te-geneset-gsea for TE sets, then idempotently append TE rows to master_gsea_table.csv. Use the filter(database != "TE_FAMILY") |> bind_rows(...) pattern from bulk-rnaseq-gsea references/master-tables.md — the 13-column schema is shared.
For the full GMT column contract and annotated code walkthroughs → see references/te-gsea.md.
Verification Checklist
After running this skill, confirm:
- Overlap check passed:
sum(T2G$gene_symbol %in% names(ranked)) / length(unique(T2G$gene_symbol)) > 0.3 - Non-empty result:
nrow(gsea_fam@result) > 0 - @geneSets slot fixed:
length(gsea_fam@geneSets) == length(unique(T2G$gs_name)) - ID prefix applied: pathway IDs in master table start with
TE_FAMILY_orTE_CLASS_ - Biological plausibility: LINE/SINE enrichment direction consistent with known TE derepression phenotype if applicable
Common Pitfalls
Pitfall: Wrong ID space — 0 results
- Symptom:
nrow(gsea_fam@result) == 0; overlap check shows0or near-zero. - Cause: Ranked list uses Ensembl gene IDs or gene symbols instead of TE subfamily IDs. The GMT
gene_symbolcolumn contains subfamily names (e.g.HAL1,L1Md_T,B1_Mm) — these must match the rownames of the ranked list. - Fix: Extract TE rows from the combined DGEList using
is_te == TRUEand confirm rownames are subfamily IDs. Re-runbuild_te_annotation(rownames(mat_te))if$gene_symbolis missing from the annotation.
Pitfall: Empty @geneSets slot breaks enrichplot
- Symptom:
enrichplot::gseaplot2()errors or produces blank plots. - Cause:
clusterProfiler::GSEA()does not always populate the@geneSetsslot from custom TERM2GENE inputs. - Fix: Always add after GSEA:
gsea_result@geneSets <- split(T2G$gene_symbol, T2G$gs_name). UseT2G$gene_symbolandT2G$gs_name— NOTT2G$gene/T2G$term(those columns do not exist in TE GMTs).
Pitfall: Class GMT drops all sets with default min_size
- Symptom:
nrow(gsea_class@result) == 0when using the class GMT; no error. - Cause: The class GMT has only ~6 gene sets. With the default
minGSSize = 5, sets with fewer than 5 member subfamilies are silently excluded. - Fix: Set
minGSSize = 3for class-level GSEA. Verify which sets survive withunique(gsea_class@result$ID).
Pitfall: Overlap check passes but results are biologically unexpected
- Symptom: GSEA runs, results non-empty, but enrichment is flat or reversed.
- Cause: The t-statistic ranking is on the combined gene+TE DGEList, but size factors may have been estimated from genes only. TE DE effect sizes relative to genes may be miscalibrated.
- Fix: Confirm size factors used in the combined DGEList came from the gene-only subset (this is the documented behavior of
create_combined_dge). Compare TE log-fold changes to standalone TE-only edgeR results as a sanity check.
Prerequisites
The TE path requires the project-vendored TE-RNAseq-toolkit v2.0.3 and an
analysis_config.yaml-based analysis layout.
Resources
- clusterProfiler: https://bioconductor.org/packages/release/bioc/html/clusterProfiler.html
- TE-RNAseq-toolkit v2.0.3:
01_modules/TE-RNAseq-toolkit/—R/create_te_genesets.R,R/te_utils.R - Deeper reference (load on demand):
references/te-gsea.md— full GMT column contract,gs_name/gene_symbolvsterm/genedistinction, annotated GSEA code, overlap-check pattern, class-GMT min_size guidance
When not to use
- Do not use for gene-symbol or MSigDB GSEA. Use bulk-rnaseq-gsea instead.
See also
annotate-bulk-rnaseq-data— Prerequisite (TE path); build the TE family/class GMTs and combined DGEListbulk-rnaseq-gsea— Alternative; gene-symbol or MSigDB GSEA (Hallmark/KEGG/Reactome), shares clusterProfiler machinery