# 第 16 章 常见问题与排错

> 前置：已完成或正在运行本书流程。本章是“救火手册”：遇到报错先翻这里，按症状找对策。

## 本章目标

1. 能独立解决安装类问题：Rtools 缺失、Bioconductor 包编译失败、CellChat 依赖、Python 版本冲突、conda 环境混乱。
2. 掌握运行类问题的对策：内存不足、Seurat v5 与 v4 语法差异、cell2location 训练不收敛、BayesSpace 参数选择。
3. 解决可视化类问题：中文乱码、图像对齐、PDF 字体嵌入。
4. 理解结果类问题的根源：聚类不稳定、注释不准、通讯推断假阳性。
5. 会用交付前 10 项检查清单收尾。

## 16.1 安装类问题

安装问题占新手报错的 70%，且大多有固定解法。先记住一个总原则：**报错信息读最后 3–5 行**，通常真正的错误在末尾；前面几百行只是“上下文”。

### 16.1.1 Rtools 缺失（Windows）

- **症状**：安装含 C/C++ 源码的 R 包时报 `compilation failed for package 'xxx'` 或 `ERROR: dependencies 'Rcpp', 'RcppArmadillo' are not available`，且找不到 `make`。
- **原因**：R 编译源码包需要 Rtools 工具链，RStudio 不会自动装。
- **对策**：到 CRAN 官网（`https://cran.r-project.org/bin/windows/Rtools/`）下载与你的 R 版本匹配的 Rtools 安装；安装后**重启 RStudio**，并确认 PATH：

```r
# 目的：确认 Rtools 是否被 R 识别
Sys.which("make")
# 输出形如 C:\rtools43\usr\bin\make.exe 即为正常；若为空，说明 PATH 没生效
```

**运行结果解读**：若 `Sys.which("make")` 返回空字符串，先重启 RStudio；仍不行再手动把 `C:\rtools43\usr\bin` 加入系统 PATH（安装时勾选“Add to PATH”可避免此问题）。

### 16.1.2 Bioconductor 包编译失败

- **症状**：`BiocManager::install("BayesSpace")` 报错，如缺依赖、编译失败。
- **原因**：Bioconductor 包依赖较多，且部分需要编译；版本不匹配（R 太旧或太新）。
- **对策**：先更新 BiocManager 并安装依赖，失败时看是“缺哪个依赖”再单独装：

```r
# 目的：规范安装 Bioconductor 包
if (!requireNamespace("BiocManager", quietly = TRUE))
  install.packages("BiocManager")
BiocManager::install(version = "3.19")   # 与你的 R 版本匹配的 Bioc 版本
BiocManager::install(c("SingleCellExperiment", "BayesSpace"),
                     update = TRUE, ask = FALSE)
```

**运行结果解读**：安装成功则 `library(BayesSpace)` 无报错。若仍失败，把报错里 `ERROR:` 后面第一行的包名记下来，单独 `BiocManager::install("该包")` 逐个击破；Windows 上编译类失败优先检查 Rtools（16.1.1）。

### 16.1.3 CellChat 依赖（NMF / ComplexHeatmap）

- **症状**：`devtools::install_github("jinworks/CellChat")` 失败，或 `library(CellChat)` 报 `there is no package called 'NMF'`。
- **原因**：CellChat v2 依赖 NMF、ComplexHeatmap、presto 等包，未先装依赖时直接装 CellChat 会失败。
- **对策**：先装依赖再装 CellChat（NMF 与 ComplexHeatmap 优先）：

```r
# 目的：先装 CellChat 的硬依赖，再装 CellChat 本体
install.packages("NMF")
BiocManager::install("ComplexHeatmap")
if (!requireNamespace("remotes", quietly = TRUE)) install.packages("remotes")
remotes::install_github("jinworks/CellChat")
library(CellChat)   # 不报错即成功
```

**运行结果解读**：`library(CellChat)` 后应能看到 CellChat 版本号（v2.x）。若 NMF 编译失败，回 16.1.1 检查 Rtools；ComplexHeatmap 失败则检查 BiocManager 版本。

