让我们先来玩一个游戏,假设我们想要研究一个人体重的影响因素。现在我邀请你想出三个可能的影响因素。比如我想到了,日常饮食摄入量、身体活动水平、基础代谢率。
接下来,我想请你继续对这三个影响因素做拆解,对于每一个你想到的影响因素,分别再找三个影响因素。
对于日常饮食摄入量,可能有总热量摄入、宏量营养素比例、进食频率与时间。身体活动水平则可能受有氧运动量、重训量、非运动性活动产热。基础代谢率则可能对应年龄、性别、肌肉量。
我们还可以继续拆下去,可以无限地往下拆。比如宏量营养素比例由你今天吃了多少蔬菜、多少肉决定,而吃了多少蔬菜又可以进一步拆成吃了多少绿叶蔬菜、多少有色蔬果。蔬菜又分什么品种、谁种的。体重还会由你开不开心、办公室每天点不点奶茶、半夜愿不愿意出去撸串等各式各样的因素影响。
我们看到的体重是这无限多种因素,一层一层产生影响并导致的最终结果。我们可以说,体重这个最终结果是由一个庞大系统生成出来的结果。这在统计学当中被称作「数据生成过程」简称 DGP。
提醒: 本文当中所有的样本代码都可以重复执行多次,笔者鼓励你多按几次 Run 按钮感受数据生成过程的动态性。
变异
面对这么庞杂的生成系统,一个自然的问题是:在这些密密麻麻的因素里,到底哪个和体重的关联最紧密? 是饮食的波动大时体重也跟着大起大落,还是运动习惯变了体重几乎纹丝不动?
要回答这个问题,我们得先想清楚一件事:一群人的体重,如果全都一模一样,那我们根本没东西可研究。 正因为有人重、有人轻,才有了研究的空间。统计学家把这种「人和人之间不一样的程度」叫作变异。
这概念其实我们每天都在用。比如你带孩子去体检,医生说「你家孩子体重在同龄人里算偏重的」这句话背后的依据就是所有同龄孩子的体重存在变异。如果没有变异,所有孩子一样高,那「偏高」这个词就不存在了。
光说「有变异」还不够,我们想知道变异有多大。比如,是所有人的体重都在 60 到 65 公斤之间晃悠,还是有人 40 公斤、有人 120 公斤?为了衡量这个变异,我们构建了方差这个统计量。
方差的做法是:算出每个人的体重和所有人平均体重的差距,把差距平方(让高于平均和低于平均的偏差不互相抵消),最后取平均。数字越大,说明这群人体重越分散。
有了这把尺子,接下来的问题就好办了:体重的总变异这把尺子量出来是一个数,我们能不能想办法把它切成几块,分别看看饮食这块有多大、运动那块有多大?
巧的是,方差这把尺子有个特别好用的性质:如果几个因素的效应是简单相加的,那么总方差也可以按照这几个因素拆开。 这叫方差的可加性。
方差的可加性
下面让我们看得更具体一点。假设把饮食摄入量和运动量两个因素加在一起,构成一个综合的「生活方式变量」,那这个变量的方差,是不是等于饮食摄入量的方差加上运动量的方差?当这个问题被提出时,方差的可加性就自然地出现了。
在概念上来说,方差的可加性指,对于一个数据集,你把两个变量(比如变量 A、变量 B)做加法,得到第三个变量(变量 C),那么,变量 C 的方差应当等于变量 A 和变量 B 的方差之和。
我们可以试着做一个实验,直接点击下面的运行按钮看看结果是怎样的。如果你好奇,也可以试着读读源码。
const randN = (x) => new Array(x).fill('0').map(() => Math.random());
const mean = (a) => a.reduce((s, x) => s + x, 0) / a.length;
const varArr = (arr) =>
arr.reduce((acc, val) => acc + Math.pow(val - mean(arr), 2), 0) / arr.length;
const addArr = (a, b) => a.map((x, i) => x + b[i]);
const n = 100;
const a = randN(n);
const b = randN(n);
const c = addArr(a, b);
console.log(`样本量是 ${n}`);
console.log(`A 的方差是 ${varArr(a).toFixed(4)}`);
console.log(`B 的方差是 ${varArr(b).toFixed(4)}`);
console.log(`C 的方差是 ${varArr(c).toFixed(4)}`);
console.log(`A 的方差和 B 的方差之和是 ${(varArr(a) + varArr(b)).toFixed(4)}`);
大多数社科教材告诉你的事情基本上就到此为止了,但是如果你观察了代码的运行结果,会发现尽管结果相似,但是 C 的方差并不总是严格地等于 等于 A 与 B 的方差之和。究其原因,让我们深入思考一下,由几个变量构成的系统,除了包含它们的独立影响因素之外,还有什么因素没有被考虑进去?
有点难猜,正确的答案是两个变量之间共享的部分:协方差。
让我们再来拼一下公式:
const randN = (x) => new Array(x).fill('0').map(() => Math.random());
const mean = (a) => a.reduce((s, x) => s + x, 0) / a.length;
const varArr = (arr) =>
arr.reduce((acc, val) => acc + Math.pow(val - mean(arr), 2), 0) / arr.length;
const addArr = (a, b) => a.map((x, i) => x + b[i]);
const cov = (x, y) => {
const mx = mean(x),
my = mean(y);
return x.reduce((s, xi, i) => s + (xi - mx) * (y[i] - my), 0) / x.length;
};
const n = 100;
const a = randN(n);
const b = randN(n);
const c = addArr(a, b);
console.log(`样本量是 ${n}`);
console.log(`A 的方差是 ${varArr(a).toFixed(4)}`);
console.log(`B 的方差是 ${varArr(b).toFixed(4)}`);
console.log(`C 的方差是 ${varArr(c).toFixed(4)}`);
console.log(`A 和 B 的协方差是 ${cov(a, b).toFixed(4)}`);
console.log(
`A 的方差和 B 的方差之和加上二倍协方差: ${(varArr(a) + varArr(b) + 2 * cov(a, b)).toFixed(4)}`
);
你会发现「A 的方差和 B 的方差之和加上二倍协方差」这个数字才和「C 的方差」相等。实际上,每一个方差内部都包含这样的一个矩阵。
比如现实中,体重受到的不只是两个因素影响。如果有W、X、Y、Z四个因素,那两两之间都可能存在协方差,一共有十六种组合。我们可以用一张表来组织它们,让我们假设变量 V 由 W、X、Y、Z 这四个变量构成,那么 V 内部包含的变异结构可以用这个表格来表达:
| W | X | Y | Z | |
|---|---|---|---|---|
| W | cov(WW) | cov(WX) | cov(WY) | cov(WZ) |
| X | cov(XW) | cov(XX) | cov(XY) | cov(XZ) |
| Y | cov(YW) | cov(YX) | cov(YY) | cov(YZ) |
| Z | cov(ZW) | cov(ZX) | cov(ZY) | cov(ZZ) |
在这张表各种,一个变量和自己的协方差就是方差本身。协方差描述了两个变量共同塑造的变异水平,方差描述了一个变量「和自己」共同塑造的变异水平。将表格中所有的元素都合在一起,我们才能还原出完整的变异系统。
换言之,方差的可加性描述的是,一个变异系统当中所有元素之间彼此互动的情况,而不只是「纯粹的方差」。
协方差
对于一些读者而言,协方差是一个很陌生且抽象的词汇。
让我们从你已经熟悉的东西出发。方差,我们说它衡量的是一个变量自己和自己的发散,也就是一群人的体重有多分散,就是体重在和体重自己比。它是一个变量的内部的发散性。
那如果有两个变量,比如饮食摄入量和体重,它们各自都会发散,但它们之间还有一件更复杂的事情发生:它们会一起发散。某人吃得比平均水平多,体重也比平均水平重;另一个人吃得少,体重也轻。这两个变量的偏离,在同步地发生。
这就是协方差想要捕捉的特征:两个变量作为一个系统,它们的联合发散性。
那怎么量化这种「一起偏离」?我们把协方差拆开来看:
Cov(X,Y) = ρ ⋅ σX ⋅ σY
这里 ρ 是相关系数,σX 和 σY 是两个变量各自的标准差,也就是各自的变异水平。
ρ 回答的是「这两个变量的步调有多一致」,永远在 −1 到 1 之间。σX 和 σY 回答的是「这两个变量各自有多大的发散空间」,把两者的变异幅度合在一起,就搭建起来了一个联动的舞台。
协方差是两件事的乘积:步调的一致程度,乘上各自变异幅度的合力。一个本身就很平稳的系统,哪怕步调完全一致,能互相传递的扰动也有限;一个高度发散的系统,哪怕只有一点点联动,协方差也可能很大。
现在让我们用这个公式验证一些边界情况,看看它们是否符合直觉。
如果让 Y = X,也就是两个变量是同一个变量,那 σX = σY,那么公式会变成, Cov(X,X) = ρ ⋅ σX2。如你所知,一个变量跟自己的相关性系数一定是 1,那么公式可以进一步简化成 Cov(X,X) = σX2,恭喜你!重新发明了方差!这印证了我们一开始说的:方差是协方差的特例,是一个变量和自己的联合发散性。
当 ρ=0 时,协方差为零。这意味着两个变量之间没有任何系统性的联动。知道某人吃得比平均水平多,对猜测他体重偏高还是偏低毫无帮助。两个变量只是各自独立地在发散,互不相干。当 ρ < 0 时,协方差为负。两个变量反向联动,一个偏高时,另一个倾向于偏低。比如运动量和静息心率,往往就是这种关系。
独立变量
下面我邀请你仔细看看我们之前的数据生成过程,你会发现,X 和 Y 两个变量是完全独立生成的。换言之,他俩在生成的时候谁都没参考对方的数字,因为在 Ground Truth,也就是「上帝的石碑」这个视角下,二者是完全独立的。但如果我们计算二者的相关性系数:
const randN = (x) => new Array(x).fill('0').map(() => Math.random());
const mean = (a) => a.reduce((s, x) => s + x, 0) / a.length;
const varArr = (arr) =>
arr.reduce((acc, val) => acc + Math.pow(val - mean(arr), 2), 0) / arr.length;
const cov = (x, y) => {
const mx = mean(x), my = mean(y);
return x.reduce((s, xi, i) => s + (xi - mx) * (y[i] - my), 0) / x.length;
};
const corr = (x, y) => cov(x, y) / Math.sqrt(varArr(x) * varArr(y)); // Pearson
const n = 100;
const a = randN(n);
const b = randN(n);
console.log(`样本量是 ${n}`);
console.log(`A 的均值是 ${mean(a).toFixed(4)}`);
console.log(`B 的均值是 ${mean(b).toFixed(4)}`);
console.log(`A 和 B 的相关系数是 ${corr(a, b).toFixed(4)}`);
你会发现多多少少都会算出来一个数字。这不是因为二者不独立,而是相关系数作为一种推测两个变量关联的工具,一定会受到噪声影响产生误差。相关系数为 0 的两组数据在数学上被称作「正交」关系。
请一定注意,从数据生成过程的角度来讲,哪怕两组数据的生成过程是完全「独立」的,只要噪声存在,那么大概率不会产生完全「正交」的数据。在实际的研究过程中,你也几乎不可能收集到相关系数为 0 的两组变量。
最后
至此,你应当理解了,「方差的可加性」这词仅在所有变量都是正交的情况下才成立,而大多数情况下你都不太可能得到完全正交的数据,因而协方差的作用必须被关注。这点在处理广义线性模型当中的共线性问题时非常重要,请各位读者无比理解其含义。
此外,对于开篇,如果你在构建变量 C 的时候用的是 A - B,那么得到的结果也是一样的:
const randN = (x) => new Array(x).fill('0').map(() => Math.random());
const mean = (a) => a.reduce((s, x) => s + x, 0) / a.length;
const varArr = (arr) =>
arr.reduce((acc, val) => acc + Math.pow(val - mean(arr), 2), 0) / arr.length;
const subArr = (a, b) => a.map((x, i) => x - b[i]);
const cov = (x, y) => {
const mx = mean(x),
my = mean(y);
return x.reduce((s, xi, i) => s + (xi - mx) * (y[i] - my), 0) / x.length;
};
const n = 100;
const a = randN(n);
const b = randN(n);
const c = subArr(a, b);
console.log(`样本量是 ${n}`);
console.log(`A 的均值是 ${mean(a).toFixed(4)} 方差是 ${varArr(a).toFixed(4)}`);
console.log(`B 的均值是 ${mean(b).toFixed(4)} 方差是 ${varArr(b).toFixed(4)}`);
console.log(`C 的均值是 ${mean(c).toFixed(4)} 方差是 ${varArr(c).toFixed(4)}`);
console.log(`A 和 B 的协方差是 ${cov(a, b).toFixed(4)}`);
console.log(
`A 的方差和 B 的方差之和减去二倍协方差: ${(varArr(a) + varArr(b) - 2 * cov(a, b)).toFixed(4)}`
);
因为方差这东西自己带平方,所以正负没什么影响,聪明的你一定能理解原因。
最后,在大多数描述变异的统计参数中,只有方差可以比较容易地满足可加性原则,因为数学平方属性在消除正负抵消、保证正交分解上的独特优势;另外,平方函数的代数性质使得展开后各项可以整洁地分离。这也是为什么我们在做统计建模的时候(t 检验、方差分析、线性回归都算统计建模)偏好使用方差作为误差的衡量标准。
这块看不懂的话也没什么关系,不影响你理解统计方法。
附带一提,大多数大语言模型都不能辨析「独立」和「正交」,如果你把我们这次分享当中的第一段代码喂给大语言模型,大多数都会将其归因于「你的样本量太小」、「你没有通过多次抽样形成分布看期望」,这些都不是对方差可加性的正确解释。请各位读者在通过大语言模型学习统计学的时候一定注意辨析这个问题。

Loading comments...