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图,根本不知道哪些基因是差异倍数大但表达量低的"噪声"。
我的推荐流程:
- 先画一张MA图,观察整体分布是否对称,低表达区有无异常离散。
- 再画一张火山图,设定p-value和log2FC阈值,标记差异基因。
- 把火山图上标记的显著基因,回探到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图负责告诉你数据能不能用,火山图负责告诉你数据里有什么。两张图一个都不能少。我个人的工作流里,MA图是必查项,火山图是必出图。别把顺序搞反了,也别只画其中一张。
MA图看分布,火山图看筛选。先诊断再筛选,两图互补不打架。