# 第 14 章 常见问题与排错

> 本章覆盖从装环境到投稿最常遇到的 9 类问题，每条按“问题 → 原因 → 解决”展开。遇到报错时，请先读报错信息的第一行与最后几行，再对号入座。

## 本章目标

学完本章，你将能够：

1. 独立解决 R 包安装、GEO 数据下载等环境类问题；
2. 看懂表达矩阵、差异分析、绘图中最常见的报错并定位原因；
3. 面对“生存分析不显著”“富集结果为空”等结果类问题，给出规范、诚实的处理方案；
4. 掌握内存管理与数据降采样的基本技巧；
5. 知道如何回应审稿人对“缺少实验验证”的质疑，守住科研诚信底线。

## 14.1 环境与安装

### 问题 1：安装包失败

**问题**：`install.packages()` 或 `BiocManager::install()` 报错、装不上包。

**原因**：最常见有三类——① 网络问题：CRAN/Bioconductor 服务器在境外，校园网或公司网络常常很慢甚至连不上；② 包来源搞错：Bioconductor 的包用 `install.packages()` 装，会提示 "package 'limma' is not available for this version of R"；③ 依赖缺失：报错尾部写着 "ERROR: dependency 'xxx' is not available"；④ R 版本过旧：新包普遍要求 R ≥ 4.x。

**解决**：

1. 先分清包属于哪个仓库：CRAN 包用 `install.packages()`；Bioconductor 包先 `install.packages("BiocManager")`，再 `BiocManager::install("limma")`；GitHub 包用 `remotes::install_github("用户名/仓库名")`。
2. 换国内镜像加速：`install.packages("ggplot2", repos = "https://mirrors.tuna.tsinghua.edu.cn/CRAN/")`，或写入 `options(repos = c(CRAN = "https://mirrors.tuna.tsinghua.edu.cn/CRAN/"))` 一劳永逸。
3. 缺依赖就按报错顺序先装依赖；提示“需要更高版本 R”就升级 R 到当前稳定版（≥ 4.3）。
4. Windows 下遇到编译类报错（如 igraph、Matrix 提示需要本地编译），通常是没装 Rtools；到 CRAN 下载对应版本安装后重启 R。优先使用官方预编译的二进制包可避开多数编译问题。
5. 实在装不上不必死磕：先把报错最后几行复制到搜索引擎，多数问题都有现成答案；少数包可用功能相近的替代包。

### 问题 2：GEOquery 下载慢/失败

**问题**：`getGEO("GSE14520")` 转圈半天、报 "Timeout of 60 seconds was reached"，或下载到一半断掉。

**原因**：GEO 服务器位于美国，跨洋网络不稳定；而 GSE 的 series matrix 文件可能有几十 MB（GSE14520 含 400+ 样本），R 默认约 60 秒的超时很容易不够用。

**解决**：

1. 加大超时：`options(timeout = 600)`，下载大文件前必改。
2. 缓存到本地：`getGEO("GSE14520", destdir = "data/")`，文件会存到 `data/`，之后重复读取不再联网。
3. 失败就重试，或换个时段；网络抖动是常态，不是你的代码问题。
4. 终极办法：用浏览器打开 GEO 官网的 GSE 页面，手动下载 series matrix 压缩包放到 `data/`，再 `getGEO(filename = "data/GSE14520_series_matrix.txt.gz")` 离线读取——把“下载”和“分析”分离后，网络问题就不再阻塞分析。
5. TCGA 数据同理：文件更大，建议用 TCGAbiolinks 的 query/download 接口分步下载，并确认断点续传。

## 14.2 数据与差异分析

### 问题 3：探针 ID 无法映射到基因

**问题**：表达矩阵行名是 `202763_at` 这类探针 ID，与基因符号对不上；或一个基因对应多条探针，不知该取哪个。

**原因**：芯片平台的探针与基因是多对多关系（一条探针可能命中多个转录本，一个基因被多条探针覆盖），映射关系存放在平台（GPL）对应的注释包中；平台错配是“映射结果几乎全 NA”的头号原因。

**解决**：

