数据下载与读取:从GEO/SRA到pandas/numpy

做Slide-seq分析的第一步,就是拿到原始数据。你可能觉得不就是下载嘛,点个链接就行。但真做起来,坑还是挺多的。我在带学生的时候,总有人卡在“为什么下载完打不开”这一步。

这一章我们就把整个过程捋清楚。从GEO/SRA怎么找数据,到用什么工具下载,最后怎么用pandas和numpy把数据读进内存。保证每一步都有代码,有经验,有坑。

先搞清楚数据在哪儿

Slide-seq的原始数据,大部分都存放在GEO(Gene Expression Omnibus)里。每次发文章,作者都会提交一个GSE号。比如经典的Slide-seqV2小鼠大脑数据,GSE号是GSE111111(我随便举的,别当真)。

你到了GEO页面,会看到几个关键区域:

  • Supplementary files:这里一般是处理好的表达矩阵、barcodes、features之类,可以直接用pandas读。
  • SRA run selector:这里指向SRA数据库,存放原始测序reads(fastq或sra格式)。如果你要做自己的pipeline,必须从这步开始。

我个人习惯是先看Supplementary files。如果人家已经给出了表达矩阵,我直接下载省时间。如果我要自己跑pipeline,或者文章没提供矩阵,才去SRA拉原始数据。

小提示:GEO页面底部通常有个“Series Matrix File(s)”链接,那是整理的样本信息表。我建议顺手下载下来,后面做metadata时很有用。

从SRA下载原始数据

SRA提供了两种原始数据格式:一是.sra(压缩二进制),二是.fastq(解压后的reads)。我推荐直接用prefetch和fastq-dump这套工具。

首先,你得安装SRA Toolkit。我用的是conda安装,稳得很:

conda install -c bioconda sra-tools

然后找到一个SRA accession,比如“SRR11111111”。用prefetch下载到本地:

prefetch SRR11111111

下载完是个.sra文件。我习惯把它转成fastq,用fastq-dump:

fastq-dump --split-files SRR11111111.sra

--split-files是啥意思?如果你的是双端测序,不加这个参数会给你合并成一个文件。我刚开始做的时候不知道,跑完一看只有一个大文件,还以为数据丢了。后来查文档才明白。所以,嗯,这里要注意一下。

踩坑经验:我曾经用默认参数跑了100多G的Slide-seq数据,结果发现都是单端reads,实际上人家是双端的。浪费了好几个小时。后来我每次下载前,都会先去SRA页面看一眼文库类型,再决定加不加--split-files。

Slide-seq数据特有的格式问题

Slide-seq和10X Visium不一样。Visium的barcode是已知的,直接能匹配。但Slide-seq的barcode是随机的bead序列,你得自己去匹配。原始fastq里,reads的结构通常是:

  • Read 1(25~30 bp):包含bead barcode + UMI
  • Read 2(50~100 bp):基因序列

很多工具(比如STARsolo)能直接从fastq处理,不需要你自己拆。但我个人有个小习惯:先用seqtk或者python脚本快速检查一下reads的前几个碱基,确认barcode位置和长度。避免后面跑了半天发现参数设错了。

zcat SRR11111111.fastq.gz | head -8

如果看到前25个碱基是ATCG随机组合,大概率就是barcode+UMI。

直接下载处理好的表达矩阵

如果你不想从fastq跑起,GEO上很多Slide-seq文章也直接提供了表达矩阵。常见的格式是:

  • counts matrix:通常是.tsv或.csv,行是基因,列是bead barcode
  • barcodes.tsv:每个bead的barcode列表
  • features.tsv:基因名或ID
  • bead metadata:例如每个bead的空间坐标x,y

我一般直接wget下来:

wget https://ftp.ncbi.nlm.nih.gov/geo/series/GSE111nnn/suppl/GSE111111_RAW.tar
tar -xvf GSE111111_RAW.tar

解压后就是几个文本文件。注意,有些文件是gzip压缩的,记得先解压:

gunzip *.gz

用pandas和numpy加载数据

到这里才算进入正题。假设你拿到了表达矩阵(矩阵文件),以及对应的barcode和坐标文件。我们用pandas来读。

先读取barcode和基因列表:

import pandas as pd
import numpy as np

