MA图与火山图的区别与联系

说真的,我刚入行那会儿,也觉得MA图和火山图就是一回事。都是散点图,都是看基因表达变化,为什么要搞两套东西?后来在一次组会汇报上,我被导师问住了——"你这张图想告诉我差异倍数大,还是统计可信度高?"我支支吾吾答不上来。嗯,从那以后我才真正理解了这两个图的定位完全不同。

视觉上的第一印象

你想想看,MA图横轴是平均表达量(A值),纵轴是差异倍数(M值)。它主要回答一个朴素问题:低表达基因的差异靠不靠谱?

火山图就不一样了。横轴log2(Fold Change),纵轴-log10(p-value)。它在乎的是:差异够大吗?统计够显著吗?

我个人的习惯是:做差异分析之前先看MA图,做筛选之后再画火山图。顺序不能反。

核心区别一:坐标系的哲学

维度MA图火山图
横轴A = (C1 + C2)/2,平均表达量log2(Fold Change),差异倍数对数
纵轴M = C1 - C2,差异倍数对数-log10(p-value),显著度负对数
关注点表达量范围与差异的关系差异大小与统计显著性的双重筛选
典型用途数据质控、标准化评估差异基因筛选、标记基因展示

这里有一点容易忽略。MA图的纵轴和火山图的横轴,都是log2(Fold Change)。但MA图用纵轴放差异,火山图用横轴放差异。我刚开始做的时候,总觉得火山图把差异倍数放横轴,看起来很别扭。后来习惯了才发现,这样更直观——越往两边,差异越大。

一个小技巧
我个人习惯在MA图上叠加"低表达区"的色块,一般A值小于5的区域用浅灰底色覆盖。这样可以快速判断低表达差异是否集中在灰色区。如果是,说明你的数据标准化做得还行。

核心区别二:MA图看"分布",火山图看"筛选"

MA图不要设阈值。真的,我见过太多人在MA图上画水平线标差异倍数。

为什么要这么做?

  • MA图的首要任务:检查低表达区域的离散度是否异常。如果A值很小的区域M值乱飞,说明标准化有问题。
  • 火山图的首要任务:用两条线——一条水平p-value阈值,两条垂直log2FC阈值——把显著差异基因圈出来。

说白了,MA图是诊断工具,火山图是展示工具。

我还踩过一个坑。有一次做单细胞数据,用MA图看质控觉得一切正常,但火山图画出来一个显著差异基因都没有。为什么?因为我用了错误的p-value计算方法。MA图根本不会暴露这个问题,它只看表达量高低和差异倍数,从不关心统计假设。

联系:同一个数据,两张面孔

这两张图的原始数据一模一样,只是投影方式不同。

假设你有三个样本的count矩阵:

基因  对照1  对照2  处理1  处理2
A      100    120     50     60
B        2      1    200    180
C     5000   5200   4800   5100

对基因B:平均表达量极低,但差异倍数极大。MA图上它会飞在左上角或右上角(取决于方向),火山图上它会在横轴远端但p-value可能不显著——因为低表达基因的统计检验方差大,容易不显著。

遇到过这种情况吗?我遇到过。有一次客户拿着火山图追问:为什么这个基因差异倍数超过50倍,还不是显著差异基因?我让他回去看MA图,那个基因落在A值小于1的区域——说白了,表达量太低,统计检验没有把握说它可靠。

什么时候该用哪个?

避坑指南
我曾经在客户汇报时只用火山图,被问到一个很简单的问题:你筛选出的这300个基因,在低表达区有多少个?我当场答不上来。因为我没看MA图,根本不知道哪些基因是差异倍数大但表达量低的"噪声"。

我的推荐流程:

  1. 先画一张MA图,观察整体分布是否对称,低表达区有无异常离散。
  2. 再画一张火山图,设定p-value和log2FC阈值,标记差异基因。
  3. 把火山图上标记的显著基因,回探到MA图上,看它们在A轴上的分布位置。

如果你的显著基因大量集中在A值小于2的区域,我建议你重新检查标准化步骤,或者考虑使用更稳健的差异检验方法。

画图代码:同一个数据源两幅图

这里用Python演示一下。数据是模拟的,你直接替换成你的表达矩阵就行。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 模拟数据
np.random.seed(42)
n_genes = 1000
ctrl = np.random.negative_binomial(5, 0.3, size=n_genes).astype(float)
treat = ctrl * np.random.lognormal(0, 0.5, size=n_genes)

# 计算MA图参数
avg_expr = (np.log2(ctrl + 1) + np.log2(treat + 1)) / 2
log2fc = np.log2(treat + 1) - np.log2(ctrl + 1)

# 计算火山图参数
from scipy.stats import ttest_ind
pvals = np.array([ttest_ind([ctrl[i], treat[i]], [])[1] for i in range(n_genes)])

# MA图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
ax1.scatter(avg_expr, log2fc, s=5, alpha=0.6)
ax1.axhline(0, color='red', linestyle='--')
ax1.set_xlabel('Average log2 expression (A)')
ax1.set_ylabel('log2 Fold Change (M)')
ax1.set_title('MA Plot')

# 火山图
logp = -np.log10(pvals + 1e-300)
ax2.scatter(log2fc, logp, s=5, alpha=0.6)
ax2.axhline(-np.log10(0.05), color='blue', linestyle='--')
ax2.axvline(1, color='red', linestyle='--')
ax2.axvline(-1, color='red', linestyle='--')
ax2.set_xlabel('log2 Fold Change')
ax2.set_ylabel('-log10 p-value')
ax2.set_title('Volcano Plot')

plt.tight_layout()
plt.show()

注意一点,上面代码里p-value的计算方式比较粗糙。实际项目中我一般用DESeq2或edgeR的统计检验,不做t-test。

核心知识体系

下面这张图把MA图和火山图的关系梳理了一下。你看完应该能记住:MA图是诊断,火山图是展示,两者互补。

MA图 vs 火山图·关系框架 表达矩阵 / Count Table MA图 y = log2FC | x = A值 诊断:评估数据质量 低表达区离散度 标准化效果检查 火山图 x = log2FC | y = -log10(p) 筛选:标记差异基因 设定p-value阈值 设定log2FC阈值 回探:差异基因低表达占比 可信差异基因列表

嗯,总结一下:MA图负责告诉你数据能不能用,火山图负责告诉你数据里有什么。两张图一个都不能少。我个人的工作流里,MA图是必查项,火山图是必出图。别把顺序搞反了,也别只画其中一张。

一句话记牢
MA图看分布,火山图看筛选。先诊断再筛选,两图互补不打架。