ClusterProfiler로 GO·KEGG·GSEA 농축분석하기 — DEG 목록에 생물학적 의미 입히기 (R 실전)

이 글에서 만들 것 — DESeq2로 차등발현 유전자(DEG) 목록을 뽑고 나면, 손에 쥐는 건 유전자 심볼 수백 개의 벽입니다. "그래서 무슨 생물학이 움직였나?"에 답하는 단계가 농축분석(enrichment analysis)입니다. R의 clusterProfiler로 두 갈래 — 과대표현 분석(ORA)과 유전자 집합 농축 분석(GSEA) — 을 돌려, GO·KEGG·MSigDB 경로로 DEG를 해석합니다. 유전자 ID 변환부터 dotplot·GSEA plot까지 코드 한 줄마다 한글 설명을 답니다. 볼케이노 plot → DESeq2 → 농축분석으로 이어지는 RNA-seq R 3부작의 완결편입니다.
🔗 관련 글 · 직전 단계(입력): DESeq2로 차등발현 분석 · 결과 시각화: Volcano plot으로 DEG 시각화 · 같은 R/Bioconductor 분석: phyloseq로 마이크로바이옴 분석
1. 왜 농축분석인가 — "유전자 200개가 떴다, 그래서?"
DESeq2를 돌리면 보통 수백 개의 차등발현 유전자(DEG)가 나옵니다. 문제는 그다음입니다. 유전자 심볼이 빽빽한 표를 들고 "이게 무슨 의미죠?"라는 질문 앞에 서면 막막합니다. 유전자 하나하나를 손으로 검색해 볼 수도 없습니다.
농축분석은 이 벽을 이야기로 바꿔 줍니다. 개별 유전자가 아니라 이미 정의된 유전자 집합(생물학적 경로·기능 범주)을 단위로 보고, "내 DEG가 특정 경로에 유난히 몰려 있는가"를 통계로 판정합니다. 결과는 "스테로이드 호르몬 반응·염증 신호 경로가 켜졌다"처럼 해석 가능한 문장으로 돌아옵니다.
유전자 집합의 출처는 셋이 표준입니다.
- 유전자 온톨로지(Gene Ontology, GO) — 유전자를 생물학적 과정(BP)·분자 기능(MF)·세포 구성요소(CC)로 분류한 거대 사전.
- 교토 유전자·유전체 백과사전(KEGG) — 대사·신호전달 경로(pathway) 지도.
- MSigDB — 특히 Hallmark 집합은 잘 정제된 50개 핵심 생물학 테마라 노이즈가 적습니다.
이 모두를 R 한 패키지로 다루는 도구가 clusterProfiler (Yu 외, 2012; Wu 외, 2021)입니다.
2. 두 갈래 — ORA vs GSEA
clusterProfiler의 농축분석은 접근이 둘로 갈립니다. 이 차이를 모르면 엉뚱한 함수를 씁니다.
| ORA (과대표현 분석) | GSEA (유전자 집합 농축 분석) | |
|---|---|---|
| 입력 | 유의 DEG 목록(컷오프 적용) | 전체 유전자 순위 목록(컷오프 없음) |
| 질문 | "내 DEG가 이 경로에 몰렸나?" | "이 경로 유전자들이 순위 위/아래로 쏠렸나?" |
| 검정 | 초기하검정(hypergeometric)·Fisher | 순위 기반 농축점수(ES) + 순열 |
| 함수 | enrichGO·enrichKEGG·enricher | gseGO·gseKEGG·GSEA |
| 장점 | 단순·직관적 | 컷오프 임의성 없음, 미세한 협응 변화 포착 |
| 단점 | 컷오프·배경에 민감, 순위정보 버림 | 순위 지표 선택 필요, 계산량 ↑ |
한 줄 정리: 이미 믿을 만한 DEG 목록이 있고 빠른 기능 요약을 원하면 ORA, 컷오프 없이 실험 전체를 통계적으로 풍부하게 보고 싶으면 GSEA입니다. 둘은 경쟁이 아니라 보완이라, 보통 같이 돌려 교차확인합니다.
3. 사전 준비 — 설치
핵심 패키지는 Bioconductor에, 유전자 집합 도우미 msigdbr는 CRAN에 있습니다. 최초 1회만 설치합니다.
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
# 농축분석 핵심 + 인간 유전자 주석 + 시각화
BiocManager::install(c("clusterProfiler", "org.Hs.eg.db",
"enrichplot", "DOSE"))
# MSigDB 유전자 집합 (CRAN)
install.packages("msigdbr")
생쥐 데이터라면 org.Hs.eg.db 대신 org.Mm.eg.db를, KEGG 코드는 "hsa" 대신 "mmu"를 씁니다.
4. STEP 1 — 입력과 유전자 ID 변환
농축분석의 첫 관문은 ID 변환입니다. clusterProfiler의 GO·KEGG 함수는 대개 ENTREZ ID로 동작하는데, RNA-seq 결과는 보통 ENSEMBL ID라서 변환이 필요합니다. 여기서 막히는 사람이 가장 많습니다.
library(clusterProfiler)
library(org.Hs.eg.db)
library(enrichplot)
# DESeq2 결과(res)에서 시작 — 행이름 = ENSEMBL ID, 열 = log2FoldChange, padj
res <- as.data.frame(res)
# ① ENSEMBL 버전 꼬리표 제거: ENSG00000000003.15 -> ENSG00000000003
res$ENSEMBL <- sub("\\..*", "", rownames(res))
# ② ENSEMBL -> ENTREZID·SYMBOL 변환
conv <- bitr(res$ENSEMBL, fromType = "ENSEMBL",
toType = c("ENTREZID", "SYMBOL"),
OrgDb = org.Hs.eg.db)
conv <- conv[!duplicated(conv$ENSEMBL), ] # 1:다 매핑 중복 제거
res <- merge(res, conv, by = "ENSEMBL")
bitr()가 띄우는 "x%가 매핑 실패" 경고는 에러가 아니라 정상입니다. 일부 ID는 ENTREZ에 대응이 없어 버려집니다. 핵심은.15같은 버전 꼬리표를 먼저 떼는 것 — 안 떼면 매핑이 통째로 0이 됩니다.
5. STEP 2 — ORA: GO 과대표현 분석
이제 유의 DEG를 골라 enrichGO()에 넣습니다. 배경(universe) 지정이 중요한데, 전체 유전체가 아니라 이번 실험에서 실제로 검정된 유전자를 배경으로 줘야 유의성이 부풀지 않습니다.
# 유의 DEG 선별 (padj < 0.05 & |log2FC| > 1)
sig <- subset(res, !is.na(padj) & padj < 0.05 & abs(log2FoldChange) > 1)
sig_entrez <- unique(sig$ENTREZID)
universe <- unique(res$ENTREZID) # 배경 = 검정에 쓰인 전체 유전자
ego <- enrichGO(gene = sig_entrez,
universe = universe,
OrgDb = org.Hs.eg.db,
keyType = "ENTREZID",
ont = "BP", # 생물학적 과정(BP/MF/CC 중 택)
pAdjustMethod = "BH",
pvalueCutoff = 0.05,
qvalueCutoff = 0.2,
readable = TRUE) # ENTREZ -> SYMBOL 자동 변환
head(ego)
dotplot(ego, showCategory = 15) # 상위 15개 GO 항목
barplot(ego, showCategory = 15)
readable = TRUE를 주면 결과의 유전자가 ENTREZ 숫자 대신 심볼로 표시돼 읽기 편합니다. ont은 생물학적 과정("BP")·분자 기능("MF")·세포 구성요소("CC") 중 고릅니다.
6. STEP 3 — KEGG 경로 분석
KEGG는 대사·신호 경로 단위라 해석이 직관적입니다. enrichKEGG()는 KEGG 서버를 실시간 조회하므로 인터넷이 필요합니다.
ekegg <- enrichKEGG(gene = sig_entrez,
organism = "hsa", # 사람 (생쥐는 "mmu")
pvalueCutoff = 0.05) # KEGG 서버 조회 → 인터넷 필요
ekegg <- setReadable(ekegg, OrgDb = org.Hs.eg.db, keyType = "ENTREZID")
dotplot(ekegg, showCategory = 15)
enrichKEGG는 결과에 ENTREZ ID를 그대로 두므로, setReadable()로 심볼로 바꿔 줍니다.