barcodes = pd.read_csv('barcodes.tsv', header=None, names=['barcode'])
features = pd.read_csv('features.tsv', header=None, names=['gene_id', 'gene_name'])

然后读表达矩阵。注意矩阵格式:有的是一行一个gene、一列一个barcode的稀疏矩阵(market matrix格式)。Slide-seq常用的是.mtx格式(Market Exchange Format):

from scipy.io import mmread

matrix = mmread('matrix.mtx').tocsr()

如果你拿到的是稠密的.csv或.tsv,那更简单:

counts = pd.read_csv('counts.tsv', sep='\t', index_col=0)

有些时候,矩阵里有很多列是空白的(没有bead附着)。我的做法是先过滤掉all-zero列:

counts = counts.loc[:, counts.sum(axis=0) > 0]

完了加载坐标文件:

coords = pd.read_csv('bead_coords.csv')
coords.columns = ['barcode', 'x', 'y']

我个人习惯把barcode设成index,这样后面合并元数据更方便:

counts.columns = barcodes['barcode'].values
coords.set_index('barcode', inplace=True)

数据整合与快速检查

现在有了表达矩阵和坐标,我们可以做一个简单的Anndata对象(当然这一章我们先用numpy/pandas验证一下)。比如检查一下表达矩阵的维度,看看有多少beads和基因:

print(f"Matrix shape: {counts.shape}")
print(f"Number of beads: {counts.shape[1]}")
print(f"Number of genes: {counts.shape[0]}")

你想想看,如果矩阵的列数远小于barcode文件里的bead数量,那说明有很多beads没检测到表达。我遇到过这种情况,后来发现是因为原数据把低质量的beads提前过滤了。这不一定是错误,但需要你在后续分析中注意。

再检查一下坐标范围:

print(coords.describe())

如果x,y都集中在0~10000的范围,那基本正常。如果出现负坐标或者NaN,可能数据格式有问题。我曾经下载过一次老数据,坐标列名写成了“x_position”而不是“x”,结果合并时对不上。后来我用列名模糊匹配才找到。所以,读取之前先 print(coords.columns) 是一个好习惯。

常见数据格式对比

为了让你快速区分不同的下载方式,我整理了一个表:

数据来源 格式 读取方式 优点 缺点
SRA原始fastq .fastq / .fastq.gz 需pipeline预处理 完全控制预处理 慢,需较大计算资源
GEO表达矩阵(tsv) .tsv / .csv pd.read_csv 快速获得结果 可能被过滤过
GEO表达矩阵(mtx) .mtx + barcodes + features scipy.io.mmread 稀疏节省内存 需额外合并坐标
GEO metadata(坐标csv) .csv pd.read_csv 小巧易读 有时列名不规范

如果你只想做下游分析,建议直接用GEO提供的矩阵+坐标。如果你要自己跑定量,必须走SRA raw fastq路线。

本章知识:从原始数据到可分析矩阵

Slide-seq数据下载与读取流程 GEO / SRA SRA Toolkit下载 直接下载矩阵 fastq-dump 转fastq 定量工具 (STARsolo等) pd.read_csv / mmread pandas/numpy 表达矩阵 + 坐标 左侧流程:原始reads → 矩阵

上面的图展示了两种常见路线。左侧是完整pipeline,适合需要定制预处理的高端用户;右侧是快速上手路线,我推荐初学者先走右侧。

还有一些实用技巧

最后补充几个我经常用的小技能:

  • 内存不足怎么办? Slide-seq的矩阵可能很大(几十万beads × 两万基因)。我一般用pd.read_csv(..., chunksize=10000)分块读,或者转成scipy.sparse再操作。
  • 坐标和barcode对应不上? 我曾遇到过barcode文件里有很多“-1”后缀,但坐标文件里没有。用 .str.replace('-1', '') 清洗一下:
barcodes['barcode'] = barcodes['barcode'].str.replace('-1', '')
  • 文件编码问题:有些GEO数据用UTF-16编码,pandas读出来全是乱码。加个参数:encoding='utf-16-le' 就好了。

嗯,数据下载和读取这块看起来琐碎,但打好底子后面分析才能顺。你按照我上面列的那几个步骤走一遍,基本不会出大问题。