3、创建 Giotto 对象:数据导入与对象构建
空间转录组分析的第一步,说白了就是把你的数据装进一个结构化的容器里。Giotto 的对象系统就是这个容器。你想想看,每个 spot 有表达值,有空间坐标,可能还有病理注释 —— 这些信息散落在不同文件里,操作起来非常容易出错。Giotto 对象的核心价值,就是把它们统一管理起来。
我个人习惯在项目一开始就把数据规整好,构建一个干净的 Giotto 对象。这一步做踏实了,后面的分析会顺畅很多。我在项目中遇到过好几次因为原始数据格式不对,导致后续聚类和可视化全乱套的情况。所以这一章,咱们就把数据导入和对象构建这件事彻底讲透。
3.1 构建 Giotto 对象需要哪些数据
构建一个标准的 Giotto 对象,你需要准备三类核心数据:
| 数据类型 | 格式要求 | 说明 |
|---|---|---|
| 表达矩阵 | 行为基因,列为细胞/spot | UMI 计数或标准化后的值,推荐使用稀疏矩阵节省内存 |
| 空间坐标 | 两列数值(x, y) | 每个细胞/spot 在组织切片上的物理位置 |
| 细胞元数据 | 可选,每行对应一个细胞 | 如样本分组、区域注释、病理评分等 |
表达矩阵是必选项,空间坐标也是必选项。没有坐标,你做的就是普通单细胞,不是空间转录组。元数据不是必须的,但我强烈建议你在构建对象时就带上。为什么?因为后续的过滤、可视化和差异分析都会频繁用到分组信息,提前绑定到对象里,省去反复 merge 的麻烦。
小贴士: 如果你手头的数据是 10X Visium 格式,Giotto 提供了专用的导入函数,会自动识别空间坐标和图像信息。后面我会演示具体用法。
3.2 数据导入的三种方式
实际项目中,数据来源五花八门。我总结下来,99% 的情况逃不出下面三种:
- 从 Giotto 内置示例数据导入 —— 学习阶段最快上手的方式。
- 从标准 10X Visium 输出导入 —— 最常见的情况,直接读 filtered_feature_bc_matrix 目录。
- 从自定义矩阵 + 自定义坐标导入 —— 适用于 SeqFISH、MERFISH、Slide-seq 等平台。
咱们先看第一种,用内置数据快速感受一下:
library(Giotto)
# 加载内置的 seqFISH+ 示例数据
gobject_example <- createGiottoObject(
expression = seqfish_expression,
spatial_locs = seqfish_locs,
cell_metadata = seqfish_meta
)
# 几秒钟就构建好了
print(gobject_example)
这段代码背后的逻辑很简单:把三个数据框塞进 createGiottoObject 函数,Giotto 会自动检查维度是否匹配、坐标是否有效。我刚开始用的时候,曾经把表达矩阵的行和列搞反了,结果构建出来的对象里基因数和细胞数完全颠倒了。嗯,那之后我每次都会用 dim() 先看一眼数据形状。
3.3 从 10X Visium 数据创建对象
10X Visium 是目前最主流的空间转录组平台。Giotto 专门提供了 createGiottoVisiumObject 这个函数,一条命令搞定所有:
# 指定 Visium 输出目录
visium_dir <- "data/Visium_FFPE_Human_Breast_Cancer/outs/"
gobject_visium <- createGiottoVisiumObject(
dir = visium_dir,
gene_column_index = 2, # 使用 gene_name 作为基因标识
h5_visium_path = NULL, # 不使用 h5 文件,直接读 mtx
instructions = createGiottoInstructions(
save_plot = TRUE,
show_plot = FALSE,
save_dir = "results/"
)
)
# 看看构建结果
names(gobject_visium)
关键参数说明: gene_column_index = 2 表示从 features.tsv 的第二列取基因名(即 gene_symbol),第一列是 ensembl ID。如果你习惯用 symbol 做分析,这个参数非常实用。默认值是 1,即使用 ensembl ID,我建议你改成 2,因为 symbol 更直观。
这个函数会自动读取 filtered_feature_bc_matrix 目录下的矩阵文件,同时把 tissue_positions_list.csv 里的空间坐标和 scale_factors.json 里的图像缩放参数一并导入。一步到位,非常省心。
我曾经在处理一个 Visium 数据集时,遇到了坐标文件里包含了很多"out of tissue"的 spots。嗯,这里要注意,createGiottoVisiumObject 默认只保留"in tissue"的 spots,但你最好在导入后用 filterGiotto 再确认一遍过滤阈值。
3.4 从自定义数据创建对象
如果你的数据不是 10X 标准格式,比如来自 SeqFISH+ 或 MERFISH,那就需要用通用的 createGiottoObject 函数手动构建。这里我给出一个完整的实操流程:
# 1. 加载表达矩阵(行为基因,列为细胞)
expr_mat <- readRDS("data/my_expr_matrix.rds")
cat("表达矩阵维度:", dim(expr_mat), "\n")
# 2. 加载空间坐标(必须包含 cell_ID、x、y 三列)
spatial_locs <- read.csv("data/my_spatial_locs.csv", row.names = 1)
head(spatial_locs)
# 3. 加载细胞元数据(可选,但推荐)
cell_meta <- read.csv("data/my_cell_metadata.csv", row.names = 1)
# 4. 构建 Giotto 对象
gobject_custom <- createGiottoObject(
expression = expr_mat,
spatial_locs = spatial_locs,
cell_metadata = cell_meta,
expression_feat = c("rna"), # 特征类型命名
instructions = createGiottoInstructions(
save_plot = TRUE,
show_plot = FALSE,
save_dir = "results/"
)
)
# 5. 快速检查对象
print(gobject_custom)
plot_spatial_grid(gobject_custom)
注意: 空间坐标的列名必须包含 sdimx 和 sdimy,或者你在 spatial_locs 中指定了对应的列名映射。如果列名不匹配,Giotto 会报错。我曾经因为坐标文件的列名是 "X" 和 "Y"(大写)而报错,改成 "sdimx" 和 "sdimy" 后就正常了。
3.5 Giotto 对象的结构速览
构建好对象后,我强烈建议你用 names() 或 slotNames() 看看里面到底装了啥。Giotto 对象是一个 S4 对象,主要包含以下几个关键槽位:
| 槽位 (Slot) | 内容 | 访问方式 |
|---|---|---|
expression |
原始表达矩阵及标准化后的数据 | gobject@expression |
spatial_locs |
细胞/spot 的空间坐标 | gobject@spatial_locs |
cell_metadata |
细胞级别的元数据 | gobject@cell_metadata |
gene_metadata |
基因级别的元数据 | gobject@gene_metadata |
instructions |
分析指令(参数、路径等) | gobject@instructions |
你不需要把所有槽位都背下来,但至少要知道:表达数据在 @expression 里,空间坐标在 @spatial_locs 里。后续的所有分析,包括标准化、降维、聚类、可视化,都会基于这些槽位的数据进行计算。
3.6 对象构建后的质量检查
对象构建完成后,别急着往下跑。花 2 分钟做一个快速的质量检查,能帮你提前发现很多问题。我个人的检查清单如下:
- 细胞数和基因数是否合理? 用
dim(gobject)查看。 - 空间坐标范围是否正常? 用
summary(gobject@spatial_locs)看一眼 x/y 的 min 和 max。 - 是否有大量零表达基因? 用
filterGiotto()的min_cells参数过滤。 - 细胞元数据是否完整? 用
head(gobject@cell_metadata)抽查几行。
# 快速检查示例
dim(gobject_custom)
summary(gobject_custom@spatial_locs)
# 查看前 100 个基因在多少细胞中表达
gene_detection <- rowSums(gobject_custom@expression$rna$raw > 0)
summary(gene_detection[1:100])
如果检测到异常,比如某个基因只在 1 个细胞中表达,或者某个 spot 的坐标落在了切片区域之外,建议回到数据导入步骤排查。不要带着问题数据往下跑,不然结果会越跑越偏。
核心原则: Giotto 对象是你整个分析流程的起点。起点稳,后面才快。我宁愿在对象构建环节多花 10 分钟检查数据,也不愿意在聚类之后发现是数据导入错了,回头重来。
上面的流程图清晰地展示了从原始数据到 Giotto 对象的完整构建路径。你可以看到,表达矩阵、空间坐标和元数据经过参数配置后,统一流入 createGiottoObject 函数,最终生成结构化的 Giotto 对象。对象构建完成后,紧接着就是质量检查与过滤,确保进入下游分析的数据是干净的。
说白了,这个流程就像盖房子打地基。你花在数据检查和对象构建上的每一分钟,都会在后续的分析中得到回报。我自己早期做空间转录组项目时,曾经因为坐标文件里混入了一个重复的 cell ID,导致整个对象构建失败,排查了半天才发现是数据预处理时 merge 没去重。后来我养成了一个习惯:在构建对象之前,先用 duplicated() 检查一下 rownames 是否有重复。
实践建议: 对于初学者,我推荐先用 Giotto 内置的示例数据(如 seqfish_expression)跑一遍完整流程,感受一下对象构建的各个步骤。然后再切换到自己的数据。这样即使报错,你也知道是数据格式的问题,而不是函数用错了。