上次带一位朋友复盘他的脚本,他在算两个批次差值的波动,代码里写的是var(x) - var(y),跑出来是负数,他第一反应是数据脏了。我没直接给答案,让他先把协方差那一行补上,结果负数没了,剩下的偏差全落在样本量上。协方差这个量,单看定义平平无奇,真正好用的是它在加减、缩放、求和、分组这些操作上的那套拆分规则——只要把拆分规则吃透,一大类看起来吓人的方差推导,都能拆成几块熟面孔拼起来。
下面这些内容,是我这些年做数据分析和概率推导时反复用到的东西,从定义式的符号含义一路讲到wᵀΣw和全协方差公式,中间穿插手算顺序、代码验证和几个高频踩坑点。适合已经学过一点概率统计、但在推导时经常卡壳的人,也适合完全零基础、只想把公式用对的人。公式本身不长,难的是知道哪一步能拆、哪一步不能拆、拆完符号怎么走。
1. 协方差在度量什么:从定义式到共变的直觉
1.1 乘积期望里每一项的符号含义
先把定义摆出来:Cov(X, Y) = E[(X - E(X))(Y - E(Y))]。很多人背得下来,但没细想过这个乘积到底在干什么。把它拆成两个偏离量:X - E(X)是 X 相对自己均值偏了多少,Y - E(Y)是 Y 相对自己均值偏了多少。两者相乘,符号规则只有四种情况——同正得正、同负得正、一正一负得负。也就是说,当 X 和 Y 倾向于朝同一个方向偏离各自的均值时,乘积为正的样本占多数,期望就是正的;反过来就是负的。
这个符号规则解释了一件事:协方差本质上是一个"同向性"的度量,而不是"大小"的度量。所以它带着量纲,单位是 X 的单位乘 Y 的单位。你用"米"和"千克"算出来的协方差,单位是"米·千克",换个单位制数字就全变了。这也是为什么跨数据集比较协方差没有意义,必须标准化成相关系数ρ = Cov(X,Y) / (σ_X σ_Y)才能横向看。我在给别人解释的时候喜欢用一个类比:协方差像两个人走路的步频同步程度,但一个人按秒计、一个人按分钟计,你算出来的"同步值"数字完全不同,可这种同步关系本身没变。
还有一个容易忽略的点:协方差衡量的是线性同向性。如果 Y 和 X 是 U 型关系,比如Y = X²且 X 关于 0 对称,那协方差会是 0,但两者明显不独立。这一点在后面讲"协方差为零不等于独立"时还会展开,因为拆公式的时候,很多人会下意识把"协方差为 0"直接当成"可以随便拆、随便化简"的通行证,这是推导出错的常见源头。
1.2 分母取 n 还是 n-1,在协方差上更容易翻车
方差的样本估计有除以 n 和除以 n-1 两个版本,协方差同样有,而且坑更深。样本协方差的无偏估计写的是S_xy = Σ(x_i - x̄)(y_i - ȳ) / (n - 1),分母是 n-1 而不是 n。原因和方差一样:你用了x̄和ȳ这两个估计值去中心化,等于给数据加了两个约束,自由度从 n 掉到 n-1。如果用 n,得到的是有偏估计,期望会系统性偏小,偏的系数是(n-1)/n。
这个小偏差在样本量大时可以忽略,但在小样本场景下会实打实地改变结论。我遇到过一次,某团队用 20 个样本算两个指标的协方差,代码里用的是np.cov的默认行为(ddof=1,也就是除以 n-1),而他们自己手算校验时用的是除以 n,两边差了约 5%,然后开始怀疑数据源。查了两小时才发现是分母口径不一致。这类问题没有技术含量,但极其消耗时间,所以我的习惯是:任何一次手算校验,先把分母口径写在纸上,再动笔。
顺便说一句,numpy.cov默认ddof=1,pandas的DataFrame.cov()默认也是无偏版本,而有的统计库默认走最大似然口径(除以 n)。跨库对比数字时,这一项必须先对齐,否则后面所有推导都会带着一个系统性偏差在跑。
1.3 协方差为零不代表独立:一个必须亲手算的反例
这个反例我建议每个人都自己推一遍,因为它决定了你后面敢不敢随便把协方差项划掉。取 X 服从关于 0 对称的分布(标准正态就行,或者取 -1、0、1 等概率的离散分布),令Y = X²。显然 Y 完全由 X 决定,两者极度不独立。但算协方差:E(X) = 0,E(Y) = E(X²) = 1,于是Cov(X, Y) = E[X·X²] - 0·1 = E(X³)。
对于关于 0 对称的分布,E(X³) = 0(奇函数在对称分布下的期望为零,前提是三阶矩存在)。所以协方差是 0,但两者显然不独立。这个例子的价值在于:它把"协方差为零"和"独立"这两件事彻底切开了。协方差只捕捉线性关系,Y 和 X 的关系是纯二次的,线性部分为零。
在拆分公式时,这个区分非常关键。当你在Var(X + Y) = Var(X) + Var(Y) + 2Cov(X, Y)里看到协方差项,如果题目只告诉你"X 与 Y 不相关"(也就是协方差为零),那你可以把这一项划掉;但如果题目说的是"X 与 Y 的协方差为零",你不能由此推出Var(X·Y) = Var(X)·Var(Y)之类的结论,因为乘积的方差需要更高的矩信息,跟协方差为不为零没关系。我见过有人在这上面栽跟头,把一个明显非线性的关系硬套成独立假设,最后结果对不上。
2. 双线性:协方差能被"拆开"的全部底气
2.1 Cov(X+Y, Z) 的完整推导与成立前提
所有拆分规则都来自同一个性质:协方差对两个参数分别线性,也就是双线性。推导Cov(X + Y, Z)只需要三步,我把它写全,因为中间那步"期望的线性性"是整条链子的关键。
从定义出发:
Cov(X + Y, Z) = E[((X + Y) - E(X + Y)) · (Z - E(Z))] = E[((X - E(X)) + (Y - E(Y))) · (Z - E(Z))]第二步是把E(X+Y)拆成E(X) + E(Y),这一步永远成立,不需要任何独立性假设。第三步展开括号:
= E[(X - E(X))(Z - E(Z))] + E[(Y - E(Y))(Z - E(Z))] = Cov(X, Z) + Cov(Y, Z)拆成两个期望相加,用的是期望的线性性E(A + B) = E(A) + E(B),同样不需要独立性。所以Cov(X + Y, Z) = Cov(X, Z) + Cov(Y, Z)是一条无条件成立的恒等式,只要涉及的期望都存在且有限。
这里"期望存在且有限"不是一句客套话。柯西-施瓦茨不等式告诉我们,如果Var(X)和Var(Y)都有限,那么|Cov(X, Y)| ≤ σ_X σ_Y也有限,协方差一定存在。但如果某个变量的方差无穷(比如某些重尾分布),协方差可能根本不存在,这时候谈"拆分"就没意义了。实务中大部分数据不会走到这一步,但做理论推导时要留个心眼。
同理可以推出Cov(X - Y, Z) = Cov(X, Z) - Cov(Y, Z),以及对称版本Cov(Z, X ± Y) = Cov(Z, X) ± Cov(Z, Y)。这四个式子合起来,就是"协方差对加减法可以逐项拆"的完整表述。
2.2 常数项与伸缩系数怎么搬进搬出
双线性的另一半是数乘。规则是Cov(aX + b, cY + d) = ac · Cov(X, Y)。这个式子里的加法常数b、d直接被吃掉了,乘法系数a、c提出来相乘。我建议每个人都自己推一遍常数为什么消失:aX + b减去自己的均值是aX + b - aE(X) - b = a(X - E(X)),b在减法里消掉了。所以平移不改变偏离量,自然不改变协方差。
这条规则在实际计算里出现频率极高,而且符号最容易搞错。举几个具体例子:
Cov(-X, Y) = -Cov(X, Y),负号直接提出来。Cov(2X, 3Y) = 6Cov(X, Y),系数相乘。Cov(X + 5, Y - 3) = Cov(X, Y),常数全消。Cov(-2X + 1, -3Y + 7) = 6Cov(X, Y),两个负号相乘得正。
最后这个例子特别值得注意。很多人在做变换时会漏掉"负负得正",尤其是在处理"取相反数"的变量时。比如在金融里把亏损定义成负收益,如果两个变量都取了负号,协方差其实没变;如果只取一个,协方差变号。这个细节在推导组合方差时如果弄错,会导致整个风险预算的符号反掉。
还有一条常被混淆的:Cov(X, X) = Var(X)。这不是巧合,代入定义就是E[(X - EX)(X - EX)] = E[(X - EX)²],正是方差的定义。所以方差是协方差的一个特例,协方差是方差的推广——理解了这一点,后面Var(X + Y)的推导就变成了纯机械操作。
2.3 把加减拆分规则压成一张表
规则不多,但组合起来容易乱,我习惯把它们整理成一张表,推导时对着看。
| 表达式 | 结果 | 说明 |
|---|---|---|
Cov(X, Y) | Cov(Y, X) | 对称性,顺序无关 |
Cov(X, X) | Var(X) | 方差是特例 |
Cov(X, c) | 0 | 常数与任何变量协方差为零 |
Cov(X + Y, Z) | Cov(X, Z) + Cov(Y, Z) | 对第一个参数线性 |
Cov(X - Y, Z) | Cov(X, Z) - Cov(Y, Z) | 减法版本 |
Cov(Z, X + Y) | Cov(Z, X) + Cov(Z, Y) | 对第二个参数线性 |
Cov(aX + b, cY + d) | ac · Cov(X, Y) | 数乘与平移 |
Cov(ΣXᵢ, Y) | Σ Cov(Xᵢ, Y) | 有限和可逐项拆 |
这张表里最有信息量的是第三行Cov(X, c) = 0。常数没有波动,它的偏离量恒为零,乘什么都是零。这条规则在展开多元表达式时能砍掉大量项。比如Cov(X + 3, Y - 2),按表直接得Cov(X, Y),不需要任何推导。
还有一点值得强调:所有这些规则都是恒等式,不依赖任何分布假设、不依赖独立性。独立性只在你想进一步把Cov(X, Y)划成 0 的时候才需要。这个层级关系一定要分清——先把结构拆开,再根据已知条件决定哪一项可以归零,顺序反了就容易在还没拆完的时候就急着化简。
3. Var(X±Y) 的两个公式:加号那个坑埋得最深
3.1 从 Cov(X+Y, X+Y) 展开得到方差公式
Var(X + Y)的公式不需要单独记,它就是Cov(X + Y, X + Y)的直接应用。把X + Y当成一个整体,套用上一节的线性拆分,对第一个参数拆一次、对第二个参数拆一次:
Var(X + Y) = Cov(X + Y, X + Y) = Cov(X, X) + Cov(X, Y) + Cov(Y, X) + Cov(Y, Y) = Var(X) + Var(Y) + 2Cov(X, Y)四项分别对应"X 和 X""X 和 Y""Y 和 X""Y 和 Y"。中间两项因为对称性相等,合并成2Cov(X, Y)。这个"四个格子"的视角很好用,因为它能自然推广到多变量情形——三个变量就是九宫格,n 个变量就是 n×n 的表格,对角线是方差,非对角线成对出现。
同样地,把加号换成减号:
Var(X - Y) = Cov(X - Y, X - Y) = Var(X) + Var(Y) - 2Cov(X, Y)这里唯一的变化是中间两项变成负的:Cov(X, -Y) = -Cov(X, Y),Cov(-Y, X) = -Cov(X, Y),两个负号一加,就是-2Cov(X, Y)。而两端Cov(X, X)和Cov(-Y, -Y) = Var(Y)都还是正的。所以Var(X - Y)里Var(Y)前面的符号是正号,这一点是整节的核心。
我用一句话总结这个结构:方差项永远是加的,协方差项跟着符号走。两个变量相减,方差部分还是Var(X) + Var(Y),只有协方差项变成减。
3.2 把 Var(X-Y) 写成 Var(X)-Var(Y) 是最高频的错误
回到开头那个朋友的例子。他写var(x) - var(y),本质上是把Var(X - Y)记成了"方差的差"。这个错误的根源在于把方差当成了一种"可以随便做加减的运算",而它其实是通过协方差定义的二次型,根本没有Var(X - Y) = Var(X) - Var(Y)这种性质。
最直观的反驳是取两个独立同分布的变量,都服从N(0, 1)。Var(X) - Var(Y) = 1 - 1 = 0,但X - Y服从N(0, 2),方差是 2,显然不是 0。这个反例不需要任何计算技巧,一眼就能戳穿错误公式。
正确的直觉应该是:两个独立的随机变量相减,波动只会变大不会变小。因为 X 往上偏的时候 Y 可能往下偏,差值被拉得更开。只有当两者高度正相关(Cov(X, Y)接近σ_X σ_Y)时,Var(X - Y)才会变小——这正是"对冲"的数学本质:找一个和你的持仓高度正相关的反向头寸,把波动抵消掉。
举一个具体数字,方便记住量级:设σ_X = 3、σ_Y = 4、ρ = 0.5。那么Cov = 0.5 × 3 × 4 = 6。
Var(X + Y) = 9 + 16 + 12 = 37,标准差约 6.08。Var(X - Y) = 9 + 16 - 12 = 13,标准差约 3.61。
可以看到,Var(X - Y) = 13远大于|9 - 16| = 7,也远大于 0。这个算例我常拿来做口算练习,做三五遍之后符号就不会再错。
3.3 独立条件下公式退化,但别把退化式当通式
当 X 与 Y 独立时,Cov(X, Y) = 0,于是Var(X ± Y) = Var(X) + Var(Y),加号和减号的结果一样。这个退化式很常用,但它成立的前提是独立性(或者至少不相关),一旦这个前提不成立,套用就会出错。
最典型的误用场景是"看起来独立但其实不独立"。比如同一个人在上午和下午各测一次血压,两次测量共享同一个人的基线水平,误差项相关,此时两次读数之差的方差不能简单按Var₁ + Var₂算,因为协方差项不为零——而且通常是正的,导致实际差值方差比公式给出的更大。我在处理重复测量数据时踩过这个坑,一开始按独立算,算出来的差值方差明显偏小,后来把个体随机效应显式建模进去,补上协方差项才对上。
反过来也有"看起来相关其实不相关"的情形。两个变量在业务上有关联,但线性相关系数接近零,此时把协方差项划掉在数值上是合理的近似,但要有意识这是近似而不是恒等。做推导时我会在草稿上标一句"此处假设不相关",避免后面忘了这个前提,把结论推广到不该推广的地方。
还有一个技术细节:独立能推出不相关,但不相关推不出独立(1.3 节的反例)。所以"协方差为零"这个条件比"独立"弱得多。如果你的推导只用到协方差为零,那结论的适用范围就更广;如果用到了独立性(比如涉及乘积的期望分解),那结论就只在独立假设下成立。这个区别在写论文或者做严谨推导时很重要,日常算数倒不必过分纠结。
4. 多变量求和与条件拆分:把求和号丢进协方差里
4.1 Cov(ΣaᵢXᵢ, ΣbⱼYⱼ) 的双重求和展开
单个加减拆完了,就该处理带求和号的表达式。最一般的形式是:
Cov(Σᵢ aᵢXᵢ, Σⱼ bⱼYⱼ) = Σᵢ Σⱼ aᵢbⱼ · Cov(Xᵢ, Yⱼ)这个式子看起来吓人,其实就是"双重循环":外层遍历第一个参数的每一项,内层遍历第二个参数的每一项,把每对的协方差乘上各自的系数,全部加起来。系数aᵢ和bⱼ可以提到协方差外面,因为它们对协方差是线性的。
一个具体例子能说明它的威力。计算Cov(X₁ + 2X₂, 3Y₁ - Y₂):
= 1·3·Cov(X₁, Y₁) + 1·(-1)·Cov(X₁, Y₂) + 2·3·Cov(X₂, Y₁) + 2·(-1)·Cov(X₂, Y₂) = 3Cov(X₁, Y₁) - Cov(X₁, Y₂) + 6Cov(X₂, Y₁) - 2Cov(X₂, Y₂)四项,系数是 3、-1、6、-2。手算的时候我建议画一个小表格,行是第一个参数的各项,列是第二个参数的各项,格子里填系数乘积 × 协方差,最后全部相加。这样不会漏项,也不会把系数乘错位。
最容易漏的是i ≠ j的交叉项。当你的表达式里变量有四个以上时,很多人会只算"同名配对"(Cov(X₁,Y₁)、Cov(X₂,Y₂))而忘掉交叉项,理由往往是"它们看起来没关系"。但这属于用直觉代替条件——除非明确知道交叉项的协方差为零,否则不能省略。
4.2 独立同分布情形下的简化与"分散化下限"
把上面的公式用到同方差、两两相关系数为ρ的一串变量上,能得到一个非常实用的结果。设X₁, ..., Xₙ每个方差都是σ²,任意两个的相关系数都是ρ(这个结构在实务里叫等相关结构),那么:
Var(ΣXᵢ) = nσ² + 2 · C(n, 2) · ρσ²其中C(n, 2) = n(n-1)/2是配对数量。整理一下:
Var(ΣXᵢ) = nσ² + n(n-1)ρσ² = nσ²[1 + (n-1)ρ]这个式子值得多盯一会儿。当ρ = 0时,它退化成nσ²,也就是独立情形下"方差相加"的标准结论,标准差是σ√n,随 n 增长是次线性的——这就是分散化效果的数学来源。当ρ > 0时,括号里多了一项(n-1)ρ,方差随 n 增长得更快;极端情况下ρ = 1(完全相关),1 + (n-1) = n,得到n²σ²,标准差是nσ,跟单个变量的标准差乘 n 一样,分散化完全失效。
这个结果解释了一个在实务中反复出现的现象:很多人以为"多买几只标的就是分散风险",但只要这些标的之间的相关系数为正,风险下降就有下限,加得越多边际收益越小。ρ越大,这个下限越高,组合规模再大也压不下去。我见过一些组合报告,把持仓数量当成分散度的指标,这在相关性普遍为正的市场里是明显站不住的。
顺带提一个对称的结论:如果要求的是均值X̄ = (1/n)ΣXᵢ的方差,把系数1/n提出来:
Var(X̄) = σ²[1 + (n-1)ρ] / n当ρ = 0时,这是熟悉的σ²/n;当ρ > 0时,n → ∞的极限是ρσ²,也就是无论样本量多大,均值的方差都降不到零以下。这条结论在抽样调查里叫设计效应(design effect),在金融里叫系统性风险不可分散,说的是同一件事。
4.3 组合方差 wᵀΣw 的手算规则与矩阵写法
把上一节推广到不等权重的情形,就得到组合方差的标准形式。设权重向量w,协方差矩阵Σ(对角线是各自的方差,非对角线是协方差),组合方差是wᵀΣw。展开成标量形式:
Var(Σᵢ wᵢXᵢ) = Σᵢ wᵢ²Var(Xᵢ) + 2Σᵢ<ⱼ wᵢwⱼCov(Xᵢ, Xⱼ)手算时记住两条:对角线项的系数是wᵢ²不带 2,非对角线项统一乘 2。这个"2"来自对称性——Cov(Xᵢ, Xⱼ)和Cov(Xⱼ, Xᵢ)是两个格子但数值相同,合并成一个再乘 2。矩阵写法则自动处理了这件事:wᵀΣw里每个元素都只乘一次,但由于Σ是对称矩阵,算出来的结果和标量形式一致。
举个例子,两只资产权重各 0.6 和 0.4,方差分别是 0.04 和 0.09(标准差 20% 和 30%),协方差 0.018:
wᵀΣw = 0.6² × 0.04 + 2 × 0.6 × 0.4 × 0.018 + 0.4² × 0.09 = 0.0144 + 0.00864 + 0.0144 = 0.03744标准差约 19.35%,比两只资产各自的标准差都低。原因在于协方差 0.018 相对两个标准差的乘积 0.06 而言,相关系数只有 0.3,相关性不高,分散化起了作用。如果把相关系数调到 0.9,协方差变成 0.054,重算一遍:0.0144 + 0.02592 + 0.0144 = 0.05472,标准差约 23.4%,明显上升,但仍低于 30%。这个对比能直观说明相关性对组合波动的影响。
矩阵形式的好处是 scalable,几十个标的用代码一行就出来了,标量形式则会变成几百项的噩梦。但理解的时候还是要靠标量形式——矩阵只是把双重求和换了个写法,本质没变。我在给别人讲的时候都会强调:先能手算三四项的版本,再看矩阵,否则很容易把wᵀΣw当成一个黑箱,调参时说不出为什么。
4.4 按条件拆分:全协方差公式的另一条路
前面讲的都是对变量做代数拆分,还有一种拆分是按条件切分,对应的是全协方差公式:
Cov(X, Y) = E[Cov(X, Y | Z)] + Cov(E[X | Z], E[Y | Z])这个式子的结构和全方差公式(Var(X) = E[Var(X|Z)] + Var(E[X|Z]))完全平行,可以理解为"协方差 = 组内协方差的平均 + 组间均值的协方差"。第一项衡量的是在同一组内部,X 和 Y 还剩多少共变;第二项衡量的是不同组之间,X 和 Y 的均值是不是同向变化。
这个拆分在分析分层数据时特别有用。举个具体的:假设你在分析某个指标在两个渠道下的关系,直接算整体协方差,得到的是一个混合值,看不出结构。按渠道分组后拆开,可能发现组内协方差接近零(同渠道内部两个指标基本无关),但组间均值协方差很大(渠道 A 的两个指标都高,渠道 B 的都低)。这种情况下,整体的相关性完全是由"渠道差异"这个混杂因素驱动的,并不是指标之间真的存在个体层面的关联。
这种"辛普森悖论式"的陷阱在实证分析里很常见,全协方差公式给了一个干净的诊断工具:把整体协方差拆成两部分,看哪一部分占主导。如果组间项占了大头,那就要警惕是不是有遗漏的分组变量在作祟。我处理分组数据时基本都会做这个拆解,成本很低,但能避免很多误读。
另外提一句,这个公式对 Z 是离散还是连续都成立,Z 是向量也成立,适用范围比想象中宽。前提同样是相关的期望存在。
5. 用代码把每条公式跑一遍
5.1 构造一组有已知相关性的样本
公式推完之后我一般会用代码验一遍,因为手推容易在符号或者系数上出错,而数值验证能立刻暴露问题。构造数据的方式是:先生成两个独立的标准正态,再用线性组合造出指定相关系数的两个变量。
import numpy as np rng = np.random.default_rng(20240607) n = 500_000 rho = 0.6 z1 = rng.standard_normal(n) z2 = rng.standard_normal(n) x = z1 y = rho * z1 + np.sqrt(1 - rho**2) * z2这里y由z1和z2组合而成,z1的权重是rho,剩下的权重是sqrt(1 - rho²),这样Var(y) = rho² + (1 - rho²) = 1,Cov(x, y) = rho,相关系数正好是rho。这个构造方式很通用,调rho就能扫不同的相关强度。
数据造好之后先验证一下样本统计量是不是接近理论值:
cov_mat = np.cov(x, y, ddof=1) print(cov_mat) print("样本相关系数:", cov_mat[0, 1] / np.sqrt(cov_mat[0, 0] * cov_mat[1, 1]))50 万样本下,样本相关系数一般会落在 0.6 附近千分之几的范围里。如果偏差远大于这个量级,说明构造公式写错了,先在这里排查,不要往下走。
5.2 逐个公式比对与差值排查
接下来把每一条拆分规则都验一遍。我习惯写成一串断言,跑通就说明公式和代码理解一致:
vx = np.var(x, ddof=1) vy = np.var(y, ddof=1) cxy = np.cov(x, y, ddof=1)[0, 1] # 基本展开 lhs = np.var(x + y, ddof=1) rhs = vx + vy + 2 * cxy print("Var(X+Y) 差值:", lhs - rhs) # 减法版本 lhs = np.var(x - y, ddof=1) rhs = vx + vy - 2 * cxy print("Var(X-Y) 差值:", lhs - rhs) # 双线性:Cov(X+Y, X-Y) 应等于 Var(X) - Var(Y) lhs = np.cov(x + y, x - y, ddof=1)[0, 1] rhs = vx - vy print("Cov(X+Y, X-Y) 差值:", lhs - rhs)最后一个式子值得一提:Cov(X + Y, X - Y) = Var(X) - Var(Y)。推导过程是展开四项,Cov(X,X) - Cov(X,Y) + Cov(Y,X) - Cov(Y,Y),中间两项抵消,剩下Var(X) - Var(Y)。在我们构造的数据里vx = vy,所以这个协方差应该接近 0。这是个很好的自检项——如果代码里算出来明显不为零,那多半是在某个地方把符号搞反了。
如果某条断言的差值超预期,排查顺序我一般是:先看ddof是不是两边一致,再看是不是把样本协方差和总体协方差混用了,最后才怀疑公式本身。绝大多数差异都出在前两项。
5.3 浮点误差与容差设置的经验值
数值验证不能要求精确相等,浮点运算有舍入误差。我一般用相对误差做判据,容差设在1e-10量级,因为这里的运算只有加减乘除,没有累加几百万项那种病态情况。
def check(name, lhs, rhs, tol=1e-10): scale = max(abs(lhs), abs(rhs), 1.0) err = abs(lhs - rhs) / scale status = "OK" if err < tol else "FAIL" print(f"{name}: 相对误差 {err:.3e} [{status}]")用相对误差而不是绝对误差,是因为变量的量级可能差很多。如果 X 的量级是 1e6,绝对误差 1e-6 其实已经相当精确了,用绝对误差判据会误报。反过来如果量级是 1e-6,绝对误差 1e-10 就显得很粗糙。相对误差把量级因素消掉了,判断更稳。
再补充一个容易忽略的点:np.var和np.cov在大样本下的数值稳定性。它们内部用的是两遍算法(先算均值再算偏差平方和),稳定性比一遍算法好很多。如果你自己手写E[X²] - (E[X])²这种一遍算法,当均值远大于标准差时会出现严重的相消误差——比如数据都在 1e8 附近、波动只有 1,那E[X²]和E[X]²都是 1e16 量级,相减之后有效位数几乎掉光。这个坑我在处理带大偏移的传感器数据时踩过,换回np.var就好了。所以做数值验证时,能调库函数就别自己写展开式。
6. 三个场景里的拆分套路与手算顺序
6.1 测量误差模型:真值加误差的方差怎么算
设观测值Z = X + ε,X 是真值,ε 是测量误差。如果两者独立,那Var(Z) = Var(X) + Var(ε),观测方差等于真值方差加误差方差,这就是经典的可靠性分解。反解出真值方差Var(X) = Var(Z) - Var(ε),这个式子在某些场景下会被直接拿去估计"真实波动",但它有个隐含前提:误差和真值不相关。
如果误差和真值相关(比如仪器在高值区间系统性地高估),那Var(Z) = Var(X) + Var(ε) + 2Cov(X, ε),多出来的协方差项会让分解失真。更麻烦的是,这个协方差项无法从单次观测中估计出来——你只看到 Z,看不到 X 和 ε 各自的值。这时候需要重复测量或者引入外部校准信息才可能识别。
我在做传感器数据清洗时遇到过类似问题。某个指标在高温段的读数波动明显变大,一开始以为是真值波动增加,后来把误差项单独建模才发现,是误差方差不恒定(异方差),而真值方差其实变化不大。如果只按Var(Z)的变化下结论,就会把仪器问题误判成业务问题。
顺便说一句,重复测量的场景下,两次观测Z₁ = X + ε₁、Z₂ = X + ε₂的协方差是Cov(Z₁, Z₂) = Var(X) + Cov(ε₁, ε₂)。如果两次测量误差独立,这一项就是Var(X),可以直接用来估计真值方差——这就是重测信度的数学基础。但如果两次测量在同一台仪器上连续做,误差之间往往正相关,这时候估出来的"真值方差"会偏大,因为把仪器漂移也算进去了。
6.2 组合收益:两只资产合成后的波动
设两个资产的收益率R₁、R₂,方差分别是σ₁²、σ₂²,协方差σ₁₂。组合收益是R = w₁R₁ + w₂R₂,w₁ + w₂ = 1。组合方差:
Var(R) = w₁²σ₁² + w₂²σ₂² + 2w₁w₂σ₁₂这个式子是 4.3 节的直接应用,没有新增内容,但它在实务里被用得最多,也最容易被简化过度。最常见的简化是把σ₁₂设成零,理由是"不想估计协方差,反正估不准"。这么做的后果是系统性地低估组合方差(因为多数资产之间协方差为正),在风险预算里会给出过于乐观的结论。
另一个常见操作是把权重设成 50/50,然后说"等权是最稳的"。等权确实不需要估计期望收益,但它仍然需要协方差才能算组合方差,而且等权并不等于最优。如果两个资产的波动差很多,等权会让高波动资产主导组合风险——比如σ₁ = 10%、σ₂ = 40%,等权组合的方差里σ₂²那一项贡献了 16 倍于σ₁²的量,风险几乎全来自第二个资产。
手算的时候我建议按"三项"的顺序走:先算两个平方项,再算交叉项,最后加总。这样当数字对不上时,能快速定位是哪一项出了问题。交叉项的系数容易漏掉那个 2,这是最高频的错误——w₁w₂σ₁₂和2w₁w₂σ₁₂差一倍,在组合方差里可能是几十个基点的差异。
6.3 分组汇总:组内、组间与交叉项
最后一类场景是分组数据的方差汇总。假设把总体按某个分类变量分成 k 组,总方差可以拆成三块:组内方差的加权平均、组间方差(各组均值相对总均值的波动),以及一个交叉项。
用全协方差公式的视角看更清楚。如果目标是Var(X),把 Z 取成分组变量,套用全方差公式:
Var(X) = E[Var(X | Z)] + Var(E[X | Z])第一项是组内方差的加权平均,第二项是组间均值方差。这个拆分里没有交叉项,因为Var(X)是同一变量的协方差,结构更简单。但如果目标是Cov(X, Y),按 4.4 节的公式拆,就只有两项:E[Cov(X,Y|Z)]和Cov(E[X|Z], E[Y|Z]),同样没有交叉项。
我之前提到的"三块"是在同时处理求和与分组时出现的。比如计算Var(ΣXᵢ)且Xᵢ来自不同组,展开后会有组内的方差和协方差、组间的均值差异,结构更复杂。这种情况下我一般不去硬背公式,而是回到最基础的展开式,按 4.1 节的双重求和逐项列出来,再按组归类。这样虽然慢一点,但不会漏项。
一个实用的小技巧:把分组后的数据整理成"组内均值"和"组内偏差"两列,然后分别算方差,再按公式组合。这样做的好处是每一步都有明确的解释——组内方差反映的是组内个体差异,组间方差反映的是分组本身带来的差异,两个数字都能直接和业务含义对上。比起直接扔给一个黑箱函数算总方差,这种拆法更容易发现异常,也更容易向别人解释。
最后分享一个我在推导时保持清醒的习惯:每次写出一个"协方差项被划掉"的步骤,就在旁边标注依据——是"独立"、"不相关"还是"常数"。这个小动作花不了几秒钟,但能在复盘时快速定位错误的来源。我踩过的符号错误,十次里有八次都能追溯到某个没有标注依据的化简步骤。