简介:abagen 是一个面向神经影像与计算神经科学领域的 Python 工具箱,用于获取、处理并准备艾伦人脑图谱(AHBA)微阵列表达数据。它解决的核心问题是:原始 AHBA 数据在折叠到感兴趣脑区、跨供体组合等环节存在大量分析选择差异,导致结果难以复现。该包提供可重现的工作流,适合具备一定 Python 基础、需要开展人脑基因表达与脑区关联分析的研究者。资源包共 131 个文件,约 3.85MB,以 52 个 py 源码文件为主体,辅以 27 个 gz 压缩数据、19 个 rst 文档、11 个 png 示意图及 yml、csv、json 等配置与数据文件,覆盖源码、文档、示例数据与构建脚本。目前已有 1798 人学习下载。借助该工具箱,读者可获得从原始 AHBA 数据到区域化、跨供体整合表达矩阵的完整处理链路,理解不同分析选择对下游结果的影响,并直接复用其模块化代码与示例数据,提升人脑转录组研究的可复现性与效率。
1. abagen 到底解决什么问题:从“基因表达 × 脑区”说起
做脑影像遗传学或者跨尺度建模的人,迟早会撞上一个需求:手里有一堆脑区(比如 HCP 的 360 个皮层分区、Schaefer 的 200/400 分区),想给每个脑区配一个“基因表达指纹”,然后拿去做关联分析、做机器学习特征、做受体分布约束。问题在于,人脑转录组数据不是随便就能拿到的——公开可用的主要来源是艾伦人脑图谱(Allen Human Brain Atlas, AHBA)的微阵列表达数据,而它原始形态是 6 个捐献者、几千个采样点、几万条探针,坐标、注释、归一化各成一摊。直接上手处理,光是“把采样点映射到脑区”这一步就能耗掉一周。
abagen 就是把这个流程工具化的一个 Python 工具箱。它做的事情可以一句话概括:把 AHBA 的微阵列表达数据,按你指定的脑图谱分区,聚合成“每个脑区一个表达向量”的矩阵。中间它替你处理了探针到基因的映射、样本到脑区的空间分配、跨捐献者的归一化、缺失基因的插补。对做脑区基因表达关联的从业者来说,这相当于把一条容易翻车的流水线封装成了几个函数调用。
它适合谁?如果你在做脑网络与基因表达的耦合分析、在构建脑区的转录组特征、在复现别人论文里的“基因表达-脑区属性”关联,abagen 基本是绕不开的起点。新手可以跟着默认参数跑通最小流程,熟手则需要理解它每一步的取舍,因为默认值不一定适合你的图谱和样本量。下面按“先立住概念、再动手复现、最后讲坑”的顺序拆开讲。
2. 微阵列数据到脑区矩阵:abagen 的四个核心步骤
2.1 为什么不能直接对原始探针做平均
AHBA 微阵列数据的原始结构是:每个捐献者一份表达矩阵,行是探针(probe),列是采样组织块(sample),值是表达强度。一个基因可能对应多个探针,不同探针测的是同一基因的不同区域,表达值可能差出好几倍。如果你直接把所有探针按脑区平均,等于把噪声和真实信号混在一起,后续关联分析基本不可信。
abagen 的处理逻辑是:先把探针映射到基因,对同一基因的多个探针做选择或聚合,再进入空间映射。这个顺序很关键——先基因级聚合,再脑区级聚合,而不是反过来。常见做法是取每个基因表达值最高的探针(max probe),也有用中位数或平均的。abagen 默认用的是“差异稳定性”策略:对每个基因,选在不同捐献者之间表达模式最一致的探针。这个选择直接影响最终矩阵的可靠性,后面避坑章节会展开。
2.2 采样点怎么落到你的脑图谱分区里
AHBA 的采样点是立体定位坐标(MNI 空间),而你的脑图谱是一组分区标签。abagen 要做的就是:把每个采样点的 MNI 坐标,分配到对应的分区标签上。这一步的难点在于,采样点不一定落在灰质内,也不一定落在你图谱覆盖的范围内,边界附近的点归属还可能模棱两可。
abagen 提供了几种分配策略,核心参数是atlas_info和tolerance。tolerance控制采样点离分区边界的容忍距离(单位毫米),默认是 2mm。意思是,如果一个采样点离某个分区边界在 2mm 以内,它仍可能被分配给该分区。这个值调大,纳入的采样点更多但归属更模糊;调小,归属更严格但可能丢掉大量采样点。实际用的时候,我一般会先跑默认值,看每个脑区最终有多少采样点支撑,如果某些脑区采样点数为零或个位数,就要考虑放宽 tolerance 或换图谱。
2.3 跨捐献者归一化:为什么必须做
AHBA 有 6 个捐献者,每个人的表达值量纲和分布都不一样。如果直接把 6 个人的数据拼在一起做脑区平均,相当于假设他们的表达水平可以直接比较,这显然不成立。abagen 默认对每个捐献者单独做归一化,用的是样本间的秩变换(rank-based)或 z-score,把每个人的表达分布拉到可比的尺度上。
这里有个容易忽略的点:归一化是在“样本”层面做的,不是“脑区”层面。也就是说,先对每个捐献者的所有采样点做归一化,再按脑区聚合。如果你反过来,先按脑区平均再归一化,跨捐献者的可比性会打折扣。abagen 的normalize参数控制这一步,默认是True,用的方法是"zscore"。如果你的数据本身已经做过标准化,或者你想保留原始量纲做特定分析,可以关掉,但大多数关联分析场景下建议保持开启。
2.4 缺失基因和缺失脑区怎么补
即使做完上面三步,你拿到的矩阵大概率还是有缺失值:某些基因在某些脑区没有采样点覆盖,或者某些脑区的采样点太少导致表达值不可靠。abagen 提供了插补选项,核心参数是missing。默认是"centroids",意思是对于缺失的脑区-基因对,用该基因在所有脑区的均值来填充。还有"interpolate"选项,用空间上邻近脑区的表达值做插值。
选哪个取决于你的分析目标。如果缺失比例很低(比如低于 5%),用"centroids"简单填充影响不大。如果缺失比例高,或者你关心空间连续性,"interpolate"更合理,但它假设表达值在空间上是平滑的,这个假设不一定对所有基因成立。我的习惯是:先跑一遍看缺失比例,超过 10% 就回头检查图谱和 tolerance 设置,而不是急着插补。
3. 用 abagen 跑通最小流程:从安装到拿到表达矩阵
3.1 环境准备与数据获取
abagen 是纯 Python 包,依赖 numpy、pandas、nibabel、scipy 这些常规科学计算库。安装本身不复杂,但数据获取是第一个门槛。AHBA 的微阵列数据需要从公开数据源下载,abagen 提供了fetch_microarray函数来拉取。注意,这个函数默认会把数据缓存到本地目录,第一次运行会下载几百 MB 的文件,网络不稳的话容易中断。
# 安装 abagen(建议在虚拟环境里做) # pip install abagen import abagen # 拉取 AHBA 微阵列数据,指定缓存目录 # data_dir 建议设在一个空间充足的路径下 microarray = abagen.fetch_microarray( data_dir="./ahba_data", # 缓存目录,首次下载后复用 donors="all", # 使用全部 6 个捐献者 verbose=1 # 打印下载进度 )这段代码的逻辑是:fetch_microarray会检查data_dir下是否已有数据,没有就去下载。donors="all"表示用全部捐献者,你也可以指定单个捐献者做敏感性分析。verbose=1会输出下载和解析的进度,第一次跑建议开着,方便判断是否卡住。下载完成后,microarray是一个字典,键是捐献者 ID,值是该捐献者的表达数据和样本信息。
3.2 指定脑图谱并生成表达矩阵
拿到数据后,下一步是告诉 abagen 你要用哪个脑图谱。abagen 内置了一些常用图谱的获取接口,也支持你传入自己的分区文件。下面以常用的皮层分区为例,走一遍完整流程。
import abagen # 获取一个内置的脑图谱(这里以某常用分区方案为例) # 实际使用时替换成你手头的图谱名称或本地文件路径 atlas = abagen.fetch_atlas( "schaefer_2018", # 图谱名称 n_rois=200, # 分区数量 data_dir="./ahba_data" ) # 生成脑区 × 基因的表达矩阵 expression = abagen.get_expression_data( atlas=atlas["image"], # 图谱的 NIfTI 文件路径 atlas_info=atlas["info"], # 图谱的分区标签信息 data_dir="./ahba_data", tolerance=2, # 采样点归属的边界容忍距离(mm) normalize=True, # 跨捐献者归一化 missing="centroids", # 缺失值用基因均值填充 verbose=1 ) # expression 是一个 DataFrame # 行是脑区,列是基因,值是表达水平 print(expression.shape)这段代码的关键参数需要逐个说清楚。atlas传的是图谱图像路径,atlas_info是分区标签的元信息(哪个标签对应哪个脑区名称)。tolerance=2是默认值,前面讲过它的作用。normalize=True开启跨捐献者归一化。missing="centroids"是缺失填充策略。verbose=1会打印每个步骤的进展,包括有多少采样点被分配到每个脑区,这个信息在排查问题时很有用。
跑完之后,expression的行数应该等于你的分区数(比如 200),列数是基因数(大约 1 万多到 2 万,取决于探针映射结果)。如果行数不对,说明图谱标签和图像不匹配;如果列数远少于预期,说明探针映射或基因过滤出了问题。
3.3 检查输出质量:三个必看的指标
拿到矩阵不等于能用。我一般会先看三个东西:每个脑区的采样点数、缺失值比例、以及表达值的分布范围。
import numpy as np # 检查每个脑区的采样点覆盖情况 # abagen 在 verbose 输出里会打印,也可以从返回的中间结果拿 # 这里假设你已经有了 expression 矩阵 # 缺失值比例 missing_ratio = expression.isna().mean().mean() print(f"整体缺失比例: {missing_ratio:.4f}") # 每个脑区的有效基因数 valid_per_region = expression.notna().sum(axis=1) print(f"有效基因数最少的脑区: {valid_per_region.min()}") print(f"有效基因数最多的脑区: {valid_per_region.max()}") # 表达值的整体分布 print(f"表达值范围: {np.nanmin(expression.values):.3f} 到 {np.nanmax(expression.values):.3f}")缺失比例低于 5% 通常可以接受,高于 10% 就要回头查。有效基因数最少的脑区如果只有几百,说明该脑区采样点太少,后续分析里这个脑区的表达特征不可靠。表达值范围如果出现极端值(比如超过 10 个标准差),可能是归一化没做好或者有异常采样点混入。
4. 参数怎么调:tolerance、normalize、missing 的取舍
4.1 tolerance 调大还是调小:先看采样点覆盖
tolerance是 abagen 里最需要根据数据实际情况调整的参数。它的单位是毫米,控制采样点离分区边界的容忍距离。默认 2mm 是一个折中值,但不是万能值。
如果你的图谱分区比较细(比如 400 个分区),分区边界密集,2mm 的容忍距离可能导致大量采样点被分配到错误的分区,或者一个采样点同时落在多个分区的容忍范围内。这时候应该把 tolerance 调小,比如 1mm 甚至 0.5mm,让归属更严格。代价是采样点利用率下降,某些小分区可能没有采样点。
反过来,如果你的图谱分区比较粗(比如 100 个分区以内),分区面积大,2mm 可能过于严格,导致边界附近的采样点被丢弃。这时候可以调到 3mm 或 4mm,提高采样点利用率。但要注意,调得太大,采样点的空间特异性就下降了,相当于用更模糊的空间信息换更多的样本量。
我的做法是:先用默认值跑一遍,看 verbose 输出里每个分区的采样点数。如果超过 20% 的分区采样点数少于 3 个,就逐步调大 tolerance,每次加 1mm,直到采样点覆盖改善但缺失比例不显著上升。如果采样点覆盖很好但缺失比例很高,反而要检查是不是 tolerance 太大导致归属混乱。
4.2 normalize 关掉的场景
normalize=True是默认值,适用于大多数跨捐献者分析。但有两种情况我会考虑关掉:一是我的分析只针对单个捐献者,不需要跨人可比;二是我后续要用自己的归一化流程,不想让 abagen 先做一遍。
关掉之后,表达值保留原始量纲,不同捐献者的数据直接拼接。这时候你必须自己处理跨捐献者的尺度差异,否则脑区平均会被某个捐献者的量纲主导。一个常见的补救做法是,在 abagen 输出之后,对每个捐献者的数据单独做 z-score,再合并。但这相当于把 abagen 内部做的事搬到外面做,容易出错,除非你有明确理由,否则不建议关。
4.3 missing 策略对下游分析的影响
missing="centroids"和missing="interpolate"的区别,本质上是“用全局均值填充”还是“用空间邻域填充”。前者简单,后者假设空间平滑。
如果你的下游分析是脑区之间的关联(比如基因表达相似性和功能连接相似性的关系),用"centroids"填充会引入一个常数偏移,可能人为拉高所有脑区之间的相似性。这时候"interpolate"更合理,因为它保留了空间结构。但如果缺失比例很低,两者的差异可以忽略。
还有一个选项是missing=None,即不填充,保留 NaN。这适合你后续要用自己的插补方法,或者你的分析框架能处理缺失值。但要注意,很多机器学习模型不接受 NaN,你得在进入模型前处理好。
5. 避坑指南:abagen 使用中的五个血泪教训
5.1 图谱标签和图像不匹配,导致脑区数对不上
现象:跑完get_expression_data,输出的行数不是你的分区数,或者某些脑区全是 NaN。
原因:atlas传的图像和atlas_info传的标签信息不是同一套。比如图像是 200 分区,标签信息却是 400 分区的,或者标签的编号方式和图像的强度值不对应。
解决:先单独加载图谱图像和标签信息,检查图像的唯一强度值是否和标签信息里的分区 ID 一一对应。可以用 nibabel 读图像,用 numpy 看唯一值,再和标签表比对。确认一致后再传入 abagen。
5.2 采样点归属为零,某些脑区完全没有数据
现象:verbose 输出里某些脑区的采样点数为 0,最终矩阵里这些脑区全是 NaN。
原因:这些脑区在 MNI 空间里的位置可能超出了 AHBA 采样点的覆盖范围,或者 tolerance 太小导致边界附近的采样点没被纳入。
解决:先确认这些脑区是否在 AHBA 的覆盖范围内(AHBA 主要覆盖皮层和部分皮层下结构,不是全脑)。如果在覆盖范围内但采样点为 0,逐步调大 tolerance。如果调大后仍然为 0,考虑换一个分区方案,或者接受这些脑区没有表达数据的事实,在下游分析里排除它们。
5.3 探针映射后基因数远少于预期
现象:最终矩阵的列数只有几千,而 AHBA 原始数据有 2 万多个基因。
原因:探针映射过程中,大量探针因为质量过滤被丢弃,或者多个探针映射到同一基因后只保留了一个,导致基因数减少。这是正常的,但如果减少得太厉害,可能是过滤阈值太严。
解决:检查 abagen 的探针过滤参数。默认会过滤掉表达值过低或变异过小的探针。如果你的数据质量本身不高,可以适当放宽过滤阈值。但要注意,放宽过滤会引入更多噪声,需要在基因数和数据质量之间权衡。
5.4 归一化后表达值出现极端值
现象:归一化后的表达值范围异常大,或者某些脑区的表达值明显偏离其他脑区。
原因:可能是某个捐献者的数据有异常采样点(比如组织质量差、表达值整体偏高或偏低),归一化没有完全消除这种偏差。也可能是 tolerance 设置不当,导致某个采样点被错误地分配到多个脑区。
解决:先检查每个捐献者的表达值分布,看是否有异常。如果有,考虑排除该捐献者或对该捐献者做额外的稳健归一化。同时检查 tolerance 设置,确保采样点归属合理。极端值如果只出现在少数脑区,可以在下游分析里做 winsorize 处理。
5.5 内存不足导致处理中断
现象:跑get_expression_data时程序崩溃,报内存错误。
原因:AHBA 原始数据加上中间计算过程,内存占用可能达到几个 GB。如果你的机器内存不足,或者同时跑了其他任务,容易崩。
解决:减少同时使用的捐献者数量(donors参数),或者分批次处理。也可以先把数据缓存到本地,避免重复下载和解析。如果内存实在紧张,考虑用missing=None减少插补计算量,或者换一台内存更大的机器。
6. 进阶技巧:把 abagen 输出接入你的分析流水线
6.1 用自定义图谱替换内置图谱
abagen 内置的图谱有限,实际研究里你很可能有自己的分区方案。接入自定义图谱的关键是准备好两个东西:图谱的 NIfTI 文件,以及一个包含分区 ID 和分区名称的 DataFrame。分区 ID 必须和图像里的强度值对应,分区名称可以自定义。
import pandas as pd import abagen # 自定义图谱信息 # atlas_info 必须包含 "id" 和 "name" 两列 # id 要和 NIfTI 图像里的强度值一致 custom_info = pd.DataFrame({ "id": [1, 2, 3, 4], # 分区 ID "name": ["region_A", "region_B", "region_C", "region_D"], "hemisphere": ["L", "L", "R", "R"] }) # 传入自定义图谱 expression_custom = abagen.get_expression_data( atlas="./my_atlas.nii.gz", # 你的图谱文件 atlas_info=custom_info, # 自定义标签信息 data_dir="./ahba_data", tolerance=2, normalize=True, missing="centroids" )这里的关键是atlas_info的列名必须匹配 abagen 的预期。id列对应图像强度值,name列是分区名称。如果你的图谱有左右半球区分,可以加hemisphere列,abagen 会在输出里保留这个信息。跑之前建议先用 nibabel 确认图像的强度值和id列完全一致,否则会出现分区错位。
6.2 把表达矩阵转成脑区相似性矩阵
拿到脑区 × 基因的表达矩阵后,常见的下游分析是计算脑区之间的表达相似性,然后和功能连接、结构连接做关联。这一步可以用 pandas 和 scipy 快速完成。
import pandas as pd import numpy as np from scipy.spatial.distance import pdist, squareform # 假设 expression 是脑区 × 基因的 DataFrame # 先处理缺失值(如果还有的话) expr_filled = expression.fillna(expression.mean()) # 计算脑区之间的相关性(表达相似性) # 用皮尔逊相关,也可以换成斯皮尔曼 corr_matrix = np.corrcoef(expr_filled.values) # 如果要做距离矩阵,用 1 - 相关 dist_matrix = 1 - corr_matrix # 转成 DataFrame 方便后续使用 corr_df = pd.DataFrame( corr_matrix, index=expression.index, columns=expression.index ) print(corr_df.shape)这段代码的逻辑是:先确保没有 NaN,然后对脑区 × 基因矩阵按行计算相关系数。np.corrcoef默认按行计算,所以每一行是一个脑区,结果矩阵的 (i, j) 元素是脑区 i 和脑区 j 的表达相似性。如果你要做的是脑区聚类或者网络分析,这个相似性矩阵就是输入。注意,如果基因数远大于脑区数,相关矩阵可能不稳定,可以考虑先做降维(比如 PCA)再算相似性。
6.3 验证表达矩阵可靠性的一个实用方法
怎么判断你拿到的表达矩阵是否可靠?一个实用的方法是做“留一捐献者”验证:每次排除一个捐献者,用剩下的捐献者生成表达矩阵,然后看不同版本之间的相关性。如果相关性很高(比如 > 0.9),说明结果对单个捐献者不敏感,可靠性较好。如果相关性低,说明某个捐献者对结果影响过大,需要检查该捐献者的数据质量。
import abagen import numpy as np donors = ["donor_1", "donor_2", "donor_3", "donor_4", "donor_5", "donor_6"] matrices = [] for holdout in donors: # 用除 holdout 之外的捐献者生成矩阵 remaining = [d for d in donors if d != holdout] expr = abagen.get_expression_data( atlas=atlas["image"], atlas_info=atlas["info"], data_dir="./ahba_data", donors=remaining, tolerance=2, normalize=True, missing="centroids", verbose=0 ) matrices.append(expr.values) # 计算不同版本之间的平均相关性 correlations = [] for i in range(len(matrices)): for j in range(i + 1, len(matrices)): # 展平后计算相关 r = np.corrcoef(matrices[i].flatten(), matrices[j].flatten())[0, 1] correlations.append(r) print(f"留一验证平均相关: {np.mean(correlations):.4f}") print(f"最低相关: {np.min(correlations):.4f}")这个验证的代价是计算量翻倍(每个捐献者跑一次),但能帮你判断结果是否稳健。如果最低相关低于 0.8,说明某个捐献者的数据可能有问题,值得单独检查。我一般会在正式分析前跑一次这个验证,确认矩阵可靠后再往下做。
6.4 一个容易忽略的细节:基因过滤的阈值
abagen 在探针映射阶段会做基因过滤,默认会去掉在一些脑区表达值恒定的基因。这个过滤的阈值会影响最终基因数。如果你的分析对基因数敏感(比如要做基因集富集分析),建议显式检查过滤后的基因列表,确认没有丢掉你关心的基因。可以在get_expression_data之后,用expression.columns查看保留的基因,和你预期的基因列表比对。如果发现关键基因被过滤掉了,回头调整探针过滤参数,或者手动把该基因加回来。
说到底,abagen 是一个把复杂流程标准化的工具,但标准化不等于万能。它的默认参数是为“通用场景”设计的,你的数据、你的图谱、你的分析目标,都可能需要微调。我自己的习惯是:每次用 abagen 之前,先花十分钟确认图谱和标签匹配、采样点覆盖合理、缺失比例可接受,再往下跑。这三步做完,后面翻车的概率会低很多。希望帮到你。
本文还有配套的精品资源,点击获取