### 16.1.4 Python 版本冲突

- **症状**：`pip install scanpy` 后 `import scanpy` 报 `No module named`，或装 A 包把 B 包版本升级坏了。
- **原因**：系统 Python 里混装多个项目的包；不同教程要求不同 Python 版本。
- **对策**：**永远用 conda 环境隔离**，不要在 base 环境乱装：

```bash
# 目的：创建隔离的 Python 环境（本书统一用 python=3.10）
conda create -n spatial python=3.10 -y
conda activate spatial
pip install scanpy squidpy anndata matplotlib pandas numpy
```

**运行结果解读**：`conda activate spatial` 后执行第 6 章冒烟测试里的 Python 版本检查命令，能打印版本号即环境可用。以后每次开新终端先 `conda activate spatial`，避免“装了很多但 import 不到”。

### 16.1.5 conda 环境混乱

- **症状**：`conda env list` 里一堆环境，不知哪个能用；或环境坏了想重来。
- **对策**：坏环境直接删掉重建，别修（省时间）：

```bash
conda env list                       # 看有哪些环境
conda env remove -n spatial -y       # 删除坏环境
conda create -n spatial python=3.10 -y   # 重建
# 好习惯：建好后立刻导出 yaml，日后一条命令复现
conda env export -n spatial > envs/spatial-niche.yaml
conda env create -f envs/spatial-niche.yaml   # 别人/换机复现
```

**运行结果解读**：yaml 文件记录完整环境（含版本），这是“可复现”的基石（详见第 15 章方法写作）；本书第 06 章有完整的环境搭建流程。

## 16.2 运行类问题

### 16.2.1 内存不足

- **症状**：R 报 `Error: cannot allocate vector of size ...` 或 Python 报 `MemoryError`；Windows 下 R 直接崩。
- **原因**：Visium 单切片约 5000 spot × 2 万基因，Seurat 运行时约需 2–6 GB；cell2location 训练建议 ≥ 16 GB（00-TECH 第 2 节）。
- **对策**（按性价比排序）：

```r
# 对策 1：分析前只保留分析需要的细胞/基因，别贪多
obj <- subset(obj, subset = nFeature_Spatial > 200 &
                     nCount_Spatial > 500 & percent.mt < 25)
# 对策 2：SCTransform 只对高变基因做（默认已如此），减少内存占用
obj <- SCTransform(obj, assay = "Spatial",
                   variable.features.n = 2000, verbose = FALSE)
# 对策 3：用完的大对象及时清掉
rm(large_obj); gc()
```

```python
# 对策 4（Python）：确认矩阵是稀疏格式，别转成稠密
import scipy.sparse as sp
print(sp.issparse(adata.X))          # 应为 True
# adata.X = sp.csr_matrix(adata.X)   # 若不是稀疏，转回稀疏
```

**运行结果解读**：过滤后对象变小，`gc()` 释放内存；稀疏矩阵对 0 多的表达矩阵省 10 倍以上内存。还不行就换大内存机器（或云上 32 GB 实例），这是最后手段。

### 16.2.2 Seurat v5 与 v4 语法差异

- **症状**：按网上的 v4 教程写代码报错，如 `CreateSeuratObject` 参数不认、`GetAssayData` 用法变了。
- **原因**：本书用 Seurat v5（00-SPEC 第 2 节），v5 重构了 assay 存储（分层系统），部分 v4 函数/参数不再兼容。
- **关键差异速查**：

| 操作 | Seurat v4 | Seurat v5 |
| --- | --- | --- |
| 创建对象 | `CreateSeuratObject(counts, assay = "RNA")` | 空间数据用 `CreateSeuratObject(counts, assay = "Spatial")` |
| 归一化 | `NormalizeData()` / `SCTransform()` | `SCTransform(obj, assay = "Spatial")`（需指定 assay） |
| 取表达矩阵 | `GetAssayData(obj, slot = "data")` | `LayerData(obj, assay = "Spatial", layer = "data")` 或 `obj[["Spatial"]]$data` |
| 读取空间数据 | — | `Load10X_Spatial(data.dir, filename)`（v5 专用） |
| 多 assay 操作 | `obj@assays$RNA` | 分层存储，`JoinLayers()` 后再操作旧式函数 |