1. 从 series matrix 里找到 `!Series_platform_id`，确定 GPL 编号（如 GPL3921 = Affymetrix HG-U133A 芯片），安装对应注释包：`BiocManager::install("hgu133a.db")`。
2. 用 `AnnotationDbi::select(hgu133a.db, keys = 探针ID, columns = "SYMBOL", keytype = "PROBEID")` 得到探针 → 基因符号的映射。
3. 多探针对一基因：按"collapseByGene"的思想聚合——教学上常用 `aggregate()` 按基因符号取均值（也可取最大信号），先把 NA 过滤掉再聚合。
4. RNA-seq 数据没有探针问题，但若行名是 Ensembl ID，仍需转换：用 `clusterProfiler::bitr()` 或 biomaRt 转成 gene symbol。

### 问题 4：差异分析报错

**问题**：limma 报 "contrasts can be applied only to factors"、结果全是 NA，或倍数变化方向与预期相反。

**原因与解决**：

1. **分组方向反了**：limma 按因子水平的顺序比较，第一个水平是参照组。用 `factor(group, levels = c("normal", "tumor"))` 明确指定，再看 `colnames(fit)` 确认比较方向后再下结论。
2. **表达值不是数值**：读入的矩阵可能被当成 character（逗号、引号处理不当）。用 `str()` 检查，`as.matrix()` 后确认 `mode()` 为 "numeric"。
3. **存在 NA**：limma 不接受 NA。先 `na.omit()` 或补全，再过滤掉全零、全低表达的行。
4. **组内样本太少**：每组至少 3 个样本，limma 才能稳健估计方差；某组只有 1 个样本时先合并分组或放弃该比较。
5. **counts 数据不能直接喂 limma**：RNA-seq 原始计数需先 `edgeR::DGEList()` → `calcNormFactors()` → `voom()` 转换，再进 limma；或直接改用 edgeR/DESeq2。
6. 报错别慌：把 design 公式、contrasts 定义和 `str()` 输出贴给搜索引擎，绝大多数是上述五类之一。

## 14.3 可视化

### 问题 5：热图/火山图画不出来

**问题**：pheatmap 报错、火山图一片空白、ggplot 报 "Aesthetics must be either length 1 or the same as the data"。

![图06 差异表达火山图（示例，模拟数据）](../figures/fig06_volcano.png)

**原因与解决**：

1. pheatmap 要求输入为纯数值矩阵：传 `as.matrix(expr)`，行名是基因、列名是样本，不要带其他列；行名重复会报错，先 `make.unique()` 或按基因聚合。
2. 火山图数据要有 logFC、adj.P、gene 三列，先 `str()` 确认列名与类型；若取 `-log10(adj.P)` 时 P 值全为 NA（limma 对某些行无法估计），图自然空白——先过滤 NA 再画。
3. ggplot 常见坑：列名大小写写错、把外部向量直接塞进 `aes()`（aes 里只能写数据框的列名）、scale 函数参数拼错。逐行检查映射即可。
4. 中文显示：默认绘图设备对中文支持差，会报字体警告或显示方块。图内统一用英文标签，或加载 showtext 包配中文字体。
5. 调试技巧：先画最简散点图确认数据没问题，再逐步加颜色、标签、主题；报错信息最后几行是定位关键。

## 14.4 结果解读

### 问题 6：生存分析结果“不显著”怎么办

**问题**：KM 曲线 P > 0.05，看起来“没戏了”。

![图12 KM 生存曲线（示例，模拟数据）](../figures/fig12_km.png)

**原因**：样本量小、事件数少（删失比例过高时检验功效不足）、分组方式不佳，或该基因在本队列中确实与预后无关。

**规范应对**：

1. 先查硬条件：报告事件数与删失比例；事件太少时即使真有关也检不出。
2. 再查分组方式：中位数分组只是默认做法，可用 `survminer::surv_cutpoint()` 按数据自动找最佳截点，并在 Methods 里写清楚。
3. 换一个视角：用连续型 Cox 回归看 HR（风险比），比二分法更充分利用数据。
4. **警惕 p-hacking**：不要在几十个截点里挑 P 值最小的那个当“发现”而不做任何校正；也不要在检验过的 20 个基因里只报显著的那几个。规范做法是预先定好截点规则、报告检验过的基因总数，把阴性结果如实写入正文或补充材料。
5. 不显著也是结论：写“该基因在 TCGA-LIHC 队列中与总生存无显著关联（P = 0.3，log-rank 检验）”本身就是诚实的科学内容。

### 问题 7：富集分析结果为空/通路太少

**问题**：enrichGO/enrichKEGG 报 "No gene can be mapped"，或返回 0 条通路、通路少得可怜。

