基因 ID 转换:Symbol / Ensembl / Entrez
跟完这篇,您能分清 Symbol、Ensembl、Entrez 三类基因 ID 的关系,知道为什么建议用 Ensembl 当主键,并且会用几行代码把它们一键互转、把「一个基因占好几行」的重复规规矩矩地去掉。
基因 ID 转换是几乎每个分析都躲不开的一步:上游比对给的是 Ensembl,做 KEGG 富集又要 Entrez,出图给人看还得换回 Symbol。转来转去最容易出两类问题——转丢了一批、转出来重名了。这篇就把这两件事讲透。
三类 ID 各是什么
同一个基因,在不同体系里有不同的「身份证号」。以人类的 TP53 为例:
| 类型 | 长什么样 | 特点 | 谁在用 |
|---|---|---|---|
| Gene Symbol | TP53 | 好记、给人看;会随版本更名、有别名、可能歧义 | 论文、图表、人眼阅读 |
| Ensembl ID | ENSG00000141510 | 稳定、唯一,带版本号后缀;机器友好 | 比对 / 定量输出、Ensembl 注释 |
| Entrez ID(NCBI Gene ID) | 7157 | 纯数字、稳定;不少工具默认要它 | KEGG 富集、NCBI 系工具 |
几点关系要记住:
- 三者是多对多映射,不是一一对应。一个 Symbol 可能对多个 Ensembl,一个 Ensembl 也可能对多个 Symbol。
- Ensembl ID 有物种前缀:人类基因是
ENSG,小鼠是ENSMUSG;转录本另有ENST/ENSMUST。别把基因层和转录本层的 ID 混用。 - Ensembl ID 常带版本号后缀(如
ENSG00000141510.11),小数点后的数字会随注释版本变化。转换前一般要先把它去掉,否则容易全部匹配不上(下面第三节讲)。
为什么建议用 Ensembl 当主键
存表达矩阵、做关联合并时,第一列(行名)用哪种 ID 当主键,直接决定后面顺不顺。建议是:用 Ensembl ID 当主键,Symbol 只作为一列人看的备注。
原因很直接:
| 对比项 | Gene Symbol | Ensembl ID |
|---|---|---|
| 是否唯一 | 否,有别名、有历史名 | 是 |
| 是否稳定 | 否,会随 HGNC 更新更名 | 是,改名也保留旧 ID |
| 机器可读性 | 差,大小写 / 别名带来歧义 | 好,无歧义 |
| 常见事故 | 被 Excel 改成日期(见末节) | 基本不会 |
一句话:Symbol 是给人看的,Ensembl 是给机器认的。 拿会变、会重、会被表格软件悄悄改掉的 Symbol 当主键,是很多「样本 / 基因对不上」问题的根源。手头只有 Symbol 也能做分析,但更稳妥的做法是先转成 Ensembl 当主键。表达矩阵整理的整体规范见表达矩阵与分组表整理规范。
转换前先去掉版本号
这是最容易被忽略、又最容易「全军覆没」的一步。带版本号的 Ensembl ID(ENSG00000141510.11)拿去和不带版本号的注释库匹配,会一个都对不上。先统一去掉:
# 去掉 Ensembl ID 的版本号后缀:ENSG00000141510.11 → ENSG00000141510
ids <- sub("\\.\\d+$", "", ids)
如果 ID 是表达矩阵的行名,就对行名做同样处理,顺手确认去版本号后没有产生重复:
rownames(counts) <- sub("\\.\\d+$", "", rownames(counts))
any(duplicated(rownames(counts))) # 应为 FALSE,若为 TRUE 见「处理一对多」一节
一键互转:三种常用做法
三种做法各有适用场景,任选其一即可。前两种用本地注释包、快且离线;第三种走 Ensembl 服务器、字段最全。
做法一:clusterProfiler::bitr()——最省事,一次转多种。 适合手上是一串基因、想快速换个 ID 类型。
library(clusterProfiler)
library(org.Hs.eg.db) # 人类注释库;小鼠换成 org.Mm.eg.db
# Symbol → Ensembl + Entrez,一次转两种目标类型
map <- bitr(c("TP53", "BRCA1", "KRAS"),
fromType = "SYMBOL",
toType = c("ENSEMBL", "ENTREZID"),
OrgDb = org.Hs.eg.db)
注意 bitr() 默认会直接丢掉转不上的基因,并打印丢了多少。这个提示别忽略——丢失比例大时要回头查是不是物种选错、或该先去版本号。
做法二:AnnotationDbi::mapIds()——按向量原位对应,适合加一列注释。 它返回和输入等长、顺序对齐的结果,特别适合「给矩阵行名配一列 Symbol」。
library(AnnotationDbi)
library(org.Hs.eg.db)
sym <- mapIds(org.Hs.eg.db,
keys = rownames(counts), # 一串 Ensembl ID(已去版本号)
column = "SYMBOL", # 想要的目标类型
keytype = "ENSEMBL", # 输入 ID 的类型
multiVals = "first") # 一对多时只取第一个(见下节)
做法三:biomaRt——字段最全,联网查 Ensembl。 适合要一次性拉齐 Ensembl、Symbol、Entrez、基因位置等多种字段。
library(biomaRt)
mart <- useEnsembl(biomart = "genes", dataset = "hsapiens_gene_ensembl")
map <- getBM(attributes = c("ensembl_gene_id", "hgnc_symbol", "entrezgene_id"),
filters = "ensembl_gene_id",
values = ids, # 输入的 Ensembl ID 向量
mart = mart)
三种做法的取舍:
| 做法 | 依赖 | 适合 | 注意 |
|---|---|---|---|
bitr() | 本地 OrgDb | 快速换 ID 类型 | 默认丢弃转不上的,看它打印的丢失数 |
mapIds() | 本地 OrgDb | 给矩阵配注释列 | 一对多要指定 multiVals |
biomaRt | 联网 Ensembl | 要多字段、要最新 | 结果版本随 Ensembl 更新,注明版本 |
处理一对多与去重
ID 之间是多对多,转换后几乎一定会遇到两种「一对多」。规范做法如下:
| 情形 | 会怎样 | 规范做法 |
|---|---|---|
| 一个 Ensembl → 多个 Symbol | 一个 ID 配出好几个名字 | mapIds 用 multiVals = "first" 取一个,但它是任取,重要基因请人工核对 |
| 多个 Ensembl → 一个 Symbol | 转 Symbol 后行名重复 | 每个 Symbol 只保留表达最高的一行(惯例,非铁律) |
转不上(结果为 NA) | 假基因、废弃 ID、纯别名 | 如实丢弃,并记录丢了多大比例 |
最推荐的做法其实是从源头绕开这个问题:主键始终用 Ensembl,Symbol 只当备注列,这样永远不会因为「换名字」而重复。只有确实需要以 Symbol 为行名(比如某些下游工具只认 Symbol)时,才需要下面这套按表达去重。它和整理矩阵时的去重是同一套逻辑:
library(dplyr)
library(org.Hs.eg.db)
expr2 <- counts %>%
mutate(symbol = mapIds(org.Hs.eg.db, rownames(.),
"SYMBOL", "ENSEMBL", multiVals = "first"),
.avg = rowMeans(across(where(is.numeric)))) %>% # 每行平均表达
filter(!is.na(symbol)) %>% # 丢掉没转上的
arrange(desc(.avg)) %>%
distinct(symbol, .keep_all = TRUE) # 每个 symbol 只留表达最高的一行
rownames(expr2) <- expr2$symbol
expr2 <- expr2 %>% select(-symbol, -.avg)
any(duplicated(rownames(expr2))) # 应为 FALSE
「按表达最高保留」是被广泛采用的惯例,不是唯一正确答案——按数据和目的,也可换成对同名行求和或取中位数(WGCNA 的 collapseRows() 就提供多种合并策略)。关键是方法要事先定好、全程一致,并在方法部分写清楚。
常见坑速查
最后这几条,踩中一个就可能悄悄把结果带偏:
- Excel 把基因名改成日期。
SEPT2、MARCH1、DEC1这类 Symbol 会被 Excel 自动转成日期或数字,且往往不易察觉。HGNC 为此已给部分基因改名(如MARCH1→MARCHF1)。对策:用 Ensembl 当主键存表;确实要在表格软件里开含 Symbol 的文件时,先把该列设为文本格式。 - 物种选错。 人类用
org.Hs.eg.db、KEGG 代号hsa;小鼠用org.Mm.eg.db、mmu。人鼠 Symbol 大小写还不同(人TP53vs 鼠Trp53),别直接套用。 - 版本号没去掉。 带
.11后缀去匹配注释库会几乎全丢,先做去版本号那一步。 - 注释库版本对不上数据。 您数据的注释版本和本地 OrgDb / biomaRt 的版本若差得远,老 ID 可能已废弃、转不上属正常;报告里注明所用注释版本。
- 丢失比例别不看。 无论
bitr()还是getBM()都会丢一部分基因。留意丢了多少,比例偏高时先排查上面几条,必要时在正文说明。
在平台上跑一遍
在百沐一下不写代码也能做这一步:把带任意一种 ID 的表格传上去,用一句话说清「把 Ensembl 转成 Symbol」或「转成 Entrez 做 KEGG 富集」,组学智能体会自动去版本号、按惯例去重、并把丢失比例告诉您,还给可复现的 R 代码。各类文件的 ID 与格式要求见上传数据的格式要求。
相关
- 表达矩阵与分组表整理规范 —— ID 转换的上游,主键与去重的整体规范
- 富集分析:GO / KEGG / GSEA —— KEGG 富集为什么要先转成 Entrez
- count / TPM / FPKM / CPM 该用哪个 —— 整理矩阵时另一件常犯错的事