# 第 03 章 生信入门必备：R/Python 与核心数据结构

> 本章只讲“够用 70%”的基础。目标不是把你变成程序员，而是让你能读懂并修改本书的代码。

## 本章目标

读完本章后，你应该能：

1. 掌握 R 的向量、数据框、tibble、管道与函数写法；
2. 理解 ggplot2“数据 + 映射 + 几何层”的绘图理念；
3. 掌握 Python 的 numpy 数组、pandas DataFrame 与列表推导；
4. 看懂 Seurat、SingleCellExperiment、AnnData 三个核心对象的结构；
5. 认识 10x mtx 三件套、h5、h5ad、rds、csv 等文件格式及对应读写函数；
6. 独立完成 20 分钟热身练习：读 csv → 算均值 → 画第一张图。

## 3.1 R 语言快速上手

本书主流程用 R（Seurat、CellChat、BayesSpace），先补五个最小概念。每个概念只给 1–2 行示例，够用即可。

**① 向量（vector）**：一维数据，用 `c()` 创建：

```r
x <- c(3, 1, 4, 1, 5)
mean(x)          # 平均值 = 2.8
x > 2            # 逐元素比较，返回 TRUE/FALSE 向量
```

**② 数据框（data.frame）**：表格数据，列是变量、行是观测：

```r
df <- data.frame(spot = c("s1", "s2", "s3"),
                 nCount = c(3000, 5200, 1800))
df$nCount                # 用 $ 取列
df[df$nCount > 2000, ]   # 按条件筛选行
```

**③ tibble**：tidyverse 的“升级版”数据框，打印更友好、行为更一致，本书默认使用：

```r
library(tidyverse)
tb <- tibble(spot = c("s1", "s2"), nCount = c(3000, 5200))
tb                       # 打印时显示类型，长表自动截断
```

**④ 管道 `|>`**：把左边的结果“喂给”右边的函数，让代码从“从内向外读”变成“从左向右读”，是本书的“主力语法”：

```r
tibble(spot = c("s1","s2","s3"), nCount = c(3000, 5200, 1800)) |>
  filter(nCount > 2000) |>
  summarise(mean_nCount = mean(nCount))
```

**⑤ 函数**：`名称 <- function(参数) { 函数体 }`：

```r
square <- function(v) v^2
square(c(1, 2, 3))   # 返回 1 4 9
```

**运行结果解读：** 五段代码分别演示了取平均与逻辑比较、取列与筛选、tibble 打印、管道串联、自定义函数。第 08 章你会看到 `RunPCA(obj) %>% FindNeighbors(...)`——那是管道在真实分析中的延续（`%>%` 与 `|>` 功能等价，本书混用）。

## 3.2 R 绘图理念：ggplot2

ggplot2 的绘图哲学一句话：**图 = 数据 + 映射 + 几何层**。

- **数据（data）**：一个数据框，是图的“原料”；
- **映射（aes）**：把数据列“映射”到图形的视觉属性（x 轴、y 轴、颜色、大小）；
- **几何层（geom）**：决定画成什么形状（点、柱、箱线、密度……）。

```r
library(ggplot2)
d <- data.frame(生态位 = rep(c("肿瘤核心", "侵袭前沿"), each = 10),
                CXCL12 = c(rnorm(10, 5, 1), rnorm(10, 8, 1)))
ggplot(d, aes(x = 生态位, y = CXCL12)) +
  geom_boxplot() +
  labs(title = "不同生态位的 CXCL12 表达")
```

**运行结果解读：** `aes(x=, y=)` 把两列映射到坐标轴，`geom_boxplot()` 画箱线图，`+` 号用来叠加图层。本书后面所有图——气泡图、网络图、热图、空间散点——都是这个“数据 + 映射 + 几何”框架的变体，只是几何层不同。

## 3.3 Python 快速上手

本书的 Python 用于 scanpy、squidpy、cell2location（第 09、10、13 章）。三个最小概念：

**① numpy 数组**：同构多维数组，适合数值计算：

```python
import numpy as np
a = np.array([[1, 2], [3, 4]])
a.mean()          # 全部元素均值 = 2.5
a.sum(axis=0)     # 按列求和 → array([4, 6])
```

**② pandas DataFrame**：带标签的表格，是 Python 界的“数据框”：

