资讯动态

GEO数据预处理全攻略:从GSE下载到构建干净表达矩阵

发布时间:2026/10/21 12:49:15 来源:尧图企业网站定制
做生信的人几乎都会遇到同样一个场景在GEO里翻到一个编号很顺眼的GSE心里想着赶紧下载、跑个差异分析、画出火山图结果卡在第一步GEO数据预处理上——文件下载不下来、表达矩阵格式看不懂、探针ID对不上基因名一天时间就没了。这篇就针对GEO数据预处理的上半段从拿到GSE编号开始到正确下载数据、读懂数据结构、构建出干净的基因表达矩阵为止。适合刚接触公共数据库、准备把GEO数据用于差异分析或可视化的读者。整个过程我会按照自己平时完整的实操顺序来写包括每一步的技术细节和踩过的坑尽量让基础一般的朋友也能照着操作下来。1. GEO的数据结构读懂GSE、GSM、GPL三个编号再动手先从一个常见问题说起。很多新手下载数据时只盯着GSE编号把页面当成一个“文件下载站”点开series matrix下载按钮就直接read.delim。结果读进来发现第一行全是感叹号矩阵列名是GSM编号行名是探针ID样本分组信息藏在元数据里根本没法直接用。要理解这些先得知道GEO里的编号体系是怎么工作的。1.1 一个GSE嵌套多个GSM平台GPL决定数据性质GEO里主要有三类标识。GSE是实验系列代表一个完整的研究例如“某个条件下测了多少样本、用了什么平台”。GSM是单个样本一个GSE下通常有多个GSM每一个GSM代表一次杂交或一个样品的测序结果也可能同一个样本重复测了几次于是有多个GSM。GPL是平台代表芯片或测序平台的具体设计每个GSM必须挂靠在一个GPL下数据预处理时探针注释和基因名转换全靠它。这个逻辑跟series matrix文件直接相关文件里一列是一个GSM行是GPL平台上的探针或基因。想验证理解可以在GSE页面的“Samples”表格里看到每个GSM和GPL的关系。如果一个GSE的数据来自多个不同平台页面通常会把样本按平台分组展示一眼就能看出来。有一个容易踩坑的场景同一篇论文可能把不同平台的数据放在同一个GSE里或者拆成多个系列编号分别上传。下载前一定要先看GSE页面的平台信息。如果是一个GSE包含两套GPL我会先把GSE拆成两个ExpressionSet分别处理千万别强行把两套平台的探针矩阵合并成一个。探针ID不兼容合并后的行名根本没有可比性后面差异分析会直接炸掉白跑一天。1.2 Series Matrix File里面到底是什么Series Matrix File是GEO为了方便普通用户做分析而生成的标准化表达矩阵文件文件名格式通常是“GSE编号_series_matrix.txt.gz”压缩后几十到几百兆不等。它的内部结构有点特殊一部分行以“!”开头是描述性元数据包括标题、摘要、样本分组说明等一部分行以“#”开头是对矩阵列的解释最后才是真正的表达矩阵数据。我第一次手动处理这个文件时盯着文本看了半天后来明白了一个类比这个文件就像一个“自带使用说明书的表格”前面几十行是说明书后面才是正文。正因为行起点不一样直接用基础read.table读很容易错位。所以要嘛用GEOquery的现成函数读要嘛自己写代码时把以“!”和“#”开头的行丢掉再读矩阵。理解这一点再看到别人的教程里写comment.char参数就不会觉得玄乎了。还有一个容易忽略的点series matrix文件里的表达矩阵并不是原始信号而是已经经过预处理的数值。芯片数据通常是平台厂商或作者用某套标准化流程算好的表达值测序数据则可能是作者提供的处理后矩阵。所以拿到文件后第一件事不是再做背景校正而是搞清楚里面的数值是什么量纲、什么单位。这个判断直接决定后续差异分析该选什么方法是预处理里面最容易被忽略的一环。1.3 第一步判断数据是芯片还是测序GEO里既有芯片数据也有高通量测序数据。虽然下载页面长得差不多预处理思路完全是两条线。芯片类数据的特征GPL平台编号下通常是探针设计表达矩阵行名是探针ID部分平台也直接给基因名。series matrix里的数值往往是做好的表达值绝大多数情况下可以直接用于线性模型做差异分析前提是数值分布没有严重异常个别情况需要再做跨样本归一化。测序类数据的特征表达矩阵行名可能是基因ID或转录本ID数值类型取决于作者上传的是raw counts还是RPKM、TPM之类的定量值。如果拿到的是raw counts不要直接拿去跑芯片数据的常规套路的——counts的方差结构与连续表达值不同需要走专门的统计流程后面第4节我会展开讲。一句话总结先看平台和文件说明再决定后续处理。别看到“表达矩阵”四个字就直接上代码数据性质判断错了后面分析全白搭。2. 下载之前想清楚拿表达矩阵还是拿原始文件在跑下载命令之前先花两分钟想清楚一个问题你要的是series matrix表达矩阵还是原始数据文件这个选择影响的不只是下载流量更影响下游分析的可信度和灵活性。2.1 两种下载目标的适用场景很多场景下series matrix就够用了用别人已经预处理好的表达值做下游差异分析、聚类、可视化快速复现论文图表的计算逻辑或者只是教学练手不打算统一处理原始数据。这种情况下下载series matrix是最省事、最稳的路径也是我平时第一选择。需要下载原始数据的情况也很明确平台或作者提供的series matrix量纲不好判断想统一用一套流程重新处理芯片数据要自己从头做标准化比如遇到CEL原始文件时想自己控制参数测序数据要重新比对定量或者原矩阵里某些样本缺失、文件有手工修改痕迹。这时候就必须回到原始数据虽然费时但每一步都可控后续出问题也好排查。我平时跑正式分析时会先把series matrix快速拉下来当作初版结果确认数据方向和分组设计没问题后如果确实有必要再回到原始数据做精细处理。这个顺序帮我避开了很多“下载了半天资源最后发现矩阵根本不能用”的尴尬。2.2 用GEOquery下载Series Matrix的标准操作先安装包一行命令就行if (!requireNamespace(BiocManager, quietly TRUE)) install.packages(BiocManager) BiocManager::install(GEOquery)下载主代码是library(GEOquery) options(download.file.method.GEOquery auto) gse - getGEO(GSE12345, GSEMatrix TRUE, AnnotGPL TRUE)这里GSE12345是示例编号实际换成你要的编号。GSEMatrix TRUE表示下载series matrix格式AnnotGPL TRUE表示同时获取GPL平台的注释信息这样后面fData里会有Gene Symbol等字段。getGEO返回的对象有个容易踩的坑当GSE只有一个平台时返回的是一个ExpressionSet对象不是list当GSE包含两个及以上平台时返回的是list每个元素对应一个平台。我刚学这步时写的是exprs(gse[[1]])结果单平台数据集直接报错。现在我的习惯是先判断类型if (is.list(gse)) { eset - gse[[1]] } else { eset - gse }这样不管什么格式都能稳住。如果数据集确实有多个平台就分别取gse[[1]]、gse[[2]]分开处理。注意多平台数据集的处理策略不能简单合并这个前面已经提过后面第3节还会再验证一下为什么。2.3 不知道GSE编号怎么查检索与筛选技巧去GEO官网按关键词检索是最直观的方式。在搜索框输入疾病名、细胞类型、处理条件等然后用页面筛选器缩窄范围类型选“Series”、平台选芯片或测序、物种选人或鼠等。注意别光看标题点进GSE页面后还要检查样本量和分组设计确认数据规模和分析需求匹配。另一种批量方式是用R包rentrez去查底层的Entrez检索数据库适合写脚本自动化检索的场景。比如想检索跟某个基因相关的GSElibrary(rentrez) res - entrez_search(db gds, term gene_name[All Fields] AND GSE[ETYP], retmax 50) res$ids得到GSE编号后继续用getGEO下载。这种方式的优势是能把全部结果一次性拉下来再筛选不用一个个网页点开劣势是对检索词构造有要求基本语法跟网页检索类似需要一点时间熟悉。2.4 大文件下载的兜底方案GEO服务器偶发不稳定或者网络被限制时getGEO可能卡死一直转圈没反应。这时候别硬等直接手动下载更稳。到GSE页面找到“Supplementary file”区域或者直接拼接标准文件名用浏览器或下载工具把series matrix文件拉下来。下载后用GEOquery离线读gse - getGEO(filename GSE12345_series_matrix.txt.gz)这个函数会直接从本地解析文件不走网络速度非常快。我习惯把下载好的原始文件统一放在一个目录里比如geo_cache/文件名保留原样方便后续重跑时直接读本地文件不用反复从服务器拉。另外getGEO支持destdir参数指定一个本地目录缓存下载文件gse - getGEO(GSE12345, GSEMatrix TRUE, AnnotGPL TRUE, destdir ./geo_cache)第二次再执行同样命令时如果缓存文件还在就不会重新下载。这个功能平时不起眼数据集一旦变大能省不少时间和流量。比如我处理一个包含上百个样本的测序数据集时第一次下载后缓存到本地后面改参数重新跑几乎秒开。3. 从ExpressionSet到干净的基因表达矩阵下载完成后真正干活的时候到了。目标是把ExpressionSet转成分析用的数据框表达矩阵、样本信息、基因注释三件套。这三样东西各干各的但分析时经常需要交叉使用所以一开始就要把它们整理清楚。3.1 提取三大件表达矩阵、样本信息、探针注释ExpressionSet就像一个盒子里面装了三种数据expr_mat - exprs(eset) # 表达矩阵行为探针/基因列为样本 pheno - pData(eset) # 样本信息行为样本列为各种描述 anno - fData(eset) # 探针注释行为探针列为注释字段拿到后先看维度确认数据规模跟自己预期一致dim(expr_mat) colnames(expr_mat)[1:5] rownames(expr_mat)[1:5]对芯片数据行名一般是探针ID对测序数据有可能是基因ID。样本列名是GSM编号看起来像随机字符串但不要试图用编号本身去猜分组分组信息在pheno里。这一步有一个我后来总结出来的经验任何时候都不要跳过dim()和colnames()检查哪怕只花十秒钟也能防止后续用错维度导致的各种诡异报错。还有一步经常被忽视表达矩阵里偶有缺失值或无穷值。先运行summary()和is.finite()检查一遍尤其注意有没有Inf。极端值不代表数据一定错但要在分析前决定怎么处理。我通常会先画一个样本箱线图一眼就能看出有没有哪个样本整体分布离谱如果真有异常样本后面差异分析的结果质量会大打折扣。3.2 探针ID转基因Symbol的两种做法芯片数据最烦的部分是探针ID到基因Symbol的转换。如果下载时开了AnnotGPL TRUEfData(eset)里通常会有“Gene Symbol”列。缺失这个列时很多教程会让你用各种GEO注释包其实核心思路是一样的。技巧是先用colnames(anno)查看有哪些注释列。不同平台、不同GPL注释包返回的列名不完全一样有的叫“Gene Symbol”有的叫“GENE_SYMBOL”还有的干脆没有。我最早遇到一个数据集fData里只有探针名和染色体位置没有基因名折腾了一天才确认是平台注释文件本身就没带这个字段。这时候只能去GPL页面的“Annotation”区下载完整注释表比如gpl - getGEO(GPL570) ann - Table(gpl) head(ann)拿到注释表后用match或merge把探针ID跟基因Symbol对应上这个过程跟普通表连接的逻辑是一样的。但这里有一个很关键的坑一个探针可能对应多个基因或者同一个基因对应多个探针。直接一对多映射会让后续矩阵行数变多、重复严重。最常见的处理方式是把同一基因下所有探针的表达值合并成一个值。合并函数可以用limma::avereps它对同一ID下多个探针取平均比较常用也可以用aggregate(..., FUN median)取中位数稳健性更好。我自己的习惯是先过滤掉没有基因名或基因名为“---”的探针再用avereps合并。这里记住一点合并前矩阵必须是纯数值包含任何非数值列都会报错。如果拿到的是已经整理好的基因表达矩阵行名直接是基因名那这一步可以跳过但要确认没有大量重复行名有的话仍然需要合并。3.3 样本分组信息的清洗与逻辑检查很多数据集的分组信息隐藏在pData的“characteristics_ch1”这类列里格式是“disease status: normal”这样带标签的文本。直接拿来用不行要先把标签去掉。我的通用做法是先看有哪些列名colnames(pheno)然后针对所有characteristics开头的列统一提取“:”后面的内容pheno$group - gsub(.*: , , pheno$characteristics_ch1) table(pheno$group)注意不同数据集的列名和格式差异很大。有的列名是“source name”有的分组写在“title”里。遇到不确定的情况把pheno表格全部打印出来人工核对一遍就清楚了。这一步懒不得因为分组信息一旦弄错后面整个差异分析的结果都是错的尤其做临床数据时一组标签错位就可能让结论彻底翻车。另外如果series matrix文件里声明有配对样本比如同一个患者治疗前后两个样本字段里通常有“paired”或“patient id”之类的注释这时候需要额外设计配对检验。在预处理阶段就把这个字段提取好比分析时抓瞎强。我通常在整理分组信息时同时做一个样本编号到分组编号的映射表保存成一个CSV后续任何分析都能直接复用。3.4 一次快速质控分布、PCA和相关性的使用矩阵和注释都准备好了花十分钟做三个快速检查很多数据问题就能提前暴露。第一个是箱线图看每个样本的表达值分布是否正常boxplot(log2(expr_mat 1), las 2, main Expression distribution)如果某个样本的箱体整体偏移特别大要么是批次效应要么是处理问题。在去掉这类样本前先结合分组信息和实验记录判断原因别一言不合就删除。样本不是代码里的垃圾变量删掉了想恢复就得重新下载。第二个是PCA用所有基因表达值做主成分分析把前两个主成分画出来观察样本是否按分组自然聚在一起pca - prcomp(t(expr_mat), scale. TRUE) plot(pca$x[, 1:2], col as.factor(pheno$group), pch 19)PCA图里有明显“按批次分开”的模式时说明批次效应强后续要么在差异分析模型里加批次项要么用专门算法校正。在预处理阶段先看到现象总比分析做到一半才发现要好。第三个是样本相关性热图用一个简单的cor()函数加heatmap()就能画。通过热图能快速判断有没有样本放错分组、重复样本是否真的重复等低级错误。我见过一个数据集分组的标签写反了PCA和热图都显示出两个“群”看起来像真实的生物学差异最后发现只是样本信息读取时搞错了行顺序。这套质控流程不算复杂但能避免的坑非常多。我每次拿到新数据都会先跑一遍跑完再考虑差异分析心里就有底了。4. 高频翻车点与排查记录最后这部分汇集我在处理GEO数据时真正遇到过的几个问题。如果你也踩过类似的坑多半能在这里找到对应的解法。4.1 下载连接中断或超时现象是getGEO一直转圈或者报“cannot open connection”之类的错误。这种问题大多不是代码问题而是网络环境或镜像不稳定。我的排查顺序是这样的先试options(download.file.method.GEOquery libcurl)有时能换底层下载方式解决。接着把getGEO的destdir指到本地缓存生效后就不用重复下载整个文件。再不行直接用浏览器或下载工具手动下载series matrix再getGEO(filename...)读入。不要在一个函数上死磕换个通道往往30秒内解决。另外一个经验是尽量不要在数据分析的高峰时段下载有的数据集文件很大网络高峰期下载失败的概率会明显增加。4.2 fData里没有Gene Symbol列出现这种情况先确认下载时是否设置了AnnotGPL TRUE。如果设置了还是没有那就是平台注释本身的问题。新平台或小众平台经常缺注释字段。这种时候有两个替代路径。第一去GPL页面下载完整注释表用探针ID匹配拿基因Symbol列。第二如果平台很老但底层数据库没有收录完整注释可以搜同平台的其他注释版本比如官方注释包或旧版注释文件。两边的ID版本如果不一致旧版本用旧Symbol新版本又换成新命名最好再做一次ID转换校验。这个坑我遇到过不少次每次都花不少时间。后来养成了一个习惯下载完数据先看一眼fData的列名再继续别等分析到一半才发现注释缺失到时候重新下载、重新匹配成本翻倍。4.3 counts矩阵和芯片表达值混着用的后果测序数据如果拿到的是raw counts直接把它当成普通表达值跑常规差异分析结果通常会有一堆“显著基因”但仔细看一半是低表达基因在异常波动。这不是算法不行而是counts的统计分布跟连续表达值不一样。正确的做法是明确数值类型后counts就用专门针对测序数据的流程经过内建标准化再做差异分析如果是RPKM、TPM或作者已处理的表达矩阵再考虑用常规线性模型。不要被“GEO上作者已经帮你处理过”这句话冲昏头。作者处理用的参数未必适合你的下游分析先确认数值量纲永远是第一优先级。我见过有人拿着一个raw counts矩阵直接跑聚类结果不同样本由于测序深度差异被强行分成两类差点得出一个完全离谱的生物学结论。4.4 探针一对多基因和缺失值芯片平台里一个探针对应多个基因常见于探针设计时期基因注释不完整。此时杂交信号来自哪个基因并不清楚直接拆分到两个基因不太严谨。我通常的做法是如果两个基因中有一个是明确的编码基因、另一个是假基因或非编码就保留编码基因两个都是编码基因且分不开就丢弃该探针如果用的是合并策略就取平均值这样至少保证后续行名为唯一基因。缺失值处理也一样我一般不用随机填充。如果一个探针在多数样本里是NA直接剔除只在少数样本里缺失可以考虑对该样本取整个矩阵的中位数填充。这个选择没有绝对标准关键是处理过程要记下来写在方法里方便后续复核。数据清洗时做的每一个决策都可能影响最终差异基因列表所以记录本身就是分析的一部分。从数据下载到矩阵构建这一整套流程我在不同数据集上重复过很多次。说句实在话GEO预处理看着步骤简单真正费时间的是判断数据性质和处理各种意外。把上面这些环节提前到动手之前考虑清楚后面跑差异分析会顺畅很多。下一篇我会写数据预处理的下半段归一化补全、批次效应合并和差异分析的标准流程到时候咱们接着聊。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