← 返回模块
3.2.3.1beta 可读 · 未来免费内容校验中内容版本 2026-05-27

scipy.stats 分布对象与描述性统计

3.2.3 · SciPy 与统计工具 · 编程

周一上午十点,你坐在一家中型私募的研究台。3.2.2 收尾那张 tear-sheet 昨晚跑完了,落到磁盘的中间产物里有一行 returns = (closes['510300.SH'].pct_change().dropna()).to_numpy()——一根长度 252 的 np.ndarray,是沪深300 ETF(510300.SH)在 2024 年的日简单收益。PM 把咖啡放下,砸出四个问题:(1) 这只 ETF 的 5% 一边在险价值(value at risk, VaR)是多少?(2) 拿正态分布(Gaussian distribution)当近似还过得去吗,还是必须切到学生 t 分布?(3) 偏度(skewness)和超额峰度(excess kurtosis)落在什么区间?(4) 想给隔夜压力测试造一条同分布的合成路径,怎么造?四个问题在 scipy.stats 里都是一行。一行能成立,是因为 SciPy 把每一种分布都收进了同一套对象接口;这一课讲这套接口,以及它在「拟合 + 描述性统计」上的两块直接落地。

一、分布对象的统一接口

scipy.stats 里的每一个分布——normtlognormchi2f——既能「不冻结」直接调函数,也能「冻结」成对象反复用。冻结的写法把参数绑在对象上,避免每次调用都重传一遍 locscale

import scipy.stats as st
d = st.norm(loc=0.0, scale=1.0)
d.pdf(0.0)
d.cdf(1.96)
d.ppf(0.975)
d.sf(1.96)

四个方法是每一个连续分布都支持的:.pdf(x)​概率密度函数​​(probability density function, PDF)在 xx 处的取值(离散分布的对应物叫 .pmf,本课不展开);.cdf(x)​累积分布函数​​(cumulative distribution function, CDF),即 P(Xx)P(X \leq x).ppf(q)​百分位函数​​(percent point function, PPF),即 CDF 的反函数,返回使 P(Xx)=qP(X \leq x) = q 成立的那个 xx——这是后面 VaR 一行的承重件;.sf(x)​生存函数​​(survival function)1CDF(x)1 - \mathrm{CDF}(x),在尾部远离零的位置比 1 - d.cdf(x) 在数值上更稳。再加两个会用到的:.rvs(size, random_state=...) 抽样(务必显式传一个 NumPy Generator 对象,对齐 3.2.1 L3 的可复现规则),.fit(data) 对样本做 ​极大似然估计​​(maximum likelihood estimation, MLE),返回该分布参数的点估计元组。

二、五个量化最常用的分布

