First compute a running ES by hand with 10 genes, then look at a full-scale enrichment plot of 5,000 genes, and finally learn how to read NES, FDR and the leading edge.
GSEA stands for Gene Set Enrichment Analysis. ORA stands for Over-Representation Analysis; what people usually call "GO / KEGG enrichment analysis" is most often ORA.
ORA (for example a hypergeometric test on GO or KEGG terms) first cuts a DEG list with thresholds such as padj < 0.05 and |log2FC| > 1, then asks whether pathway members are over-represented in that list. GSEA uses no threshold. It ranks all genes by a differential metric and asks whether the gene set as a whole drifts toward one end of the ranked list.
ORA asks: are genes from a given pathway more common in the significant-gene list than random sampling would predict? It uses a hypergeometric test (equivalent to a one-sided Fisher's exact test) on this 2×2 table.
| In pathway | Not in pathway | Total | |
|---|---|---|---|
| Significant (DEG) | k | n − k | n |
| Not significant | K − k | N − K − n + k | N − n |
| Total | K | N − K | N |
N is the number of background genes (the universe), K the number of genes in the pathway, n the number of DEGs, and k the overlap. The p-value is the probability of drawing at least k pathway members when n genes are drawn at random from N:
Typical reports show GeneRatio = k/n and BgRatio = K/N. A GeneRatio clearly above the BgRatio means the pathway is over-represented among the DEGs.
| ORA (Over-Representation Analysis, threshold-based) | GSEA (Gene Set Enrichment Analysis, rank-based) | |
|---|---|---|
| Input | A DEG list (discrete) | A genome-wide ranked list L (continuous) |
| Threshold | Required; results are sensitive to it | None |
| Good at catching | A few genes with large changes | Many genes with small but consistent changes |
| Direction | Up and down usually run separately | The sign of NES gives the direction |
| Statistical test | Hypergeometric / Fisher's exact test | Running-sum ES with a permutation test |
| Cost | Fast and simple | Needs a permutation test and the full expression data |
| Common tools | clusterProfiler enrichGO / enrichKEGG, DAVID, g:Profiler, Enrichr | GSEA desktop, fgsea, clusterProfiler gseGO |
The Hallmark collection (MSigDB Collection H) distils thousands of overlapping MSigDB gene sets into 50 well-defined, minimally overlapping sets, each representing a specific biological state or process. Running these 50 first keeps the multiple-testing burden small and the results focused.
.gct and .cls files.stat, or sign(log2FC) × −log10(p). log2FC alone is not recommended because it ignores variance. Sort the list by r from high to low; collapse duplicate gene symbols first.h.all.v2024.1.Hs.symbols.gmt (the version number depends on when you download it). Usually only gene sets with 15–500 members present in the data are kept.This set has 200 genes. It represents the checkpoint and division machinery between the end of DNA replication and the start of mitosis: the cyclin B–CDK1 complex, PLK1 and AURKA that drive entry into mitosis, CHEK1 that brakes the cycle on DNA damage, the CDC25 phosphatases that activate CDK1 by dephosphorylation, and the spindle assembly checkpoint genes BUB1, MAD2L1 and CDC20.
| Scenario | Expected NES | Biological reading |
|---|---|---|
| Tumor vs normal tissue | Positive (+) | Cells proliferate actively, many enter M phase, and mitosis genes go up as a group |
| CDK4/6 inhibitor vs DMSO | Negative (−) | Cells arrest in G1 and cannot reach G2/M, so these genes go down as a group |
| Treatment unrelated to the cell cycle | ≈ 0, not significant | Members are scattered over the list with no collective shift |
Scenario: tumor vs normal tissue. The 10 genes below are already sorted by the ranking metric ri from high to low. Only 3 of them belong to the G2M set: PLK1, CDK1 and CHEK1 (marked green). The other 7 are non-members (hypoxia, glycolysis, extracellular matrix and metabolism genes).
← Swipe the plot sideways →
| Rank | Gene | In S? | ri | This step | Running ES |
|---|
Click any row to add the gene to the set or remove it; the plot and numbers update at once. “Reset” goes back to PLK1 / CDK1 / CHEK1.
| Rank | Gene | ri | Add / subtract | Running ES (fraction) | Decimal | |
|---|---|---|---|---|---|---|
| 1 | VEGFA | Miss | +2.5 | −1/7 | −1/7 | −0.1429 |
| 2 | PLK1 | Hit | +2.0 | +2.0/3.2 = +5/8 | −1/7 + 5/8 = 27/56 | +0.4821 |
| 3 | LDHA | Miss | +1.5 | −1/7 | 19/56 | +0.3393 |
| 4 | CDK1 | Hit | +1.0 | +1.0/3.2 = +5/16 | 73/112 (peak) | +0.6518 |
| 5 | SLC2A1 | Miss | +0.5 | −1/7 | 57/112 | +0.5089 |
| 6 | CHEK1 | Hit | −0.2 | +|−0.2|/3.2 = +1/16 | 57/112 + 7/112 = 4/7 | +0.5714 |
| 7 | COL1A1 | Miss | −0.8 | −1/7 | 3/7 | +0.4286 |
| 8 | CD36 | Miss | −1.2 | −1/7 | 2/7 | +0.2857 |
| 9 | ALB | Miss | −1.8 | −1/7 | 1/7 | +0.1429 |
| 10 | PPARG | Miss | −2.4 | −1/7 | 0 | 0.0000 |
Why it always ends at 0: the hits add up to (2.0 + 1.0 + 0.2) / 3.2 = 1, and the misses subtract 7 × 1/7 = 1. The difference must be 0.
Result: ES = +73/112 ≈ +0.6518, with the peak at rank 4 (CDK1). A positive value means G2M members lean toward the top of the list, i.e. they are up in the tumor.
Leading edge: for positive enrichment, take the members from rank 1 up to the peak: PLK1 and CDK1. CHEK1 is a member, but it sits after the peak and has r = −0.2, so it does not contribute to the ES and is not in the leading edge.
Scale the same rules up to 5,000 genes and 200 G2M members and you get the three-panel plot familiar from GSEA papers. The data below are simulated for three scenarios. The statistics are computed live in your browser from 1,000 gene-set permutations. Move the pointer over the plot to see the values at each position.
← Swipe the plot sideways →
| Panel | Shows | What to look for |
|---|---|---|
| Top: running ES curve | The cumulative score from left to right | A peak on the left above 0 means positive enrichment; a trough on the right below 0 means negative enrichment. The sharper the peak and the closer to the edge, the more tightly the members cluster at one end. |
| Middle: barcode | Each line is the position of one member gene | Dense on the left means up, dense on the right means down, evenly spread means no shift. The green shading is the leading-edge range. |
| Bottom: ranking metric | ri for all genes | Above 0 on the left means higher in the treatment group; below 0 on the right means higher in the control. The zero crossing divides up from down. |
| Measure | Meaning | Common threshold |
|---|---|---|
| ES | The running ES value farthest from 0 | Not compared directly (depends on gene set size) |
| NES | ES ÷ mean of same-sign permutation ES; comparable across gene sets | |NES| > 1.5 is strong (rule of thumb); the sign gives the direction |
| Nominal p | Permutation p value for one gene set, not corrected for multiple testing | For reference only |
| FDR q | Estimated false-positive fraction from the NES null of all gene sets | < 0.25 exploratory (GSEA’s own recommendation); < 0.05 for rigorous publication |
| FWER p | The most conservative family-wise error rate | < 0.05 means a very solid result |
# DESeq2 results in res (Tumor vs Normal), with a gene symbol column `symbol` added
library(msigdbr); library(fgsea); library(dplyr)
# From msigdbr 10 the full data live in a separate package, msigdbdf; install it once
h <- msigdbr(species = "Homo sapiens", collection = "H") # older versions: category = "H"
pw <- split(h$gene_symbol, h$gs_name)
rk <- res |> as.data.frame() |> filter(!is.na(stat), !is.na(symbol)) |>
group_by(symbol) |> summarise(stat = mean(stat)) # collapse duplicate genes
ranks <- sort(setNames(rk$stat, rk$symbol), decreasing = TRUE)
set.seed(42)
fg <- fgsea(pw, ranks, minSize = 15, maxSize = 500)
fg |> arrange(padj) |> select(pathway, NES, padj, size) |> head(10)
plotEnrichment(pw[["HALLMARK_G2M_CHECKPOINT"]], ranks) +
ggplot2::labs(title = "HALLMARK_G2M_CHECKPOINT")
fg$leadingEdge[[which(fg$pathway == "HALLMARK_G2M_CHECKPOINT")]]
fgsea’s padj is a Benjamini–Hochberg adjusted p value. It is computed differently from the FDR q of the GSEA desktop app but serves the same purpose. In the desktop app, suggested settings are: Permutations 1000, Enrichment statistic weighted, Metric Signal2Noise, Max/Min size 500/15.
| Topic | Misconception | Correct statement |
|---|---|---|
| Definition of ES | ES = argmax |Phit − Pmiss| | argmax returns a position. The ES is the running ES value farthest from 0, with its sign kept; the position is the rank of the peak (or trough). |
| NES | ES divided by the mean of all permutation ES values | Divide by the mean of same-sign permutation ES values: positive nulls for a positive ES, negative nulls (in absolute value) for a negative ES. |
| FDR | GSEA’s FDR is Benjamini–Hochberg | The original GSEA FDR is estimated directly from the NES null distribution of all gene sets, not by BH. BH is what fgsea and clusterProfiler use. |
| Leading edge | Every member of the gene set is in the leading edge | Only members before the peak (positive enrichment) or after the trough (negative enrichment) are. CHEK1 in section 4 is not. |
| Number of Hallmarks | Some entries are duplicated or share a code | There are 50 Hallmark sets, none duplicated, all named HALLMARK_…; for example HALLMARK_PEROXISOME and HALLMARK_SPERMATOGENESIS are two different sets. |