```python
import pandas as pd
df = pd.DataFrame({"spot": ["s1", "s2", "s3"],
                   "nCount": [3000, 5200, 1800]})
df["nCount"].mean()          # 列均值
df[df["nCount"] > 2000]      # 按条件筛选行
```

**③ 列表推导**：一行生成列表的 Python 特色写法：

```python
genes = ["CD3D", "CD8A", "GZMB"]
lower = [g.lower() for g in genes]   # 转小写
```

**运行结果解读：** numpy 用 `axis` 指定沿哪个轴计算，pandas 用方括号取列（`df["nCount"]`，对应 R 的 `df$nCount`），列表推导是“表达式 + for”的固定句式。记住“R 用 `$`、Python 用 `[]`”这个差异，跨语言读代码就不容易混。

**何时用哪个？** 本书的策略是：主流程（预处理、注释、通讯）用 R，去卷积（cell2location）与空间统计（Squidpy）用 Python。两种语言都要能“读”，不必都精通。

## 3.4 两个生态的核心对象

真实分析中，你不会直接操作“裸”数据框，而是操作封装好的分析对象。R 侧是 **Seurat** 与 **SingleCellExperiment（SCE）**，Python 侧是 **AnnData**。

**Seurat 对象**的三个核心部件：

- **assay（测定）**：存表达矩阵，如 `RNA`（原始计数）或 `SCT`（SCTransform 归一化后）；
- **meta.data**：每个 spot/细胞的元数据表（样本、条件、聚类标签、QC 指标等）；
- **reductions**：降维结果（PCA、UMAP），供可视化与聚类使用。

**AnnData 对象**（scanpy 生态）的五个核心部件：

- **X**：表达矩阵（细胞 × 基因，与 Seurat 的“基因 × 细胞”互为转置）；
- **obs**：细胞元数据（对应 Seurat 的 meta.data）；
- **var**：基因元数据（Ensembl ID、高变基因标记等）；
- **obsm**：降维结果（X_umap、X_pca 等）；
- **uns**：非结构化信息（聚类配色、通路注释等）。

| 部件 | Seurat | SingleCellExperiment | AnnData |
| --- | --- | --- | --- |
| 表达矩阵 | `obj[["RNA"]]` | `counts(sce)` | `adata.X`（注意转置） |
| 细胞/spot 元数据 | `obj@meta.data` | `colData(sce)` | `adata.obs` |
| 基因元数据 | `rownames(obj)` | `rowData(sce)` | `adata.var` |
| 降维结果 | `obj@reductions` | `reducedDim(sce)` | `adata.obsm` |
| 自由信息 | `obj@misc` | `metadata(sce)` | `adata.uns` |

**运行结果解读：** 三种对象结构高度对应（表达矩阵 + 细胞信息 + 基因信息 + 降维 + 杂项），只是命名不同。记住这张对照表，你在 R 与 Python 之间切换时心中有数。最容易踩的坑是维度方向：Seurat 是**基因 × 细胞**，AnnData 是**细胞 × 基因**。

## 3.5 常用文件格式

| 格式 | 是什么 | 典型后缀 |
| --- | --- | --- |
| 10x mtx 三件套 | 稀疏表达矩阵 + 条形码 + 基因 | `matrix.mtx.gz`、`barcodes.tsv.gz`、`features.tsv.gz` |
| h5（HDF5） | 单文件打包的表达矩阵 | `*.h5` |
| h5ad | AnnData 单文件 | `*.h5ad` |
| rds | R 对象单文件 | `*.rds` |
| csv/tsv | 通用表格 | `*.csv` |

读取函数对照（R 生态用 Seurat，Python 生态用 scanpy）：

| 格式 | R 读取 | Python 读取 |
| --- | --- | --- |
| 10x mtx 三件套 | `Seurat::Read10X()` | `scanpy.read_10x_mtx()` |
| h5（HDF5） | `Seurat::Read10X_h5()` | `scanpy.read_10x_h5()` |
| h5ad | `zellkonverter`（间接） | `scanpy.read_h5ad()` |
| rds | `readRDS()` | 不直接支持 |
| csv/tsv | `read.csv()`、`readr::read_csv()` | `pandas.read_csv()` |

**运行结果解读：** 日常最常用的是三件套（Visium 默认下载格式）与 h5（单文件更省事）。10x Visium 下载的 h5 单文件名通常为 `filtered_feature_bc_matrix.h5`（见第 7 章）。h5ad 是 Python 生态的“存档格式”，rds 是 R 生态的——跨生态传递数据时，通常用 csv 或 h5 作为“通用语”。注意：mtx 是稀疏格式，千万不要用 Excel 打开；读进来之后你看到的才是完整矩阵。