norm 换成其它分布,上述六个方法签名整体不变——这就是「统一接口」的现金价值。五个分布在量化场景里出场最频繁:

  • ​正态分布​​(Gaussian distribution,又称高斯分布)norm:渐近正态性论证与 t 检验零分布的默认参照;参数 loc=mean, scale=std
  • ​学生 t 分布​​(Student's t distribution)t:正态分布的厚尾表亲;参数 df​自由度​​(degrees of freedom,量化文献常写作 ν\nu),可选 locscaleν\nu \to \infty 时退化成正态;ν<30\nu < 30 尾部已明显加厚;ν<6\nu < 6 时拿正态当近似就不安全。
  • ​对数正态分布​​(log-normal distribution)lognorm:当 XN(μ,σ2)X \sim \mathcal{N}(\mu, \sigma^2)exp(X)\exp(X) 的分布,也就是 GBM 模拟出来的价格在任一固定时刻的边际分布。​**​SciPy 的参数化是 s=sigmascale=exp(mu)**​——这是一处出名的记号陷阱,第一次写一定要回去翻 docstring。
  • ​卡方分布​​(chi-squared distribution)chi2:方差比、似然比类检验统计量的零分布,参数 df
  • ​F 分布​​(F-distribution)f:两个独立方差估计比值的零分布,参数 dfn, dfd;L3 的 F 检验脚注会引用它。

五个分布的「目录学」到此打住——本课承重在统一接口,不在分布大全。

三、把分布拟合到日收益样本

回到那根 252 长的 returns。对正态与 t 各做一次 MLE:

mu_n, sigma_n = st.norm.fit(returns)
nu_t, mu_t, sigma_t = st.t.fit(returns)
assert nu_t > 0

st.norm.fit 返回 (loc, scale)st.t.fit 返回 (df, loc, scale)——返回元组的顺序逐分布不同,写一处就对照一次 docstring 的习惯比记忆可靠。在 510300.SH 这条 252 天的样本上,落地数值大致是 μn0.0001\mu_n \approx 0.0001σn0.011\sigma_n \approx 0.011νt4.5\nu_t \approx 4.5——这里只取一组示意值用于教学,并不主张匹配任一具体年份。承重诊断一句话:如果 t 拟合得到 nu_t < 6,那么正态假设在尾部就不安全,下游计算应当用 L2 的 bootstrap 置信区间或一个 t 尾模型再交叉验证一遍。nu_t ≈ 4.5 显然过线,提示 PM 别在尾部信任正态。

.fit() 在干什么?它在数值上求解 maxθilogpdf(xi;θ)\max_{\theta} \sum_i \log \mathrm{pdf}(x_i; \theta)——这就是 MLE,是 Subject 2.2.1(estimator-properties-bias-variance-and-information)整门课的对象。本课把 .fit() 当成黑盒工具:一次方法调用拿一组点估计;MLE 的相合性、渐近正态性、Fisher 信息矩阵、Cramér-Rao 下界这些理论证明都在 2.2.1 那一节,本课不动手。

四、参数化 VaR 一行落地

拿到 (μn,σn)(\mu_n, \sigma_n),5% 一边参数化 VaR 就是一行:

var_5 = -st.norm.ppf(0.05, loc=mu_n, scale=sigma_n)

负号是约定:VaR 习惯写成正数损失,所以把 5% 分位(一个负收益)取反。把 st.norm.ppf 换成 st.t(df=nu_t, loc=mu_t, scale=sigma_t).ppf(0.05) 再取反,就是 t 分布参数化 VaR;同等波动下 t 的左尾更胖,t-VaR 在数值上比正态 VaR 更大。VaR 还有两条做法——​​历史 VaR​​(直接 np.percentile(returns, 5),不做分布拟合)和 ​Monte-Carlo VaR​​(用 .rvs() 模拟一条长路径再取分位)——这里点名,不展开;本课只跑参数化这条。

五、描述性统计四件套

回收第三个问题。scipy.stats.describe 一次把四个中心矩拿齐:

desc = st.describe(returns)
nobs, (rmin, rmax), mean, variance, skewness, kurtosis = desc

返回的是一个 DescribeResult named tuple,按顺序解包即可。两条约定开关每次都要看清楚:在 fisher=True 下 SciPy 报的是超额峰度(kurtosis − 3,正态分布取 0);在 bias=False 下方差 / 偏度 / 峰度的估计量都做无偏修正(除以 N−1);两者都是 SciPy 的默认值,也是本课统一采用的口径。两个默认刚好都对,但生产代码里建议把这两个关键字显式写出来,避免下游同事踩坑。

经验区间口径:A 股日收益的偏度落在 [-0.3, +0.3] 大致可视为近似对称;超额峰度在 [3, 8] 是日级权益收益的典型区间(厚于正态但还不算极端);超额峰度过 10 通常意味着样本里混了少量极端跳跃——先一根一根查清楚再做后续建模。

把整批收益 z 化是同一行:zs = st.zscore(returns) 返回 (xxˉ)/s(x - \bar{x}) / s。应用到 (T, N) 矩阵时要写 axis=0, ddof=1axis 决定按时间还是按截面去中心化,ddof 决定与 describebias=False 是否口径一致。

六、肉眼校验与本课不做的事

可视化不是本课承重,但留两个指针:plt.hist(returns, bins=30, density=True) 叠一条 plt.plot(grid, st.norm.pdf(grid, loc=mu_n, scale=sigma_n)) 是日常的「眼测」;正式的图形化检查是 st.probplot(returns, dist='norm', plot=plt) 出的 QQ 图。两件事都在 matplotlib 那条线上,本课只点名,不动手。多元情形下,单变量的方差拟合换成 ​协方差矩阵​​(covariance matrix)拟合,那是 L4 把 df.cov() 喂给 scipy.linalg.eigh 的入口;本课只到一元。

显式拒绝:假设检验(ttest_1samp / kstest / shapiro / jarque_bera)、p 值、自助法置信区间是下一课 L2 的承重;回归(linregress / curve_fit)是 L3;约束优化、对称特征分解、数值积分是 L4。本课交付的是「拿到一根 returns,能在五分钟内给出一组点估计、一个 VaR 数字、一份描述性统计、外加一句尾部诊断」——拿这套点估计去跑 L2 的检验,是下一步要做的事。

练习

Exercise

给定一根长度为 252 的一维 NumPy 数组 returns,代表某资产的日简单收益:(1) 用 st.norm.fit(returns) 拟合一个正态分布、st.t.fit(returns) 拟合一个学生 t 分布;(2) 用 st.norm.ppf(0.05, loc=mu_n, scale=sigma_n) 从拟合正态算出 5% 一边参数化 VaR;(3) 用 st.describe(returns) 算出描述性统计元组并取出 excess_kurtosis;(4) 返回元组 (mu_n, sigma_n, nu_t, var_5, excess_kurtosis)。沿用 SciPy 默认的 fisher=Truebias=False

提示
提示 1:st.norm.fit 返回 (loc, scale)st.t.fit 返回 (df, loc, scale)。解包顺序别搞反。
提示
提示 2:st.describe(...) 返回 named tuple;.kurtosis 字段在 SciPy 默认 fisher=True 下就是 excess kurtosis。