1. 痛点:统计学课本里的“迷宫”
回想一下你学统计学的经历,是不是感觉像在背一本厚厚的“菜谱”?
- 比较两组人的身高?用 独立样本 t 检验。
- 比较三个班级的成绩?用 方差分析 (ANOVA)。
- 看身高和体重的关系?用 皮尔逊相关系数 (Pearson Correlation)。
- 自己和自己比(比如吃药前 vs 吃药后)?用 配对 t 检验。
考前大家都在狂背一张“统计检验决策树”:如果满足正态分布且方差齐性,走左边;如果不齐,走右边……生怕用错。但其实,这几十种让人头晕眼花的检验方法,本质上全是一个东西。
这也是为什么 Jonas Kristoffer Lindeløv 的那篇 Common statistical tests are linear models (or: how to teach stats) 会引起那么大共鸣的原因。
这篇文章,我们要用初中数学里最简单的直线方程,来扒掉这些统计检验的马甲。看完这篇,你的统计学思路会清晰很多。
2. 万物之源:初中数学里的直线方程
初中我们就学过直线方程:
在统计学里,为了显得高大上,大家换了个写法(但意思完全一样):
别怕这几个希腊字母,我们来翻译一下:
- (因变量):你想预测的东西(比如:体重)。
- (自变量):你用来预测的线索(比如:身高)。
- (截距):当 为 0 时, 的基础值(也就是初中公式里的 )。
- (斜率): 每增加 1 个单位, 会增加多少(也就是初中公式里的 )。
- (误差):现实世界不完美,总有预测不准的部分,这个小尾巴代表随机误差。
核心结论:不管是 t 检验还是 ANOVA,它们全都是在求上面这个方程里的 和 !
口说无凭,我们直接上代码,拿真实数据来跑一跑。
3. 见证奇迹的时刻
案例 1:皮尔逊相关系数 = 简单的线性回归
场景:我们想看看汽车的“马力 (hp)”和“百公里加速时间 (qsec)”有没有关系。传统做法是算一个皮尔逊相关系数。
这有何难呢?求两个变量的相关性,本质上就是在这两组数据之间画一条拟合得最好的直线。
# 拿 R 语言自带的 mtcars 数据集做演示
# 1. 传统的皮尔逊相关检验
cor_test <- cor.test(mtcars$hp, mtcars$qsec)
print(cor_test$p.value)
# 结果: 5.766253e-06
# 2. 强迫症做法:用线性模型 (Linear Model, lm) 来算
linear_model <- lm(qsec ~ hp, data = mtcars)
# 查看线性模型中 hp 这一项的 p 值
summary(linear_model)
# hp 的 p 值: 0.5.77e-06发现了吗?两者的 p 值分毫不差! 相关性检验,本质上就是线性模型 。当你在做相关分析时,你其实就是在问大自然:“(斜率)是不是显著不等于 0?”
案例 2:独立样本 t 检验 = 带有“开关”的线性模型
场景:我们想比较“自动挡 (am=0)”和“手动挡 (am=1)”汽车的油耗 (mpg) 是否有显著差异。刚需做法是用独立样本 t 检验。
这时候你可能会问:等等, 是连续的数字(比如马力)我能理解,但如果是“自动挡”和“手动挡”这种分类变量,怎么代入 这个数学公式里呢?总不能拿汉字去乘 吧?
天才的发明:哑变量(Dummy Variables)
统计学家想了个极其丝滑的绝招:把分类变成 0 和 1 的开关。
- 让 代表自动挡。
- 让 代表手动挡。
现在把 代入方程:
- 如果是自动挡 ():
- 所以,截距 的实际物理意义,就是自动挡汽车的平均油耗!
- 如果是手动挡 ():
- 所以,斜率 的物理意义,就是手动挡比自动挡多出来的油耗差值!
既然 代表的是“两组的均值差异”,那么检验两组是否有差异(t 检验),等价于检验 是否等于 0(线性模型)。
# 1. 传统的独立样本 t 检验 (假设方差齐性)
t_test <- t.test(mpg ~ am, data = mtcars, var.equal = TRUE)
print(t_test$p.value)
# 结果: 0.0002850207
# 2. 也是服了,直接用线性模型
lm_ttest <- lm(mpg ~ am, data = mtcars)
summary(lm_ttest)
# am 这一项的 p 值: 0.000285再一次,p 值完全一样! t 检验的马甲被扒掉了,它就是自变量 只有 0 和 1 两个取值的特殊线性回归。
案例 3:单样本 t 检验 = 只有截距的线性模型
场景:我想知道这批汽车的平均油耗 (mpg) 是不是等于 20。
这个听起来更离谱了,连对比的组都没有,哪来的 呢? 答案是:没有 。
我们的模型退化成了:
这里 就代表所有汽车油耗的总平均值。你想检验平均值是否等于 20,也就是检验截距 是否等于 20。
# 1. 传统单样本 t 检验 (检验 mpg均值是否为 20)
one_sample_t <- t.test(mtcars$mpg, mu = 20)
print(one_sample_t$p.value)
# 结果: 0.9327306
# 2. 用线性模型(注意公式里的 `~ 1`,代表只保留截距)
# 因为默认检验截距是否为0,所以我们把数据都减去 20,这样截距如果为0,就说明原均值为20
lm_one_sample <- lm(I(mpg - 20) ~ 1, data = mtcars)
summary(lm_one_sample)
# Intercept(截距) 的 p 值: 0.933丝般顺滑。
案例 4:配对 t 检验 = 算个差值再跑单样本回归
场景:你要测试一种减肥药,找了 10 个人,记录他们“吃药前”和“吃药后”的体重。课本告诉你,因为是同一个人前后的对比,数据不独立,必须用配对 t 检验。
很多新手到这里又晕了。这有何难呢? 既然是同一个人,我们直接把“吃药后的体重 - 吃药前的体重”,算出一个差值 (Difference) 不就行了?
算完差值之后,问题就变成了:“这 10 个人的体重差值,平均下来是不是等于 0?” 哎?等等,这不就是刚刚的**案例 3(单样本 t 检验)**吗!
既然回到了单样本 t 检验,那它自然也就变成了那个只有截距的线性模型:
进阶吐槽:如果你想显得更专业,统计学里还有一个高级玩法来处理这种“同一个病人测多次”的数据,叫做混合效应模型 (Mixed Effects Model)。它允许你在模型里加一个代表“人”的专属开关(随机效应):
lmer(y ~ time + (1|subject))。本质上,依然没逃出线性模型的手掌心。
案例 5:方差分析 (ANOVA) = 多个开关的线性模型
场景:比较 4 缸、6 缸、8 缸汽车(3个组)的油耗是否有差异。
在 t 检验里,我们用了一个 (0 和 1)来区分两组。现在有 3 组怎么办? 很简单,多加几个“开关(哑变量)”就行了!
模型变成:
- 选 4 缸作为基准组(全部开关关掉:)。此时 ,所以 就是 4 缸车的平均油耗。
- 是 6 缸开关。如果它是 6 缸车,就让 。此时 。 就是 6 缸比 4 缸差多少。
- 是 8 缸开关。如果它是 8 缸车,就让 。此时 。 就是 8 缸比 4 缸差多少。
ANOVA 检验各组是否有差异,本质上就是检验“这些开关的斜率是不是全都是 0”。
# 把缸数变成因子(分类变量)
mtcars$cyl_factor <- as.factor(mtcars$cyl)
# 1. 传统的 ANOVA
anova_test <- aov(mpg ~ cyl_factor, data = mtcars)
summary(anova_test)
# Pr(>F) 即 p 值: 4.98e-0 (非常显著)
# 2. 依然用线性模型
lm_anova <- lm(mpg ~ cyl_factor, data = mtcars)
# 查看整个模型的 F 检验 p 值(在 summary 的最下面一行)
summary(lm_anova)
# F-statistic p-value: 4.979e-09还是那个熟悉的配方,一模一样的结果。
案例 6:协方差分析 (ANCOVA) = 多加一个变量的线性模型
场景:比较 A、B 两种降压药的效果。但你发现,吃 A 药的病人普遍年纪偏大。为了防止“年龄”这个混杂因素背锅,课本要求你用协方差分析 (ANCOVA),把“年龄”作为协变量控制起来。
听名字“协方差分析”特别唬人,感觉又要学一套新理论。其实呢?就是小学难度的加法。 在比较两组差异( = 0或1)的基础上,直接把“年龄”作为第二个自变量()塞进方程里:
就这么简单!在这个多元回归模型里,算出来的 就是“排除了年龄影响后,A药和B药的真实差异”。所谓的 ANCOVA,不过是多了几个 的线性模型而已。
案例 7:非参数检验 = 给数据排个名再跑线性模型
场景:你要比较两组数据,但数据里有极端异常值,或者分布极其诡异(不满足正态分布)。课本严厉警告你:这时候绝对不能用 t 检验了!必须用非参数检验(比如 Wilcoxon 秩和检验,或者 Mann-Whitney U 检验)。
很多同学一听“非参数”这三个字就发怵,觉得又得学一套全新的数学体系。这到底是什么鬼?其实呢,这玩意儿的本质,依然是线性模型!
统计学家玩了一个极其聪明的“偷梁换柱”:既然原始数据分布太乱,那我们就不看具体数值了,只看排名(Rank)。
- 比如你有三个数:
[1.2, 100, 10000] - 直接算均值肯定被那个
10000带偏了。 - 怎么办?给它们排个名,变成:
[1, 2, 3](即第一名,第二名,第三名)。
非参数检验的本质,就是把原始的因变量 替换成它的排名 ,然后再去跑那个我们烂熟于心的带有“开关”的线性模型!
模型变成了:
# 1. 传统的 Wilcoxon 秩和检验 (非参检验两组差异)
# 比较自动挡和手动挡的马力 (hp)
wilcox_test <- wilcox.test(hp ~ am, data = mtcars)
print(wilcox_test$p.value)
# 结果: 0.04570132
# 2. 我也是服了,直接对排名跑线性模型!
# 注意看:我们只是把 y 换成了 rank(y)
lm_rank <- lm(rank(hp) ~ am, data = mtcars)
summary(lm_rank)
# am 这一项的 p 值: 0.04156你看,算出来的 p 值极其接近!(两者那微小的差异,仅仅是因为传统的 Wilcoxon 函数在底层做了一些关于相同排名 (tied ranks) 和样本量连续性的细微校正,但在数学本质上,它们是等价的)。
如果是比较三个组以上的非参数检验(即大名鼎鼎的 Kruskal-Wallis 检验)呢?
这有何难呢?无非就是把因变量变成排名,然后自变量用多个开关:lm(rank(y) ~ x1 + x2)。
至此,非参数检验的马甲也被扒掉了:它无非就是给原始数据套上了一个 rank() 滤镜,然后继续没心没肺地跑线性回归。
4. 生信暴击:你跑的 DESeq2 和 limma,也是线性模型!
看到这里,你可能觉得:好啦好啦,我知道基础统计学就是线性模型了,但我平时做生物信息学分析,跑的可是高大上的转录组差异分析包啊!
如果你打开过 DESeq2 或者 limma 的说明文档,你会发现里面满屏都是 design = ~ condition 这种公式。
这到底是什么鬼?这依然是线性模型!准确地说,是它的终极进化版——广义线性模型(Generalized Linear Model, GLM)。
什么是广义线性模型 (GLM)?为什么要“广义”?
前面我们讲的 叫一般线性模型,它有一个致命的弱点:它假设 可以是任何实数(正数、负数、小数),而且它的误差满足正态分布(钟形曲线)。这在预测身高体重时没问题。
但生信里的数据往往很“奇葩”:
- RNA-seq 的 Count 值:只能是非负整数(0, 1, 2, 3…),不可能测出 -5 个 read,也不可能测出 2.5 个 read。
- 临床结果:病人要么存活 (1),要么死亡 (0),只有两个离散状态。
这时候,如果强行用一条穿过宇宙的直线去拟合只能取正整数的点,模型就会预测出“某个基因的表达量是 -3.2”这种荒谬的结果。
为了解决这个问题,统计学家引入了一个超级变压器,叫做联系函数(Link Function)。 我们不再直接用直线去预测 ,而是用直线去预测 的某种变形:
- 如果你研究的是 RNA-seq counts,它服从负二项分布(Negative Binomial),我们就用对数(log) 作为联系函数:。这样一来,无论右边的直线算出来是个多大的负数,经过指数还原回去, 永远是个正数。
- 如果你研究的是病人的生死(0和1),它服从二项分布,我们就用 logit 函数:。这就是大名鼎鼎的逻辑回归(Logistic Regression)。
所以,广义线性模型 (GLM) 并没有脱离线性模型的核心,它只是在线性方程的左边加了一个“滤镜(Link Function)”,然后假设数据符合某个特定的分布(比如泊松分布或负二项分布)。
案例 8:转录组差异表达分析 = 并发跑两万个 GLM
场景:你测了 3 个正常样本(Control)和 3 个患病样本(Disease)的 RNA-seq,想找出差异表达基因(DEGs)。
在这个场景下:
- 你的 是某一个基因的表达量(Count 值,服从负二项分布)。
- 你的 是样本的分组(0 = Control,1 = Disease)。
- 联系函数是 。
在 DESeq2 的底层,它其实就是对着你的两万个基因,分别建了两万个带有对数滤镜的线性方程:
我们来看看这里的 代表什么:
- (截距):当 (即 Control 组)时,该基因在对数尺度上的基础表达量。
- (斜率):当 变成 1(即 Disease 组)时,该基因的 增加了多少。
绝杀来了:在生信里,这个 有一个鼎鼎大名的专属称呼——它就是 Log2 Fold Change (log2FC)!
当你用 DESeq2 找差异基因,设定 p-value < 0.05 且 |log2FC| > 1 时,你本质上是在问大自然:
“嘿,GLM,对于基因 A 来说,你那个叫 的斜率,是不是显著不等于 0?并且它的绝对值是不是大于 1?”
这也是为什么 DESeq2 的核心函数长这样:
# 这里的 ~ condition,就是省略了 y 的线性方程右半边!
dds <- DESeqDataSetFromMatrix(countData = cts,
colData = coldata,
design = ~ condition)如果你的实验设计变复杂了,比如不仅有 Control/Disease,还分了 Male/Female 两个性别。传统检验直接抓瞎,但在生信里,你只需要把设计公式改成:
design = ~ sex + condition这不就是我们在上一节讲的多因素回归吗:。通过把 sex 放进模型里,它就能把性别的干扰项“吸走”,从而算出最纯粹的由疾病引起的差异()。
这就是线性模型的统治力。人生苦短,不要去硬刚那些乱七八糟的检验名字。
5. 总结:大道至简
看懂了这些,以前那种考前背“检验决策树”的日子就可以结束了。
以前,你需要记住:
- 2 组分类 vs 连续 = t 检验
- 3 组分类 vs 连续 = ANOVA
- 连续 vs 连续 = 相关/回归
现在,你只需要记住一句话: 把你要预测的东西放在等式左边 (),把你的线索放在等式右边 ()。无论是分类还是连续,全都丢进 这个线性模型里就行了。
它让你明白,统计学的核心不是在玩弄各种高深的检验公式,而是在做建模——试图用一个最简单的数学方程,去拟合、去解释这个复杂世界的运行规律。
下一次有人问你“两组数据比较该用什么检验”时,你不妨微微一笑:“别折腾那些名字了,跑个线性模型吧,这有何难呢?”
参考资料
- Jonas Kristoffer Lindeløv, Common statistical tests are linear models (or: how to teach stats)
- Michael I. Love, Wolfgang Huber, Simon Anders, Moderated estimation of fold change and dispersion for RNA-seq data with DESeq2