**原因**：最常见是基因 ID 类型不匹配——clusterProfiler 默认要求 ENTREZID，而你输入的是 gene symbol；其次是差异基因太少、阈值过严；此外 KEGG 收录的通路偏经典代谢与信号通路，免疫相关基因常富集在 GO 条目而 KEGG 通路少，这并不代表分析错了。

**解决**：

1. 先转 ID：`bitr(genes, fromType = "SYMBOL", toType = "ENTREZID", OrgDb = org.Hs.eg.db)`，再用转换结果做 `enrichGO(OrgDb = org.Hs.eg.db, keyType = "ENTREZID")`。
2. 检查物种：org.Hs.eg.db 是人类注释包，别拿它映射小鼠基因；enrichKEGG 记得设 `organism = "hsa"`。
3. 阈值放宽：差异基因只有几十个时，可降低 |log2FC|（如 0.5）或只用未校正 P 值筛选，并在论文中说明；或改用 GSEA——它不需要先筛差异基因，用全部基因的表达变化排序做富集，结果更稳健。
4. GO 与 KEGG 分开报告，KEGG 通路少不代表分析失败。

## 14.5 性能与诚信

### 问题 8：内存不足/电脑卡顿

**问题**：RStudio 卡死，报 "cannot allocate vector of size ..."。

**原因**：表达矩阵本身不大（371 样本 × 2 万基因仅几十 MB），但中间对象（DGEList、voom 结果、绘图对象）层层复制，叠加浏览器占用就容易撑爆内存。

**解决**：

1. 对象瘦身：只保留需要的基因（先取焦亡基因子集再做后续分析）；用完的中间变量及时 `rm()` 并 `gc()`。
2. 分块运行：下载 → 存 RDS，差异分析 → 存 RDS，绘图时再读 RDS；重启 R 后从断点继续，而不是一个脚本从头跑到尾。
3. 数据降采样：TCGA 用 TCGAbiolinks 只取 LIHC 的 RNA-seq 与临床，不必全库下载；GEO 用 destdir 缓存避免重复下载。
4. 关掉不用的浏览器标签；矩阵类运算（如免疫浸润反卷积）实在跑不动时，可考虑学校集群或云服务器，R 脚本迁移成本很低。
5. Windows 上通常不需要手动调内存参数，问题几乎总在“对象太多”，而不是 R 本身的限制。

### 问题 9：审稿人质疑“没有实验验证”

**问题**：“你只有生信分析，没有实验验证，结论可靠吗？”

**回应思路**：不是硬杠，而是补证据 + 说实话。

1. **独立验证**：用第二个公共队列（如 GSE14520）复现 TCGA-LIHC 的预后模型——这正是本教程第 12 章做的事，也是挖掘型论文最重要的证据。
2. **多层面交叉验证**：转录组（TCGA/GEO）＋ 蛋白层面（HPA 免疫组化）＋ 免疫浸润（CIBERSORT），多个独立数据源互相印证。
3. **机制讨论**：结合已发表的焦亡机制文献，解释 hub 基因在通路中的位置，把统计关联翻译成生物学解释（明确标注为推测）。
4. **如实写 limitations**：在 Discussion 末尾写明“本研究为基于公共数据库的发现性研究（hypothesis-generating），结论有待 qPCR/WB 等湿实验验证”，并把湿实验列为未来工作——不承诺未做的实验。
5. 审稿人要的是“你知道自己结论的边界”，态度诚恳、方法透明通常就能过关。

## 本章小结

- 报错先看第一行和最后几行，把原文复制到搜索引擎，比自己瞎猜高效得多。
- 环境问题三件套：分清包来源（CRAN/Bioconductor/GitHub）、换国内镜像、按依赖顺序安装。
- 数据问题的核心是“类型与格式”：先 `str()` 看结构，再谈分析。
- 结果类问题先查统计前提（事件数、样本量、ID 类型），再考虑换方法；不显著、富集为空都要如实报告。
- 科研诚信是底线：不挑 P 值、不隐瞒阴性结果、不虚构实验。

## 思考题

1. 你用 `install.packages("limma")` 报错 "package 'limma' is not available"，而同学用 `BiocManager::install("limma")` 装上了——问题出在哪？
2. 火山图横坐标全为 0，可能是什么数据问题？你会按什么顺序排查？
3. 你的 KM 曲线 P = 0.08。审稿人可能怎么看？你会在论文中如何报告这个结果？
4. 富集分析返回 0 条通路，列出至少三种可能原因及对应的检查方法。