**对策**：以本书 08 章代码为准；遇到 v4 教程的代码，先把“取数据”与“归一化”两处换成 v5 写法（上面表格），其他流程（PCA → FindNeighbors → FindClusters → UMAP）两版一致。

```r
# v5 正确写法（本书 08 章同款）
obj <- Load10X_Spatial(data.dir = "data/visium/",
                       filename = "filtered_feature_bc_matrix.h5")
obj <- SCTransform(obj, assay = "Spatial", verbose = FALSE)
```

**运行结果解读**：v5 下 `SCTransform` 必须给 `assay = "Spatial"`，漏了会报找不到默认 assay 的错误；`Load10X_Spatial` 是 v5 读取 Visium 数据的标准入口。

### 16.2.3 cell2location 训练不收敛

- **症状**：`model.train()` 的 ELBO 损失不下降或震荡；丰度估计全是背景噪声。
- **对策**（按顺序试）：

```python
# 目的：调整训练参数促进收敛（参数需按你的数据微调）
model.train(
    max_epochs=300,          # 默认可能偏少，加大
    batch_size=512,          # 显存允许时加大，稳定梯度
    lr=0.002,                # 学习率过大易震荡
    use_gpu=True,            # 有 GPU 强烈建议
)
```

**运行结果解读**：训练后查看 `model.history["elbo_validation"]` 曲线，应单调下降并平台化。若仍不收敛：① 检查参考单细胞数据是否与 Visium 同组织类型、预处理是否一致；② 先跑小步实验（subset 1000 个 spot）验证流程，再全量训练；③ 训练前 `model.view_summary()` 看参数是否合理。cell2location 训练建议内存 ≥ 16 GB（00-TECH 第 2 节）。

### 16.2.4 BayesSpace q 太大太慢

- **症状**：`spatialCluster(q = 15)` 跑几小时不出结果，或结果碎成椒盐状。
- **原因**：q 是聚类数。q 越大参数越多、MCMC 迭代越慢；q 与数据实际结构不匹配时结果也差。
- **对策**：q 从小到大试（4 → 7 → 10），对比 `clusterPlot` 结果选最合理的；先调低迭代数做快速预实验：

```r
# 目的：用小 q + 少迭代快速看趋势，再决定正式参数
library(BayesSpace)
sce <- spatialPreprocess(sce, platform = "Visium", n.PCs = 15)
sce <- spatialCluster(sce, q = 7, platform = "Visium", d = 15, nrep = 5000)
clusterPlot(sce)   # 快速查看 q=7 的分割效果
```

**运行结果解读**：`nrep` 控制 MCMC 迭代次数，调低只用于快速预览；正式分析建议默认（或按 10 章说明调高）。选择 q 的实用标准：空间连续、边界清晰、与先验结构（如肿瘤/免疫分区）吻合，而不是 q 越大越好。若单切片就要跑很久，检查是否同时开了太多后台任务（内存竞争）。

## 16.3 可视化类问题

### 16.3.1 中文乱码

- **症状**：图里中文变成方块 `□□□`。
- **原因**：默认字体不含中文字形。解法见第 14 章 14.4，这里给速查版：

```r
# R 速查（showtext）
library(showtext); font_add("SimHei", "simhei.ttf"); showtext_auto()
```

```python
# Python 速查（matplotlib 字体注册）
from matplotlib import font_manager
font_manager.fontManager.addfont("C:/Windows/Fonts/simhei.ttf")
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "SimHei"
plt.rcParams["axes.unicode_minus"] = False
```

**运行结果解读**：设置后重画即正常。若 `simhei.ttf` 找不到，先确认字体文件路径（Windows 一般在 `C:/Windows/Fonts/`）；Linux 服务器没有微软字体，改用 `fonts-wqy-zenhei`（文泉驿）或思源黑体。

