经验逆 CDF 边际映射(Copula 流水线第二步)
Empirical Inverse-CDF Marginal Mapping (Copula Pipeline, Step Two)
开始编码实现 solution(u_samples: list[list[float]], empirical_data: list[list[float]]) -> list[list[float]]。某风险口跑蒙特卡洛 copula 模拟:先用 Cholesky 分解抽相关标准正态(姊妹题 coding-gaussian-copula-correlated-sample-via-cholesky),再把每个分量过标准正态 CDF Phi 得到 [0, 1] 上的均匀变量并保留 copula 相关结构,最后把每个均匀变量映射回历史风格样本——做法是反演该边际的经验 CDF。本函数完成第三步:给定形状 (S, D) 的 uniforms u_samples 和形状 (M, D) 的历史观测面板 empirical_data(每个维度对应一条经验边际),按标准线性插值约定(numpy 的 method='linear' 默认值)返回形状 (S, D) 的逆 CDF 输出。
算法是逐维度的经验分位数查表。(1)对每个维度 d,从 empirical_data 抽取列 d 并升序排序,记为 sorted_d。(2)对每个样本 s 和维度 d,令 u = u_samples[s][d],计算分数位置 p = u * (M - 1)、lower_idx = floor(p)、frac = p - lower_idx。若 lower_idx == M - 1,返回 sorted_d[M - 1](即 u = 1 边界钳位);否则返回 sorted_d[lower_idx] + frac * (sorted_d[lower_idx + 1] - sorted_d[lower_idx])。(3)返回形状 (S, D) 的二维列表。
**分母是 M - 1,不是 M。** M = 11 个历史观测时,u = 0 应落在 sorted_d[0]、u = 1 应落在 sorted_d[10]——都在范围内。M 分母把 u = 1 放到下标 11(越界),并且把每一个分位数都偏移一个桶。这是规范遵循的 linear / 默认百分位约定。
例
solution([[0.5]], [[10.0], [20.0], [30.0], [40.0]]) 返回 [[25.0]]。单列升序为 [10, 20, 30, 40](这例子本身就有序);M = 4、u = 0.5 时位置 p = 0.5 * (4 - 1) = 1.5,所以 lower_idx = 1、frac = 0.5,输出 sorted[1] + 0.5 * (sorted[2] - sorted[1]) = 20 + 0.5 * (30 - 20) = 25.0。中点 bug (sorted[1] + sorted[2]) / 2 = 25.0 在此恰好与正解一致(因为 frac = 0.5);改成 u = 0.1(给出 p = 0.3、frac = 0.3)时正解为 10 + 0.3 * (20 - 10) = 13.0,而中点 bug 给 15.0——差一个桶。
逐列对照算例:solution([[0.5, 0.5]], [[10.0, 5.0], [20.0, 50.0], [30.0, 500.0]]) 返回 [[20.0, 50.0]]。列 0 排序 [10, 20, 30]、列 1 排序 [5, 50, 500]。u = (0.5, 0.5)、M = 3,位置 p = 0.5 * 2 = 1.0 整数,所以 lower_idx = 1、frac = 0.0,每条边际取自己的 sorted_d[1]:维度 0 给 20,维度 1 给 50。把矩阵拍平或按行排序的实现会得到不一样(错的)的答案。
实践背景
某信用风险或市场风险口经常跑联合分布按历史经验标定的蒙特卡洛模拟:每个边际匹配历史 PnL 或因子收益的经验分布,联合相关结构由相同历史估计出的相关矩阵承担。教科书做法是经验边际的高斯 Copula:(1)通过相关矩阵的 Cholesky 分解抽相关标准正态 Z = L * z_independent;(2)逐分量应用标准正态 CDF Phi,把每个相关正态映射成 [0, 1] 上的均匀变量——这一步保留 copula 结构、把边际转换成 Uniform(0, 1);(3)逐维度反演该边际的经验 CDF,把每个均匀变量映射回边际的原始单位下的历史风格样本。第 3 步——本函数——把标定后的边际重新缝回 copula 结构。这套流水线相对全参数 Monte Carlo 的优势就在第 3 步:不必给每条边际拟合参数化分布,无论尾巴多厚、多偏,任何历史样本都能直接接入 copula。第 1 步(Cholesky 相关化)是姊妹题 coding-gaussian-copula-correlated-sample-via-cholesky,两道题串起来产出风险/压力测试管线消费的"高斯 Copula + 经验边际"完整输出。
需要明确的细节:S = 0(空 u_samples)返回 []——函数不抛异常。M = 1 时 p = u * 0 = 0 对任意 u 都成立,所以每个输出条目都等于该列唯一的经验观测,与 u 无关。u = 0.0 返回 sorted_d[0](列最小值);u = 1.0 走显式边界分支返回 sorted_d[M - 1](列最大值)。重复观测排序后天然处理——相邻相等值之间间隔为 0,frac 不再起作用,重复值直接被返回。期望算法 O(D * M log M + S * D):每个维度一次排序 + 每个 (样本, 维度) 一次查表。在约束范围(M <= 60、S <= 50、D <= 6)下总操作量最多几千次。
实现细节由 stubs/stub.py 提供。
约束条件
- 1 <= M == len(empirical_data) <= 60(每维历史观测数)
- 0 <= S == len(u_samples) <= 50(待映射样本数)
- 1 <= D == len(empirical_data[0]) <= 6(边际维度数)
- 每个 u_samples[s] 是长度 D 的浮点列表,取值在 [0.0, 1.0];每个 empirical_data[m] 是长度 D 的有限浮点列表,|x| <= 1e6
- 输出形状为 (S, D) 的 list[list[float]];每项按 rel_tol=1e-9、abs_tol=1e-9 比对;行序与 u_samples 一致
样例
Case 1 · statement-example: M=4 sorted=[10,20,30,40] u=0.5 yields 25.0
输入: [[[0.5]],[[10],[20],[30],[40]]]
期望: [[25]]
M=4,列排序后为 [10,20,30,40];u=0.5 → p=0.5*(4-1)=1.5;落在 sorted[1]=20 与 sorted[2]=30 之间,frac=0.5,结果 = 20 + 0.5*(30-20) = 25。
Case 2 · visible: u=0.0 returns sorted[0] (the minimum)
输入: [[[0]],[[10],[20],[30],[40]]]
期望: [[10]]
u=0 时 p=0,lower_idx=0,frac=0;输出 sorted[0]=10.0(即最小观测)。
Case 3 · visible: u=1.0 returns sorted[M-1] (the maximum)
输入: [[[1]],[[10],[20],[30],[40]]]
期望: [[40]]
u=1 时 p=M-1=3,lower_idx=M-1=3,触发边界分支直接返回 sorted[M-1]=40.0;不要去访问 sorted[M] 否则越界。
Case 4 · visible: S=0 empty u_samples returns []
输入: [[],[[1],[2]]]
期望: []
S=0 时返回 [] —— 不要崩。
Case 5 · visible: M=1 single empirical observation, every output equals it
输入: [[[0.3],[0.7]],[[5]]]
期望: [[5],[5]]
M=1 时 p = u*(M-1) = u*0 = 0 恒等于 0,每个输出都等于唯一的历史观测 5.0。
最近提交
还没有提交记录。
编码区
实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。
默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。
Case 1 · statement-example: M=4 sorted=[10,20,30,40] u=0.5 yields 25.0
输入: [[[0.5]],[[10],[20],[30],[40]]]
期望: [[25]]
M=4,列排序后为 [10,20,30,40];u=0.5 → p=0.5*(4-1)=1.5;落在 sorted[1]=20 与 sorted[2]=30 之间,frac=0.5,结果 = 20 + 0.5*(30-20) = 25。
Case 2 · visible: u=0.0 returns sorted[0] (the minimum)
输入: [[[0]],[[10],[20],[30],[40]]]
期望: [[10]]
u=0 时 p=0,lower_idx=0,frac=0;输出 sorted[0]=10.0(即最小观测)。
Case 3 · visible: u=1.0 returns sorted[M-1] (the maximum)
输入: [[[1]],[[10],[20],[30],[40]]]
期望: [[40]]
u=1 时 p=M-1=3,lower_idx=M-1=3,触发边界分支直接返回 sorted[M-1]=40.0;不要去访问 sorted[M] 否则越界。
Case 4 · visible: S=0 empty u_samples returns []
输入: [[],[[1],[2]]]
期望: []
S=0 时返回 [] —— 不要崩。
Case 5 · visible: M=1 single empirical observation, every output equals it
输入: [[[0.3],[0.7]],[[5]]]
期望: [[5],[5]]
M=1 时 p = u*(M-1) = u*0 = 0 恒等于 0,每个输出都等于唯一的历史观测 5.0。