
1. 为什么 scRNA-seq 的配体-受体推断总让人心里没底做单细胞测序的人大多经历过这个阶段跑完 CellChat 或 CellPhoneDB拿到一张漂亮的配体-受体互作热图CXCL12-CXCR4、KITLG-KIT、SELE-CD44 这些信号对赫然在列通讯网络图里间充质基质细胞MSC的信号输出强度排在前列。你会觉得故事已经成型了——MSC 通过 CXCL12 支持 HSPC 维持内皮细胞通过 SELE 参与髓系归巢逻辑链条清晰。但审稿人或组会上总有人问一句这些细胞真的在组织里挨着吗这个问题很致命。scRNA-seq 的配体-受体推断本质上是从 mRNA 表达量出发的统计关联它告诉你“A 细胞表达配体、B 细胞表达受体”但 A 和 B 是否处于同一空间邻域、是否共享同一个生态位、是否真的存在物理接触的可能转录组数据本身回答不了。你得到的是一张“可能性地图”不是“实际接触记录”。PCFCODEX空间蛋白成像补上的正是这一步。它能在同一张组织切片上同时标记几十种蛋白 marker保留每个细胞的空间坐标让你把 scRNA-seq 推断出的配体-受体候选列表放到真实组织邻域里去验证“它们是否相邻”。空间转录组适合发现基因表达的空间格局而 PCF 更适合围绕已知细胞群和蛋白 marker 做组织原位的关系确认。这篇内容聚焦一个具体场景你已经有 scRNA-seq 的配体-受体推断结果想用 CODEX 空间邻域观察来验证候选互作对。我会给出可复制的 config.toml 骨架和 settings.json 配置片段演示如何用 TaoToken 统一 Key 打通单细胞与空间蛋白数据的分析链路并给出配体-受体候选列表在空间邻域中的验证动作与判读标准。适合谁看正在做骨髓、肿瘤微环境、淋巴组织等复杂组织生态位研究手里有 scRNA-seq 数据、准备或已经拿到 CODEX 空间蛋白数据想把两层证据串起来的研究生和博后。不需要你是生信老手但需要你能跑 Python 脚本、能看懂基本的空间坐标数据结构。2. TaoToken 前置统一 Key 打通单细胞与空间蛋白分析链路2.1 为什么这里需要一个统一 API 通道单细胞和空间蛋白分析经常涉及多个工具链CellChat 跑配体-受体推断、Seurat 或 Scanpy 做细胞注释、CODEX 数据用 Python 做邻域分析、有时候还要调模型辅助解读结果或生成分析代码。如果每个工具都单独配一套 API Key 和 endpoint管理成本高换环境时容易出错。TaoToken 的做法是提供一个统一的 API 通道兼容 OpenAI 风格的接口调用。你可以在一个地方管理 Key然后在不同分析脚本里复用同一个 endpoint 和 Key。对于需要频繁切换工具链的空间组学流程来说这能省掉不少配置上的麻烦。官网地址https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 地址https://taotoken.net/api2.2 获取 Key 与可用入口进入控制台创建 API Key然后根据你的使用场景选择对应入口模型对话用于辅助解读配体-受体结果、生成分析思路https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodel-chatCoding Plan长期跑分析脚本、Agent 式编码https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcoding-plan控制台管理 Key 和用量https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentconsoleAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc注意Key 只存在环境变量或本地配置文件里不要硬编码进脚本后提交到公开仓库。我习惯用.env文件加python-dotenv读取下面配置片段会体现这一点。3. 可复制配置config.toml 骨架与 settings.json 片段3.1 config.toml 骨架这个 config.toml 用于统一管理分析流程中的 API 通道和空间邻域参数。放在项目根目录Python 脚本通过tomllibPython 3.11或tomli读取。# config.toml # 单细胞 CODEX 空间验证流程配置 [api] # TaoToken 统一 API 通道 base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY # 从环境变量读取不写明文 model gpt-4o # 按需替换为可用模型标识 timeout_seconds 60 max_retries 3 [scrna] # scRNA-seq 配体-受体推断结果路径 cellchat_output results/cellchat_net.rds lr_pairs_file results/lr_candidates.csv # 候选配体-受体列表 min_cells_per_group 10 pval_threshold 0.05 [codex] # CODEX 空间蛋白数据 cell_coords_file data/codex_cell_coords.csv # 列: cell_id, x, y, marker_* marker_panel [CD34, CD38, CXCL12, KIT, SELE, CD44, CD31, LEPR] neighborhood_radius_um 30 # 邻域半径按组织类型调整 min_neighbors 5 # 最少邻居数低于此值标记为孤立 [neighborhood] # 空间邻域分析参数 method radius # radius 或 knn knn_k 10 # methodknn 时生效 permutation_n 1000 # 置换检验次数 fdr_threshold 0.1 [output] result_dir results/spatial_validation save_plots true plot_format pdf3.2 settings.json 配置片段settings.json 用于存放更细粒度的运行参数方便在不同数据集之间切换时只改这一个文件。{ project_name: bm_niche_spatial_validation, scrna: { species: human, celltype_column: cell_type, ligand_receptor_pairs: [ {ligand: CXCL12, receptor: CXCR4, source: MSC, target: HSPC}, {ligand: KITLG, receptor: KIT, source: MSC, target: HSPC}, {ligand: SELE, receptor: CD44, source: Endothelial, target: Myeloid} ] }, codex: { panel_version: v2, normalization: arcsinh, cofactor: 5, segmentation_method: watershed }, spatial_validation: { test_type: permutation, distance_metric: euclidean, neighborhood_definition: radius_um, radius_um: 30, report_metrics: [ observed_neighbor_count, expected_neighbor_count, enrichment_score, permutation_pval, fdr ] }, api_usage: { use_for: [code_generation, result_interpretation], max_tokens_per_call: 2000, temperature: 0.2 } }3.3 环境变量与读取脚本# .env 文件不要提交到 git TAOTOKEN_API_KEY你的Key# load_config.py import os import json import tomllib from dotenv import load_dotenv load_dotenv() def load_config(config_pathconfig.toml, settings_pathsettings.json): with open(config_path, rb) as f: config tomllib.load(f) with open(settings_path, r, encodingutf-8) as f: settings json.load(f) api_key os.environ.get(config[api][api_key_env]) if not api_key: raise ValueError(f环境变量 {config[api][api_key_env]} 未设置) config[api][api_key] api_key return config, settings if __name__ __main__: cfg, st load_config() print(API base:, cfg[api][base_url]) print(邻域半径:, cfg[codex][neighborhood_radius_um], um) print(候选配体-受体对:, len(st[scrna][ligand_receptor_pairs]))跑一下确认配置能正常读取python load_config.py预期输出API base: https://taotoken.net/api 邻域半径: 30 um 候选配体-受体对: 34. 验证请求配体-受体候选列表在空间邻域中的验证动作4.1 从 scRNA-seq 结果提取候选列表假设你已经用 CellChat 跑完了配体-受体推断输出了一张互作表。把它整理成lr_candidates.csv至少包含这几列ligand,receptor,source_celltype,target_celltype,communication_prob,pval CXCL12,CXCR4,MSC,HSPC,0.021,0.003 KITLG,KIT,MSC,HSPC,0.015,0.011 SELE,CD44,Endothelial,Myeloid,0.009,0.0284.2 空间邻域验证的核心逻辑验证动作分三步第一步在 CODEX 数据中根据 marker 表达把每个细胞注释到对应细胞类型。比如 CXCL12 高表达且 LEPR 阳性的细胞归为 MSCCD34 和 KIT 双阳的归为 HSPC。第二步对每一对候选配体-受体计算 source 细胞和 target 细胞在空间上的邻近距离分布。具体做法是对每个 source 细胞找它半径 R 内的所有 target 细胞记录数量然后对所有 source-target 对做置换检验看实际观察到的邻接数量是否显著高于随机分布下的期望值。第三步计算 enrichment score 和 FDR筛选出在空间上显著邻近的配体-受体对。# spatial_validation.py import numpy as np import pandas as pd from scipy.spatial import cKDTree from statsmodels.stats.multitest import multipletests from load_config import load_config cfg, st load_config() radius cfg[codex][neighborhood_radius_um] n_perm cfg[neighborhood][permutation_n] fdr_thr cfg[neighborhood][fdr_threshold] # 读取 CODEX 细胞坐标和注释 coords_df pd.read_csv(cfg[codex][cell_coords_file]) # 假设已有 cell_type 列 source_mask coords_df[cell_type] MSC target_mask coords_df[cell_type] HSPC source_coords coords_df.loc[source_mask, [x, y]].values target_coords coords_df.loc[target_mask, [x, y]].values # 构建 KDTree target_tree cKDTree(target_coords) # 观察到的邻接数 observed np.array([len(target_tree.query_ball_point(s, radius)) for s in source_coords]) observed_mean observed.mean() # 置换检验随机打乱 target 坐标 perm_means [] rng np.random.default_rng(42) for _ in range(n_perm): shuffled target_coords[rng.permutation(len(target_coords))] tree cKDTree(shuffled) counts [len(tree.query_ball_point(s, radius)) for s in source_coords] perm_means.append(np.mean(counts)) perm_means np.array(perm_means) expected_mean perm_means.mean() enrichment observed_mean / expected_mean if expected_mean 0 else np.nan pval (np.sum(perm_means observed_mean) 1) / (n_perm 1) print(f观察邻接均值: {observed_mean:.2f}) print(f期望邻接均值: {expected_mean:.2f}) print(f富集分数: {enrichment:.2f}) print(f置换 p 值: {pval:.4f})4.3 批量跑多对配体-受体把上面的逻辑封装成函数遍历settings.json里的ligand_receptor_pairsdef validate_pair(coords_df, source_type, target_type, radius, n_perm1000): src coords_df[coords_df[cell_type] source_type][[x, y]].values tgt coords_df[coords_df[cell_type] target_type][[x, y]].values if len(src) 0 or len(tgt) 0: return None tree cKDTree(tgt) obs np.array([len(tree.query_ball_point(s, radius)) for s in src]) obs_mean obs.mean() rng np.random.default_rng(42) perm [] for _ in range(n_perm): shuf tgt[rng.permutation(len(tgt))] t cKDTree(shuf) perm.append(np.mean([len(t.query_ball_point(s, radius)) for s in src])) perm np.array(perm) exp_mean perm.mean() enrich obs_mean / exp_mean if exp_mean 0 else np.nan pval (np.sum(perm obs_mean) 1) / (n_perm 1) return { source: source_type, target: target_type, observed_mean: obs_mean, expected_mean: exp_mean, enrichment: enrich, pval: pval } results [] for pair in st[scrna][ligand_receptor_pairs]: r validate_pair(coords_df, pair[source], pair[target], radius, n_perm) if r: r[ligand] pair[ligand] r[receptor] pair[receptor] results.append(r) res_df pd.DataFrame(results) res_df[fdr] multipletests(res_df[pval], methodfdr_bh)[1] res_df.to_csv(f{cfg[output][result_dir]}/spatial_validation_results.csv, indexFalse) print(res_df[[ligand, receptor, enrichment, pval, fdr]])4.4 判读标准跑完上面的脚本你会得到一张表。怎么判断哪些配体-受体对在空间上真的成立指标含义判读建议enrichment 1.5实际邻接数明显高于随机期望提示空间邻近有生物学意义permutation pval 0.05置换检验显著排除随机分布的可能FDR 0.1多重检验校正后仍显著适合作为后续实验验证的候选observed_mean min_neighbors邻接数太少可能是细胞类型注释或分割问题如果某对配体-受体在 scRNA-seq 中通讯概率高但在空间邻域中 enrichment 接近 1 且 p 值不显著说明这两个细胞类型在组织中并不特别邻近推断的互作可能不是直接接触型的或者需要更大的邻域半径来捕捉。5. 本篇常见错排查5.1 邻域半径设多少合适这是最常被问的问题。30 μm 是一个常用的起点但不同组织差异很大。骨髓生态位中细胞密度高20-30 μm 可能就覆盖了直接邻接肿瘤组织中细胞间隙大可能需要 50 μm 甚至更大。我的做法是先用 30 μm 跑一遍然后画一个邻接数随半径变化的曲线看曲线在哪个半径开始趋于平缓。那个拐点附近的半径通常比较合理。radii [10, 20, 30, 40, 50, 60] for r in radii: tree cKDTree(target_coords) counts [len(tree.query_ball_point(s, r)) for s in source_coords] print(f半径 {r} um: 平均邻接数 {np.mean(counts):.2f})5.2 细胞类型注释对不上CODEX 的 marker panel 和 scRNA-seq 的细胞类型定义往往不完全一致。比如 scRNA-seq 里你分出了 MSC 亚群 1、2、3但 CODEX 只有 LEPR 和 CXCL12 两个 marker只能粗略归为 MSC。处理办法在 settings.json 里把 scRNA-seq 的亚群映射到 CODEX 能识别的粗粒度类型然后在结果解读时注明“空间验证在粗粒度层面进行”。不要强行用 scRNA-seq 的精细亚群去套 CODEX 数据那样只会引入噪声。5.3 置换检验跑得太慢如果 source 细胞有几千个、n_perm 设了 1000纯 Python 循环会很慢。两个优化方向一是用cKDTree.query_ball_point的向量化版本二是把 n_perm 降到 500 先看趋势确认有信号后再用 1000 或 2000 跑最终结果。# 向量化查询比逐个循环快很多 counts target_tree.query_ball_point(source_coords, radius, return_lengthTrue) observed_mean counts.mean()5.4 API 调用超时或返回空如果你用 TaoToken 的 API 辅助生成分析代码或解读结果遇到超时先检查timeout_seconds是否设得太短。网络波动时 60 秒可能不够可以调到 120。另外确认base_url写的是https://taotoken.net/api不要多加路径后缀。import openai client openai.OpenAI( api_keycfg[api][api_key], base_urlcfg[api][base_url], timeoutcfg[api][timeout_seconds] ) resp client.chat.completions.create( modelcfg[api][model], messages[{role: user, content: 解释一下空间邻域富集分数的含义}], temperature0.2 ) print(resp.choices[0].message.content)如果返回空内容先确认模型标识是否正确再检查 Key 是否有余额。控制台里能看到用量记录。5.5 CODEX 坐标文件和 marker 列对不上cell_coords_file要求至少有cell_id、x、y三列marker 列以marker_开头。如果你的文件列名不同在读取后做一次重命名coords_df pd.read_csv(cfg[codex][cell_coords_file]) coords_df coords_df.rename(columns{X_coord: x, Y_coord: y})确认cell_type列存在且取值和 settings.json 里的 source/target 名称一致。大小写敏感MSC和msc会被当成两种类型。6. 把两层证据串起来从候选列表到空间验证的完整链路回到最初的问题scRNA-seq 告诉你“哪些细胞可能交流”PCFCODEX告诉你“它们是否真的挨着”。这两层证据串起来你的配体-受体推断才从“计算预测”变成“有空间观察支撑的候选互作”。实际操作中我建议把流程固定成四步先从 CellChat 输出里筛出 p 值显著、通讯概率靠前的配体-受体对然后在 CODEX 数据里做细胞类型注释把 scRNA-seq 的精细亚群映射到 CODEX 能识别的粗粒度类型接着用上面的脚本跑空间邻域富集分析得到 enrichment 和 FDR最后把 scRNA-seq 的通讯概率和空间富集分数放在一张表里对照优先关注两层都显著的配体-受体对。这套流程跑通之后你手里就有一份“转录层推断 空间层验证”的候选列表后续做免疫荧光共染或功能实验时靶点选择会更有依据。如果你在配置 TaoToken 通道或跑空间邻域脚本时遇到报错可以先查接入文档里的接口说明或者到 API Keys 页面确认 Key 状态。长期跑分析流程的话Coding Plan 入口更适合频繁调用的场景。模型对话入口可以用来辅助解读富集结果或生成分析代码片段但记得把生物学判断留给自己——模型给的是思路不是结论。本文仅为科研技术方法介绍不涉及疾病诊断、治疗建议、疗效预测、用药指导或临床决策。文中提及的研究发现均来自学术文献相关分析结果需结合更多实验和研究进一步验证不构成任何医疗意见。参考文献Bandyopadhyay S, Duffy MP, Ahn KJ, et al. Mapping the cellular biogeography of human bone marrow niches using single-cell transcriptomics and proteomic imaging. Cell. 2024;187(12):3120-3140.e29.