### 16.3.2 图像对齐问题

- **症状**：`SpatialDimPlot` 里的点与组织 H&E 图像对不上（偏移、镜像、旋转 90°）。
- **原因**：图像坐标系与 spot 坐标不一致；加载时没正确读取 `scalefactors_json.json`，或图像被旋转过。
- **对策**：检查图像缩放因子与坐标读取是否完整：

```r
# 目的：核对空间数据的关键缩放信息（00-TECH 第 1 节）
json <- rjson::fromJSON(file = "data/visium/spatial/scalefactors_json.json")
json$tissue_hires_scalef; json$spot_diameter_fullres
```

**运行结果解读**：`tissue_hires_scalef` 是图像缩放到高分辨率图的因子，`spot_diameter_fullres` 是 spot 直径（像素）。如果 spot 大小明显不对，多半是缩放因子读错或用了错误的图像文件（`tissue_lowres_image.png` 与 `tissue_hires_image.png` 对应不同缩放因子）。用 `Load10X_Spatial` 自动读取一般不会错；手写读取时务必用与图像匹配的因子。旋转/镜像问题多发生在自定义数据，检查你的坐标来源是否与图像同方向。

### 16.3.3 PDF 导出字体嵌入

- **症状**：PDF 在自己电脑正常，发给别人/投稿系统后中文或部分字符变方块。
- **原因**：PDF 未嵌入字体，对方机器没有对应字体时回退失败。
- **对策**：R 用 showtext 渲染后再导出（字体随 PDF 嵌入）；已有 PDF 可用 `embedFonts` 补救：

```r
# 目的：给已有的 PDF 补嵌字体
showtext_auto()
pdf("figures/figXX_cn.pdf", width = 4, height = 3.5)
print(p)
dev.off()
embedFonts("figures/figXX_cn.pdf")   # 强制嵌入字体
```

**运行结果解读**：`embedFonts` 执行后 PDF 文件体积通常增大（字体被打包进去了），用 Adobe Reader 打开检查“字体”面板，应显示“已嵌入”。Python 侧在 `savefig` 时设 `fonttype="cmap"` 并保证字体已注册（16.3.1）即可。

## 16.4 结果类问题

### 16.4.1 聚类不稳定

- **症状**：换 `resolution` 或重跑一遍，聚类结果差很多；UMAP 图每次长得不一样。
- **原因**：Louvain/Leiden 聚类含随机性；分辨率决定聚类粒度。
- **对策**：固定随机种子 + 明确报告分辨率，多分辨率比较后选稳定方案：

```r
set.seed(42)                                          # 固定随机种子，结果可复现
obj <- FindClusters(obj, resolution = 0.6)            # 记录所用 resolution
# 稳定性检查：跑 0.3 / 0.6 / 1.0 三个分辨率，选空间上最合理的一个
```

**运行结果解读**：`set.seed` 之后重复运行 `FindClusters` 得到相同结果；写论文时在方法里注明“resolution = 0.6，set.seed(42)”（第 15 章 15.4）。注意：固定种子只保证你自己的复现，换机器/换版本仍可能略有差异，属正常。

### 16.4.2 注释不准

- **症状**：去卷积/注释结果与先验不符（如免疫区域标出大量肿瘤细胞）；marker 打分注释明显错位。
- **原因**：marker 基因选择不当、参考单细胞数据不匹配、spot 混合信号难以区分相似细胞类型。
- **对策**：多策略交叉验证 + 空间一致性检查：

```r
# 对策 1：换 marker 集合（本书 09 章统一 marker 见 00-TECH 第 7 节）
# 对策 2：去卷积换参考数据（cell2location/RCTD/SPOTlight 交叉验证）
# 对策 3：检查注释的空间一致性——同一生态位的 spot 注释应相近
```

**运行结果解读**：注释是“假设而非事实”。若两种方法结论冲突，优先信“空间上连续、生物学合理”的那个；把注释方法、参考数据、阈值写进方法章节，方便审稿人评估。