**运行结果解读：** 日常最常用的是三件套（Visium 默认下载格式）与 h5（单文件更省事）。h5ad 是 Python 生态的“存档格式”，rds 是 R 生态的——跨生态传递数据时，通常用 csv 或 h5 作为“通用语”。注意：mtx 是稀疏格式，千万不要用 Excel 打开；读进来之后你看到的才是完整矩阵。

## 3.6 20 分钟热身练习

把前面的概念串一遍：**造数据 → 读数据 → 算均值 → 画第一张图**。

```r
# 第 1 步：造一个小数据（20 个 spot 的 UMI 数，模拟）
set.seed(2024)
umi <- data.frame(
  spot   = paste0("spot_", 1:20),
  nCount = round(rnorm(20, mean = 3000, sd = 800))
)
write.csv(umi, "umi_demo.csv", row.names = FALSE)   # 存盘

# 第 2 步：读回来（真实分析中，这里读的是你的 Visium 数据）
umi <- read.csv("umi_demo.csv")

# 第 3 步：算均值
mean(umi$nCount)

# 第 4 步：画第一张图（直方图）
hist(umi$nCount, main = "UMI 数分布", xlab = "UMI 数", col = "skyblue")
```

**运行结果解读：** 你会得到一个数值（约 3000，随机种子固定后每次运行结果相同）和一张直方图。恭喜——你已经完成了“读数据 → 算统计 → 画图”的最小闭环，这正是第 08 章质控分析的雏形。

真实的“空间质控图”长什么样？提前剧透（模拟数据演示）：

![图 3-1 空间质控图的“成品”样式（模拟数据演示）。上排：按生态位分组的 UMI 数、基因数、线粒体比例小提琴图；下排：三个指标映射回组织切片的空间散点，低质量 spot 标为灰色。这张图在第 08 章会详细讲解，现在你只需要读懂“每个点代表一个 spot”。](figures/fig07_qc.png){width=85%}

## 3.7 推荐学习资源

学有余力时，按顺序看这些（都是免费公开资源）：

- **R for Data Science（《R 数据科学》）**：tidyverse 圣经，官网 r4ds.hadley.nz，建议精读前 10 章；
- **《R 语言实战》**：中文经典教材，偏基础统计；
- **scanpy 官方教程**：scanpy-tutorials.readthedocs.io，跟着做 PBMC 案例即可入门；
- **Squidpy 文档**：squidpy.readthedocs.io，空间分析专属（第 10 章用到）；
- **10x Genomics 官网**：www.10xgenomics.com，Visium 原理与 demos 的第一手资料；
- 中文社区：生信技能树、生信宝典（搜索“Seurat 教程”“scanpy 教程”）。

原则：**遇到不懂的语法，先查官方文档，再搜社区**。本书第 16 章也整理了常见报错清单。

## 本章小结

- R 的核心：向量、数据框/tibble、管道 `|>`、函数；ggplot2 = 数据 + 映射 + 几何层；
- Python 的核心：numpy 数组、pandas DataFrame、列表推导；
- 三大对象：Seurat（assay、meta.data、reductions）、SCE（counts、colData、reducedDim）、AnnData（X、obs、var、obsm、uns），注意维度转置；
- 文件格式：三件套、h5、h5ad、rds、csv，各配读写函数；
- 20 分钟热身练习完成了“读 → 算 → 画”最小闭环，这是后续一切分析的原型。

## 练习与思考

1. （动手题）把 3.6 节练习的均值改为中位数（`median()`），并给直方图加上红色边框（`border = "red"`），观察变化。
2. （动手题）用管道把 3.1 节的 tibble 例子改成“先筛选 nCount > 2000 的 spot，再算均值”，并输出结果。
3. （动脑题）Seurat 的矩阵是“基因 × 细胞”，AnnData 是“细胞 × 基因”。如果跨生态读数据时不转置，会发生什么？
4. （动手题）用 Python 重写 3.6 节练习：pandas 读 csv、算均值、matplotlib 画直方图。
5. （思考题）为什么 h5ad 适合在 Python 生态“存档”、rds 适合在 R 生态？跨生态传递数据时你更倾向哪种格式？