7. STEP 4 — GSEA: 순위 기반 농축
GSEA는 컷오프 없이 전체 유전자를 순위로 세운 목록이 입력입니다. 순위 지표를 무엇으로 둘지가 관건인데, DESeq2의 stat (Wald 통계량)이 효과 크기와 유의성을 한 값에 담아 가장 무난합니다. log2FoldChange나 sign(log2FC) * -log10(pvalue)도 쓰이지만, padj로 정렬하는 건 동점이 많아 피합니다.
# ① 순위 목록 만들기 — names = ENTREZID, 값 = stat, 내림차순 정렬
geneList <- res$stat
names(geneList) <- res$ENTREZID
geneList <- geneList[!is.na(names(geneList)) & !is.na(geneList)]
geneList <- geneList[!duplicated(names(geneList))] # 이름 중복 제거 (필수)
geneList <- sort(geneList, decreasing = TRUE) # 내림차순 (필수)
# ② GO 기반 GSEA
egse <- gseGO(geneList = geneList,
OrgDb = org.Hs.eg.db,
ont = "BP",
keyType = "ENTREZID",
minGSSize = 10,
maxGSSize = 500,
pvalueCutoff = 0.05,
eps = 0) # 아주 작은 p값까지 정확히
⚠️ 버전 주의 — 예전 코드·교재에 보이는gseGO(..., nPerm = 1000)의nPerm인자는 clusterProfiler 4.x에서 폐기됐습니다(내부적으로 fgsea 다중수준 알고리즘으로 바뀜). 빼고 쓰세요. 대신 아주 작은 p값이 필요하면eps = 0을 줍니다.
MSigDB의 잘 정제된 Hallmark 집합으로도 돌려 봅니다. 여기에 2025년에 바뀐 함정이 하나 있습니다.
library(msigdbr)
# 최신 msigdbr(v10+): category= 대신 collection=, 컬럼명은 ncbi_gene
m_df <- msigdbr(species = "Homo sapiens", collection = "H") # H = Hallmark
m_t2g <- dplyr::distinct(m_df, gs_name, ncbi_gene) # 집합명 + ENTREZ
egse_h <- GSEA(geneList, TERM2GENE = m_t2g, pvalueCutoff = 0.05)
⚠️ msigdbr 인자가 바뀌었다 — v10.0.0 (2025)부터category=→collection=,subcategory=→subcollection=로 바뀌었고, ENTREZ 컬럼 이름이entrez_gene→ncbi_gene으로 바뀌었습니다. 옛 인자도 당장은 동작하지만 경고가 뜨고,entrez_gene컬럼을 참조하던 옛 코드는 조용히 빈 결과가 됩니다. 옛 버전(10 미만)이라면category = "H"+entrez_gene컬럼을 쓰세요.
8. STEP 5 — 시각화 갤러리
농축분석의 강점은 그림입니다. enrichplot의 함수들로 결과를 한눈에 봅니다.
# GSEA 전용 그림
ridgeplot(egse, showCategory = 15) # 경로별 폴드체인지 분포
gseaplot2(egse, geneSetID = 1, # 가장 유의한 집합의 러닝 점수 곡선
title = egse$Description[1])
# 네트워크 그림 — 4.x에서는 먼저 pairwise_termsim() 필요
ego <- pairwise_termsim(ego)
emapplot(ego, showCategory = 20) # 항목 간 유사도 네트워크
cnetplot(ego, showCategory = 5) # 유전자–항목 연결망
cnetplot·emapplot·treeplot은 clusterProfiler 4.x에서 pairwise_termsim()을 먼저 실행해야 합니다(항목 간 유사도를 미리 계산). 빼먹으면 에러가 납니다. barplot()은 ORA 결과 전용이고(Count 기반), GSEA 결과는 dotplot()·ridgeplot()으로 봅니다.