### 16.4.3 通讯推断假阳性：为什么必须空间验证

- **症状**：CellChat 报出大量显著互作，但其中很多在空间上“配体细胞和受体细胞根本不挨着”。
- **原因**：CellChat 的通讯概率只基于表达量 + 配体-受体先验库（质量作用定律 + 排列检验），**不利用空间位置**（00-TECH 第 5 节）。表达量高但物理上不相邻的两个细胞群，会被算成“有通讯”，这就是假阳性来源。下图是“计算上显著、但必须用空间证据复核”的典型产物：

![图 16-1 生态位间差异通讯气泡图（模拟数据演示，同款见第 13 章 fig14）。红=侵袭前沿上调、蓝=下调；这类差异互作必须经空间共定位验证后才能写进结论。](figures/fig14_diff_comm.png){width=85%}

- **对策**：三重验证，缺一不可：

```python
# 验证 1：空间共定位（Squidpy 邻域富集，10 章已做）
sq.gr.nhood_enrichment(adata, cluster_key="celltype")
# 验证 2：空间活性（COMMOT，13 章，可选）
# import commot as ct; ct.tl.spatial_communication(adata, ...)
# 验证 3（最强）：实验——抗体中和、配体/受体敲除、荧光报告系统
```

**运行结果解读**：邻域富集 z 分数高的细胞对才支持“空间上真实发生通讯”；正文里写“通讯推断为计算预测，需空间共定位与实验验证”（00-SPEC 第 8 节）。**任何通讯结论必须配空间证据再写进论文**，这是本书反复强调的底线。

## 16.5 交付前检查清单

交稿/提交代码前，逐项打勾：

- [ ] 1. 环境可复现：`envs/spatial-niche.yaml` 已导出，换机 `conda env create -f` 能重建
- [ ] 2. 代码可运行：`code/01–10` 按顺序从头跑通一遍（至少验证关键脚本）
- [ ] 3. 随机种子已固定并在方法中说明（`set.seed(42)`）
- [ ] 4. 数据登记完整：来源、URL、md5、授权（data-inventory）
- [ ] 5. 图全部 300 dpi、PDF/PNG 双格式、图注含统计方法与样本量
- [ ] 6. 中文图无乱码，PDF 字体已嵌入
- [ ] 7. 正文数字与图表一致（15.3 对应表已过）
- [ ] 8. 统计方法（BH-FDR、排列检验）在正文与图注均已注明
- [ ] 9. 通讯结论均声明“计算预测”并附空间验证
- [ ] 10. 模拟数据图全部标注“（模拟数据演示）”

## 本章小结

排错的本质是“读报错 → 归因 → 最小化验证”。本章按四类问题给了速查表：**安装类**（Rtools、BiocManager、CellChat 依赖、conda 隔离）、**运行类**（内存、Seurat v5 语法、cell2location 收敛、BayesSpace 参数）、**可视化类**（中文、对齐、PDF 嵌入）、**结果类**（聚类、注释、通讯假阳性）。最后用 16.5 的 10 项检查清单收尾。遇到新报错时，记住：先读报错最后几行，再按“症状 → 原因 → 对策”定位，最后做最小实验验证修好了——这也是科学家解决问题的通用方法。

## 练习与思考

1. （概念题）为什么 `Sys.which("make")` 返回空字符串时，重装 R 包没有用？应该先做什么？
2. （动手题）在本机创建 `spatial` 环境并导出 `envs/spatial-niche.yaml`，然后删除环境、用 yaml 重建，验证可复现。
3. （动手题）故意把 Seurat v5 的 `SCTransform` 写成 v4 写法（不带 `assay`），运行并记录报错信息，再对照 16.2.2 修正。
4. （思考题）你的 CellChat 结果里有一对显著互作，但 Squidpy 邻域富集 z 分数接近 0。你会在论文里怎么写它？
5. （挑战题）对照 16.5 的 10 项检查清单，逐项检查你当前的交付物，写出每一项的通过/未通过状态与修复计划。
