生信面试常考统计方法一网打尽 | 覆盖宏基因组 + 微生物组 + 机器学习
做生信分析,本质上就是在回答"两组之间有没有差异?差异有多大?哪些物种/基因导致了差异?能不能预测?"——这些全靠统计方法。面试官通过统计题考察你是不是"真做过分析"还是"只会跑脚本"。
面试官问"你怎么选统计方法?"——不要背方法名,而是说出你的决策逻辑。下面是一个清晰的决策路径:
白话原理:不比较数值大小,而是比较排名。把两组数据混在一起从小到大排序,给每个值编号(秩),然后看一组的编号之和是不是偏大或偏小。如果一组的排名普遍靠前(数值小),另一组普遍靠后(数值大),就说明有差异。
R 代码
wilcox.test(shannon ~ group, data = alpha_div) 用公式写法做 Wilcoxon 秩和检验:shannon ~ group 意思是"按 group 列分组,比较 shannon 列的值";data 指定数据框exact = FALSE 不计算精确 p 值,改用正态近似。样本量大于 50 或有并列值(tie)时建议 FALSE,否则计算会很慢甚至报错correct = TRUE 启用 Yates 连续性校正——因为秩和是离散值,但正态近似是连续的,校正会让 p 值略微保守(更不容易显著),一般保持默认 TRUE 即可paired = TRUE,此时变成 Wilcoxon signed-rank test(符号秩检验),这是完全不同的检验真实输出
| 输出字段 | 含义 | 白话解释 |
|---|---|---|
| W | Wilcoxon 统计量(秩和) | 一组排名之和,越偏离期望值越说明有差异 |
| p-value | 显著性概率 | <0.05 说明两组确实不一样 |
适用场景:
| 场景 | 具体例子 |
|---|---|
| Alpha 多样性比较 | T2D 组 vs 健康组的 Shannon 指数 |
| 单个物种丰度比较 | 两组之间 Bacteroides 的相对丰度 |
| 某个功能通路的丰度 | 两组之间某 KEGG pathway 的 abundance |
白话原理:把每个样本想象成地图上的一个点,同组样本靠得近,不同组样本靠得远。PERMANOVA 就是比较"组间距离是否显著大于组内距离"——如果是,说明分组因素(如疾病/健康)确实影响了菌群组成。
技术上:PERMANOVA 对距离矩阵做方差分析的思想(类似 ANOVA),但不要求正态性,通过置换(permutation)获得 p 值。
R 代码
vegdist(otu_table, method = "bray") 计算样本间的 Bray-Curtis 距离矩阵。otu_table 是物种丰度表(行=样本,列=物种);method 指定距离度量——"bray" 是微生物组分析的首选(考虑丰度差异),其他选项有 "jaccard"(只看有无)、"euclidean"(欧氏距离,一般不用于丰度数据)adonis2(bc_dist ~ Group, data = meta) 做 PERMANOVA 检验。公式 bc_dist ~ Group 意思是"用 Group 变量解释距离矩阵的变异";data = meta 是包含分组信息的元数据表permutations = 999 做 999 次随机置换来计算 p 值——把样本标签随机打乱 999 次,看真实的 F 值在随机分布中排第几。999 次是常用默认值,发文章一般用 9999 次更稳定bc_dist ~ Group + Age + BMI,同时评估多个变量的贡献。注意变量顺序会影响结果(sequential test),所以通常把最关心的变量放第一个真实输出(vegan adonis2)
| 输出字段 | 含义 | 白话解释 |
|---|---|---|
| Df | 自由度 | Group 有 2 组所以 Df=1 |
| SumOfSqs | 平方和 | 该因素解释的"距离变异量" |
| R² | 解释度(0~1) | 这里 7.12% 的群落差异由分组解释 |
| F | 伪F统计量 | 组间变异 / 组内变异,越大差异越明显 |
| Pr(>F) | p值(基于置换) | 0.001 表示非常显著 |
betadisper() 检验离散度差异,再做 PERMANOVA。如果 betadisper 显著,PERMANOVA 的结果可能被离散度差异"污染"。
白话原理:微生物 count 数据不是正态分布,而是"负二项分布"(一种适合 count 数据的分布,允许方差大于均值)。DESeq2 用这个分布建模,通过"缩减估计"(shrinkage)稳定低丰度物种的 fold change,最后做 Wald 检验判断每个物种在两组之间的差异是否显著。
R 代码
DESeqDataSetFromMatrix(countData, colData, design) 创建 DESeq2 专用数据对象,把三样东西打包在一起:countData = count_matrix 原始 count 矩阵(行=物种/基因,列=样本)。必须是未标准化的整数,不能用相对丰度或 RPKMcolData = sample_info 样本元数据表,行名=样本名(要和 count_matrix 列名一致),至少包含分组列(如 Group)design = ~ Group 告诉 DESeq2 按哪个变量比较。~ 是 R 公式写法;如果要矫正协变量:~ Age + BMI + Group(最关心的变量放最后)DESeq(dds) 一键运行三步流程:① 估计 size factors(标准化)→ ② 估计基因/物种的离散度(dispersion)→ ③ 对每个物种做 Wald 检验。一行代码搞定所有统计results(dds, contrast, alpha) 提取差异分析结果:contrast = c("Group", "T2D", "Healthy") 指定比较方向:第一个是列名,第二个是分子(实验组),第三个是分母(对照组)。这里 log2FC = log2(T2D / Healthy),正值=T2D升高alpha = 0.05 FDR 显著性阈值,影响 independent filtering 的阈值选择和 summary() 的报告,但不影响 padj 的计算本身res[order(res$padj), ] 按校正后 p 值排序,最显著的排最前面。看结果永远看 padj 列,不要看 pvalue 列真实输出(DESeq2 results)
| 字段 | 含义 | 白话解释 |
|---|---|---|
| baseMean | 所有样本的标准化 count 均值 | 这个物种总体丰度有多高 |
| log2FoldChange | log2(T2D / Healthy) | 正值=T2D中升高,负值=T2D中降低。-1.24 意味着 T2D 组约为健康组的 1/2.4 |
| lfcSE | log2FC 的标准误 | 估计的不确定性 |
| stat | Wald 统计量 = log2FC / lfcSE | 类似 z-score,绝对值越大越显著 |
| pvalue | 原始 p 值 | 单个检验的显著性(未校正) |
| padj | BH 校正后 p 值 | 这个才是最终看的!<0.05 才认为显著 |
火山图解读:
X轴 = log2FoldChange(越左越在T2D中减少,越右越增加);Y轴 = -log10(padj)(越高越显著)。右上角 = T2D显著升高的物种;左上角 = T2D显著降低的物种;中间/下方 = 无显著差异。
| 工具 | 统计模型 | 优势 | 适用场景 |
|---|---|---|---|
| DESeq2 | 负二项分布 + Wald检验 | 统计功效高,给出 log2FC | count 数据差异分析(宏基因组) |
| LEfSe | Kruskal-Wallis + LDA | 可视化效果好,给出 LDA score | 找 biomarker,文章图好看 |
| ANCOM-BC | 线性回归 + 偏差校正 | 专门解决组成数据偏差问题 | 16S 相对丰度数据 |
| MaAsLin2 | 广义线性混合模型 | 可加协变量(年龄、BMI等) | 需矫正混杂因素时 |
白话原理:假如你检测了 1000 个物种,即使它们全都没有真正的差异,按 p<0.05 的标准,也会有大约 50 个"碰巧显著"的(50 = 1000 × 0.05)。多重检验校正就是把这些"碰巧显著"的假阳性压下来。
Benjamini-Hochberg (BH) 校正步骤图解:
| 步骤 | 操作 | 白话解释 |
|---|---|---|
| 1 | 把所有 p 值从小到大排序,编号 i=1,2,...,m | m 是总检验数(如 1000 个物种) |
| 2 | 计算每个 p 值的 BH 临界值 = (i/m) × Q | Q 是你设定的 FDR 阈值(通常 0.05) |
| 3 | 从大到小找到第一个 p ≤ 临界值的位置 | 这个位置及之前的所有 p 值都被认为显著 |
调整后 p 值的计算公式:
padj[m] = p[m] 最大的那个 p 值不需要调整,保持原样(m = 总检验数,比如检测了 1000 个物种,m=1000)padj[i] = min(padj[i+1], m * p[i] / i) 从后往前算:m * p[i] / i 就是 BH 校正公式的核心——p 值乘以"总检验数/当前排名"。取 min 是为了保证 padj 从小到大递增(单调性),否则可能出现排名靠前的 padj 反而比排名靠后的大的怪现象padj[i] = min(padj[i], 1) p 值不能超过 1,这是概率的上限Bonferroni vs BH 对比
| 特性 | Bonferroni | Benjamini-Hochberg (BH) |
|---|---|---|
| 控制指标 | FWER(Family-Wise Error Rate) | FDR(False Discovery Rate) |
| 白话 | "保证不犯任何一个假阳性错误" | "允许少量假阳性,但控制比例" |
| 公式 | padj = p × m | padj = p × m / rank |
| 严格程度 | 非常严格,容易漏掉真信号 | 适中,保留更多真信号 |
| 适用场景 | 检验数少(<20)、不容错 | 检验数多(宏基因组/转录组常用) |
| 生信中 | 很少用 | 几乎默认用 BH |
R 代码
p.adjust(p_values, method = "BH") R 内置的多重检验校正函数,一行搞定:p_values 一个数值向量,包含所有原始 p 值(比如 1000 个物种的 Wilcoxon 检验 p 值)method = "BH" 校正方法。常用选项:"BH" 或 "fdr"(两个完全一样,都是 Benjamini-Hochberg)、"bonferroni"(太严格,慎用)、"holm"(比 Bonferroni 稍温和)、"BY"(允许 p 值之间有相关性时用,更保守)p_adj_bonf <- p.adjust(p_values, method = "bonferroni") Bonferroni 校正就是每个 p 值直接乘以检验总数 m,简单粗暴但太严格白话原理:"让 100 棵决策树投票。" 每棵树只看部分数据和部分特征,做出自己的判断,最终少数服从多数。就像请 100 个医生各看部分化验单来诊断,然后投票决定——比一个医生看全部更可靠。
sklearn 代码 + 参数说明
RandomForestClassifier(...) 创建随机森林分类器对象,关键参数:n_estimators=500 森林里种 500 棵决策树。树越多结果越稳定,但训练越慢;一般 100~1000 都可以,超过 1000 收益递减max_depth=None 不限制树的深度,让每棵树充分生长到底。如果担心过拟合可以设成 10~20min_samples_split=5 一个节点里至少有 5 个样本才允许继续往下分。调大这个值=更保守,树更矮,不容易过拟合min_samples_leaf=2 叶子节点(最终预测节点)至少要有 2 个样本。防止出现只有 1 个样本的叶子(那就是死记硬背了)max_features='sqrt' 每次节点分裂时,只从 sqrt(总特征数) 个随机选中的特征中挑最好的。这是随机森林"随机"的核心来源之一,降低树之间的相关性class_weight='balanced' 自动给少数类更高的权重。如果 T2D 组 30 人、健康组 70 人,设 balanced 后模型不会偏向多数类random_state=42 固定随机种子,保证每次运行结果一样(可复现)。42 是机器学习社区的惯例数字,用别的也行n_jobs=-1 用所有 CPU 核心并行训练,加速计算。-1 表示"有几个核就用几个"StratifiedKFold(n_splits=5, shuffle=True, random_state=42) 分层 5 折交叉验证:把数据分成 5 份,每次用 4 份训练、1 份测试,轮 5 次。Stratified 保证每折中 T2D 和健康的比例和原始数据一致;shuffle=True 先打乱数据再分折cross_val_predict(rf, X, y, cv=cv, method='predict_proba')[:, 1] 对每个样本获取交叉验证的预测概率。method='predict_proba' 输出概率而非类别;[:, 1] 取第二列(即属于正类/T2D 的概率)roc_auc_score(y, y_pred_proba) 计算 AUC(ROC 曲线下面积),衡量模型区分两类的能力。1.0=完美,0.5=瞎猜rf.feature_importances_ 训练后自动生成的特征重要性数组,基于 Gini impurity 的下降量。值越大=该物种对分类贡献越大。常用来画 Top 20 重要物种柱状图| 参数 | 默认值 | 白话解释 | 调参建议 |
|---|---|---|---|
| n_estimators | 100 | 森林里有多少棵树 | 一般 100~1000,越多越好但越慢 |
| max_depth | None | 每棵树最多分多少层 | None=不限制;设 10~20 可防过拟合 |
| max_features | 'sqrt' | 每次分裂只随机看几个特征 | sqrt 是分类任务的标准选择 |
| min_samples_split | 2 | 一个节点至少多少样本才能分裂 | 调大=更保守,不容易过拟合 |
| min_samples_leaf | 1 | 叶节点最少多少样本 | 调大=叶子不会太小 |
| class_weight | None | 类别权重 | 'balanced' 用于不均衡数据 |
嵌套交叉验证图解:
AUC / ROC 解读标准:
| AUC 范围 | 解释 | 白话说法 |
|---|---|---|
| 0.5 | 随机猜测 | 模型和抛硬币一样没用 |
| 0.5 ~ 0.6 | 很差(Failed) | 几乎没有区分能力 |
| 0.6 ~ 0.7 | 差(Poor) | 有一点区分能力但不可靠 |
| 0.7 ~ 0.8 | 可接受(Fair/Acceptable) | 有一定预测能力,可以发文章 |
| 0.8 ~ 0.9 | 好(Good) | 模型很不错,临床有参考价值 |
| 0.9 ~ 1.0 | 优秀(Excellent) | 非常强的分类能力(但注意过拟合) |
| 方法 | 用途 | 输入数据 | 输出关键值 | R / Python 函数 |
|---|---|---|---|---|
| Wilcoxon 秩和检验 | 两组单指标比较 | 连续型数值(非正态) | W 统计量, p-value | wilcox.test() |
| Kruskal-Wallis | 多组单指标比较 | 连续型数值(非正态) | H 统计量, p-value | kruskal.test() |
| PERMANOVA | 群落整体差异 | 距离矩阵 + 分组 | R², F, Pr(>F) | vegan::adonis2() |
| DESeq2 | 差异物种/基因 | Count 矩阵 | log2FC, padj | DESeq() + results() |
| BH-FDR 校正 | 多重检验校正 | 一组 p 值 | 调整后 p 值 | p.adjust(method="BH") |
| 随机森林 | 分类预测 | 特征矩阵 + 标签 | AUC, feature_importances_ | RandomForestClassifier() |
统计方法交互速查 | 面试常考统计方法 | 2026 年 5 月生成