9. 결과 해석 — 표 읽는 법
농축 결과 객체를 as.data.frame()으로 펼치면 핵심 열이 보입니다.
| 컬럼 | 어디서 | 의미 |
|---|---|---|
Description | ORA·GSEA | GO·KEGG·집합 이름 |
GeneRatio | ORA | 내 DEG 중 이 항목에 든 비율(k/n) |
BgRatio | ORA | 배경에서의 비율(M/N) — GeneRatio와 비교 |
Count | ORA | 이 항목에 든 DEG 개수 |
p.adjust | ORA·GSEA | BH 보정 p값 — 유의성 판단은 이걸로 |
NES | GSEA | 정규화 농축점수. 양수=상위(상향), 음수=하위(하향) |
core_enrichment | GSEA | 점수를 끌어올린 핵심(leading-edge) 유전자 |
ORA는 GeneRatio가 BgRatio보다 클수록 그 항목에 DEG가 몰렸다는 뜻이고, GSEA는 NES의 부호로 방향(상향/하향)을, 크기로 강도를 읽습니다.
10. 자주 발생하는 에러와 해결법
| 증상 | 원인 / 해결 |
|---|---|
no gene can be mapped | keyType 불일치 또는 ENSEMBL 버전 꼬리표. sub("\\..*","",id)로 떼고 keyType 확인 |
bitr의 "x% failed to map" 경고 | 정상 — 대응 없는 ID는 자동으로 버려짐 |
enrichKEGG가 멈추거나 오류 | KEGG 서버 실시간 조회라 인터넷 필요. 일시적이면 재시도. use_internal_data=TRUE는 옛 DB라 비권장 |
| GSEA 결과가 비거나 경고 | geneList 이름 중복·NA. unique·정렬(decreasing=TRUE) 확인 |
| msigdbr 결과가 비거나 오류 | 최신판은 collection= 인자 + ncbi_gene 컬럼. 첫 호출 시 인터넷 다운로드 |
nPerm 관련 경고 | 4.x에서 폐기 — nPerm 인자를 빼면 됨 |
| 유의 항목이 비현실적으로 많음 | 배경(universe) 미지정 → 검정에 쓰인 유전자로 지정 |
| 결과가 통째로 비유의 | 사람/생쥐 DB 혼동(org.Hs.eg.db·"hsa" ↔ org.Mm.eg.db·"mmu") |
11. 핵심 정리
- ✅ 농축분석 = DEG 목록을 생물학적 경로·기능으로 해석하는 단계. DESeq2 다음 수순.
- ✅ ORA (과대표현,
enrichGO/enrichKEGG)는 DEG 목록 기반, GSEA (gseGO/GSEA)는 전체 순위 기반 — 보완 관계. - ✅ 첫 관문은 ID 변환(
bitr): ENSEMBL 버전 꼬리표 제거 → ENTREZ. 막히면 십중팔구 여기. - ✅ ORA는 배경(universe)을 검정 유전자로 지정해야 유의성이 안 부풀린다.
- ✅ 최신 버전 함정 둘 — 4.x의
nPerm폐기, 그리고 msigdbr v10+의collection=인자·ncbi_gene컬럼. - ✅ 그림:
dotplot·ridgeplot·gseaplot2, 네트워크는pairwise_termsim()먼저.
12. 다음 학습 추천
- 📊 DESeq2로 RNA-seq 차등발현 분석하기 — 이 글의 입력(DEG)을 만드는 직전 단계 (필수 선행)
- 🌋 Volcano plot으로 DEG 시각화하기 — 같은 결과를 그림으로 (시각화)
- 🧬 phyloseq로 마이크로바이옴 분석하기 — 같은 R/Bioconductor 워크플로 (응용)
References
- Yu, G., Wang, L. G., Han, Y., & He, Q. Y. (2012). clusterProfiler: an R package for comparing biological themes among gene clusters. OMICS: A Journal of Integrative Biology, 16(5), 284–287.
- Wu, T., Hu, E., Xu, S., et al. (2021). clusterProfiler 4.0: A universal enrichment tool for interpreting omics data. The Innovation, 2(3), 100141.
- Subramanian, A., Tamayo, P., Mootha, V. K., et al. (2005). Gene set enrichment analysis: a knowledge-based approach for interpreting genome-wide expression profiles. PNAS, 102(43), 15545–15550.
- Ashburner, M., Ball, C. A., Blake, J. A., et al. (2000). Gene Ontology: tool for the unification of biology. Nature Genetics, 25(1), 25–29.
- Kanehisa, M., & Goto, S. (2000). KEGG: Kyoto Encyclopedia of Genes and Genomes. Nucleic Acids Research, 28(1), 27–30.
- Liberzon, A., Birger, C., Thorvaldsdóttir, H., et al. (2015). The Molecular Signatures Database (MSigDB) hallmark gene set collection. Cell Systems, 1(6), 417–425.
Pipette & Pipeline · A bio portfolio journal — bridging research, data, and industry.
'Code Bench > R Snippets' 카테고리의 다른 글
| ComplexHeatmap으로 발현 히트맵 그리기 — z-score부터 주석·분할까지 (R 실전) (0) | 2026.06.10 |
|---|---|
| R로 생존 분석하기 — 카플란-마이어 곡선부터 Cox 회귀까지 (survival 패키지 실전) (0) | 2026.06.09 |
| Seurat로 시작하는 단일세포 RNA-seq 분석 — pbmc3k로 QC부터 셀타입 주석까지 (R 실전) (0) | 2026.06.05 |
| DESeq2로 RNA-seq 차등발현(DEG) 분석하기 — 카운트부터 볼케이노까지 (R 실전) (0) | 2026.06.02 |
| Volcano plot 기초부터 심화까지 (0) | 2026.05.26 |