4、输入数据准备:count 矩阵文件与样本注释(metadata)表格的格式要求、缺失值处理

好,咱们终于到了真正动手操作的部分了。我见过不少同学,从 RNA-seq 比对一路跑下来,结果一读到 DESeqDataSetFromMatrix 就报错——说白了,就是数据格式没对清楚。今天咱们就专门把输入数据的两个核心文件掰开了说。

其实你手头只需要两个东西:count 矩阵 和 metadata 表格。嗯,就这么简单。但格式上的坑,比想象中多。我当年第一次跑 DEseq2 时,因为 sample_id 里带了个减号,排查了一整个下午。来,咱们从最基础的说起。

4.1 count 矩阵——基因 × 样本的整数表格

count 矩阵长什么样?说白了就是一个纯整数的二维表:行是基因(或转录本),列是样本。矩阵里的每个数代表比对到该基因上的 reads 数目(或 fragments 数目)。

我建议你把数据存成 .csv 或 .tsv,列名直接用样本 ID(不要加奇怪的空格、连字符),行名用基因 ID(ENSEMBL ID、Entrez ID 或基因 symbol 都行,但要保证整个文件里不重复)。

一个标准 count 矩阵长这样(假装这是前 5 行):

gene_id,sample1,sample2,sample3,sample4
ENSG00000121410,152,0,89,204
ENSG00000175899,0,33,12,0
ENSG00000233927,78,101,0,59
ENSG00000237683,220,47,155,88
ENSG00000268179,9,0,0,3

硬性要求:

  • 所有数值必须是 非负整数(0 正常,但不能有小数、负数)。
  • 矩阵中 不允许有 NA、NaN 或空值。DESeq2 内部会调用 DESeqDataSetFromMatrix,如果遇到缺失值直接报错。我甚至见过有人把缺失值填成空字符串 "" 的,照样挂。
  • 列名要和 metadata 表格的样本 ID 列 一一对应,包括大小写和符号。你想想看,万一有个下划线没对上,R 不会帮你模糊匹配。

4.2 metadata 表格——样本 × 实验变量的注释表

metadata 表格是样本级别的信息。每一行是一个样本,每一列是一个实验变量(比如处理组、时间点、批次等)。

我个人习惯:metadata 的第一列叫 sample_id,与 count 矩阵的列名完全相同。这样用 rownames(md) <- md$sample_id 后可以直接传入 DESeq2,不容易弄错。

一个典型的 metadata 表格:

sample_id,condition,time_point,batch
sample1,control,0h,batch1
sample2,control,0h,batch1
sample3,treatment,2h,batch1
sample4,treatment,2h,batch2
列名要求 说明
列名不能包含特殊字符 避免空格、$、@、#、中文字符等。R 的列名会自动做一点转义,但手动写干净能省心太多。
因子(factor)列要注意顺序 处理组 vs 对照组,DESeq2 默认以第一水平为参考(reference level)。我建议用 factor(condition, levels = c("control","treatment")) 显式指定,避免按字母序排序。
缺失值处理 metadata 中允许出现 NA,但 不能出现在用于建模的列(比如 condition、batch)。如果一个样本的某个重要变量缺失,DESeq2 内部会直接丢弃该样本(deleting rows with missing values in design)。
行名与 count 列名一致 这是最常见的问题之一。我习惯先检查:all(colnames(counts) == rownames(metadata)) 返回 TRUE 再往下走。

我曾经遇到过一位同事的 metadata 里 “sample2” 和 “sample 2” (空格)混用,结果 50 个样本只匹配上 30 个,剩下的全部变成 NA,跑出的结果全是错的。排查了大半天才找到原因。

所以我的建议:先跑 setdiff(colnames(counts), rownames(metadata)) 和 intersect 检查一下,确保两边样本完全一致。

4.3 缺失值该怎么处理?

好,重点来了。缺失值在 count 矩阵和 metadata 里的处理方式截然不同,咱们分开看。

  • count 矩阵:零容忍。 如果某个基因在某些样本里没有读数(count=0),这完全正常,DESeq2 会用自带的正则化处理。但如果是空白单元格或 NA,DESeq2 直接罢工。你必须在导入前把那些位置填成 0,或者删掉该基因/样本。我个人倾向于填 0(如果这个基因在其他样本有表达)或者直接过滤掉那些低表达基因(用 rowSums(counts) > 10 之类的规则)。

  • metadata:区分处理。
    1. 如果缺失值在 分组变量(condition、treatment)里:这个样本基本需要剔除,或者你确定自己知道该样本的真实组别就手动补上。
    2. 如果缺失值在 协变量(批次、时间)里:可以用中位数/众数填补,但一定要在分析报告中注明。更稳妥的做法是把这个样本扔掉,反正一个批次也就几十个样本。
    3. 如果缺失值在 数值型协变量(如年龄、体重)里:用输入均值或中位数填补;如果是离群值,可以考虑用回归填补。

嗯,这里有个小技巧。如果你不确定 metadata 是否有缺失值,直接用 any(is.na(metadata)) 扫一遍。如果返回 TRUE,再用 colSums(is.na(metadata)) 看哪些列有缺失,一目了然。

4.4 整体流程:从原始文件到 DESeq2 输入

我把整个数据准备的过程画成了一张图,你跟着走一遍基本就不会出错了。

count 矩阵与 metadata 数据准备流程 原始 count 文件 原始 metadata 文件 检查数值类型 (全部整数 ≥0) 检查列名与行名 (无特殊字符、因子顺序) 统计缺失值 (填 0 或删除行) 统计缺失值 (剔除或填补) 校验行列对齐 DESeqDataSet

你顺着图走:先保证 count 矩阵的数值干净,再保证 metadata 的列名规范,然后处理两边的缺失值,最后校对行列一致性。走到最后一步,直接生成 DESeqDataSet 对象,基本不会再报格式错误。

4.5 实战 R 代码片段

放一段我平时用的导入检查流程,你可以直接套用:

# 读入 count 矩阵
counts <- read.csv("count_matrix.csv", row.names = 1)
# 读入 metadata
metadata <- read.csv("metadata.csv", row.names = 1)

# 1. 检查 count 矩阵数值类型
stopifnot(all(counts == floor(counts), na.rm = TRUE))
stopifnot(all(counts >= 0, na.rm = TRUE))
# 如果有 NA 直接报错
if (any(is.na(counts))) stop("count 矩阵中存在缺失值!")

# 2. 检查 metadata 缺失值
if (any(is.na(metadata$condition))) {
  warning("condition 列有缺失,将删除对应样本")
  samples_keep <- rownames(metadata[!is.na(metadata$condition), ])
  counts <- counts[, samples_keep]
  metadata <- metadata[samples_keep, ]
}

# 3. 样本对齐
common <- intersect(colnames(counts), rownames(metadata))
cat("共有", length(common), "个样本匹配\n")
counts <- counts[, common]
metadata <- metadata[common, ]

关键检查点:

  • 用 all(colnames(counts) == rownames(metadata)) 确认完全一致。
  • 用 table(metadata$condition) 看一下分组是否平衡。
  • 用 summary(rowSums(counts)) 了解低表达基因的比例。

处理完这些,你就有了一个干净的数据集,可以直接喂给 DESeqDataSetFromMatrix。嗯,剩下的事就交给 DEseq2 吧。