在线流式计算均值、方差、偏度与超额峰度(Welford-Pebay)
Online Streaming Mean, Variance, Skewness, and Excess Kurtosis (Welford-Pebay)
开始编码某流式风险监控持续接收 P&L(或收益) tick,每来一个就要把*累计*的样本均值、样本方差、样本偏度、样本超额峰度算出来——而且不能存历史。偏度与超额峰度是衡量分布对称性与厚尾的标准工具:盘中盯实时 P&L 的桌台不仅关心已实现波动率是否在抬升,更关心分布是否开始非对称(偏度向负漂——亏损端变厚)或者开始重尾(超额峰度跳升——尾部事件变频繁)。一个 7×24 的监控显然不能存下每一笔 tick;标准做法是 Welford 在线算法的高阶推广,即 Pébay (2008) 更新——只维护五个标量 (n, M1, M2, M3, M4),每个样本 O(1) 完成更新。
实现 solution(values: list[float]) -> dict。逐个处理 values(不能缓存整个数组、不能做第二遍扫描),最后返回四个样本统计量:
其中 是 Pébay 更新维护的滚动中心矩累加量:
更新顺序很关键: 用的是旧的 与 ; 用的是旧的 ;最后才能改写 与 。返回的 kurtosis 是超额峰度(已减 3,正态样本期望 ≈ 0)。
边界情形:
n == 0:返回{"n": 0, "mean": 0.0, "variance": 0.0, "skewness": 0.0, "kurtosis": 0.0}。n == 1:方差、偏度、峰度均为0.0(样本量不足)。n == 2:方差有定义,但偏度、峰度为0.0。n == 3:方差、偏度有定义,但峰度为0.0(需n >= 4)。- 所有元素相等:
M2 = 0,方差、偏度、峰度全部为0.0;mean仍如实返回该常数。
例如,solution([1.0, 2.0, 4.0]) 返回 {"n": 3, "mean": 2.333..., "variance": 2.333..., "skewness": 1.7181079837227284, "kurtosis": 0.0}(均值 7/3、样本方差 7/3、n<4 所以峰度强制为 0)。solution([3.5, 3.5, 3.5, 3.5, 3.5]) 返回 {"n": 5, "mean": 3.5, "variance": 0.0, "skewness": 0.0, "kurtosis": 0.0}——所有元素相等的退化路径。solution([1.0, 1.0, 1.0, 1.0, 10.0])(极端正偏的五元素流)返回 {"n": 5, "mean": 2.8, "variance": 16.2, "skewness": 3.125, "kurtosis": 5.0}。
实践背景
一个真实的 P&L 实时监控不可能把多策略账簿连续多年的每一笔交易、每一根分钟收益都留在内存里;即便只盯一个策略的逐秒 tick 流,几天之内内存就会爆掉。Welford 1962 年的在线公式从此成为流式均值与方差的教科书答案,而 Pébay (2008) 又把它自然延伸到三阶、四阶中心矩——刚好就是尾部风险面板需要的两个量。同样的五标量状态(n、M1、M2、M3、M4)天然支持成对合并(Chan–Golub–LeVeque 合并公式),所以 Spark 的 org.apache.spark.util.StatCounter、Pandas 的 rolling().skew() / rolling().kurt() 内核背后都是同一套递推。能够在尾部事件真正发生之前就识别出已实现偏度向负漂、或者超额峰度抬升的 regime,正是这套算法的目的;亚微秒级的逐 tick 预算则是逼出流式形式的根本约束。n < 4 时各项坍缩为 0 也是工程意义上的:刚启动的策略只有几笔成交,*不应当*基于三个点报出离谱的偏度/峰度;按惯例返回 0.0 就是标准的"样本不足,请忽略"信号。
约束条件
- 0 ≤ N ≤ 10^5,其中 `N = len(values)`
- `values` 是有限浮点列表(无 NaN、无 inf)
- 样本方差使用 Bessel 校正(分母 `n - 1`,即 ddof=1)
- 偏度使用 `(M3 / n) / sqrt((M2 / n)^3) * n^2 / ((n - 1)(n - 2))`
- 返回的峰度是**超额**峰度(已减 3,正态样本期望 ≈ 0)
- 实现必须**真流式**:逐个处理样本,只累加 `(n, M1, M2, M3, M4)` 五个标量——不要缓存整个数组
- 返回 `dict`,键为 `n`(int)、`mean`(float)、`variance`(float)、`skewness`(float)、`kurtosis`(float);浮点比较容差 `rel_tol = 1e-9`、`abs_tol = 1e-12`
样例
Case 1 · statement-example: n=3 small list
输入: [[1,2,4]]
期望: {"n":3,"mean":2.3333333333333335,"variance":2.3333333333333335,"skewness":1.7181079837227284,"kurtosis":0}
n=3:mean=7/3、样本方差=7/3;按规范的偏度公式 (M3/n)/sqrt((M2/n)^3) * n^2/((n-1)(n-2)) 计算偏度,n<4 时超额峰度强制为 0。
Case 2 · statement-example: all-equal degenerate
输入: [[3.5,3.5,3.5,3.5,3.5]]
期望: {"n":5,"mean":3.5,"variance":0,"skewness":0,"kurtosis":0}
所有元素相等:M2=0,样本方差=0,按惯例偏度与超额峰度均返回 0;mean 仍为该常数 3.5。
Case 3 · statement-example: heavily right-skewed n=5
输入: [[1,1,1,1,10]]
期望: {"n":5,"mean":2.8,"variance":16.2,"skewness":3.1250000000000004,"kurtosis":5}
一个极端正偏样本:四个 1 与一个 10;Pebay 累加给出 M2=64.8、M3=233.28、M4=1259.712;带入公式得到样本方差 16.2、偏度 25/8=3.125、超额峰度 5.0。
Case 4 · boundary: empty list n=0
输入: [[]]
期望: {"n":0,"mean":0,"variance":0,"skewness":0,"kurtosis":0}
Case 5 · boundary: single element n=1
输入: [[42]]
期望: {"n":1,"mean":42,"variance":0,"skewness":0,"kurtosis":0}
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: n=3 small list
输入: [[1,2,4]]
期望: {"n":3,"mean":2.3333333333333335,"variance":2.3333333333333335,"skewness":1.7181079837227284,"kurtosis":0}
n=3:mean=7/3、样本方差=7/3;按规范的偏度公式 (M3/n)/sqrt((M2/n)^3) * n^2/((n-1)(n-2)) 计算偏度,n<4 时超额峰度强制为 0。
Case 2 · statement-example: all-equal degenerate
输入: [[3.5,3.5,3.5,3.5,3.5]]
期望: {"n":5,"mean":3.5,"variance":0,"skewness":0,"kurtosis":0}
所有元素相等:M2=0,样本方差=0,按惯例偏度与超额峰度均返回 0;mean 仍为该常数 3.5。
Case 3 · statement-example: heavily right-skewed n=5
输入: [[1,1,1,1,10]]
期望: {"n":5,"mean":2.8,"variance":16.2,"skewness":3.1250000000000004,"kurtosis":5}
一个极端正偏样本:四个 1 与一个 10;Pebay 累加给出 M2=64.8、M3=233.28、M4=1259.712;带入公式得到样本方差 16.2、偏度 25/8=3.125、超额峰度 5.0。
Case 4 · boundary: empty list n=0
输入: [[]]
期望: {"n":0,"mean":0,"variance":0,"skewness":0,"kurtosis":0}
Case 5 · boundary: single element n=1
输入: [[42]]
期望: {"n":1,"mean":42,"variance":0,"skewness":0,"kurtosis":0}