feat(stats): correlation_matrix 按方案 A 拆成三个函数,收口返回契约(L0 9/12)
用户 2026-10-03 拍板方案 A(见决策 41),由 Codex 落地。
拆成三个函数:
- correlation_matrix -> 数值系数矩阵(对角线 1.0)
- correlation_pvalues -> 数值 p 值矩阵(对角线 NaN)
- correlation_table -> 星标字符串表(对角线 1.000 不带星)
三者共用 _check_corr_args 守卫与成对删缺失口径;correlation_table 在最外层调另外两个。
理由: 字符串表做不了后续计算(挑高相关对、喂聚类/PCA、画热力图),反过来随时能格式化;
一旦返回字符串,数值信息不可逆地丢失。
顺带修掉两处:
- 对角线 p 值由 0.0 改成 NaN —— 写 0 会被下游读成"极显著",而"自己和自己"没有 p 值
- p 值与 r 现在用同一批观测: 先成对 dropna() 再算,并同样尊重 min_periods。
此前 corr 是成对删缺失、pearsonr 不删,两者样本量不同,r 与 p 对不上
- p 值只算上三角再镜像(成对变量的 p 天然对称),省一半重复计算
tests: test_correlation_matrix_diagonal_is_one 摘掉 xfail 转正;新增 8 条
(数值性+对称性、非数值列跳过、p 值对照解析式 t = r*sqrt((n-2)/(1-r^2))、
对角线 NaN、成对删缺失口径、min_periods、对角线无星标、表格与数值输出一致)。
测试 105 passed / 38 skipped / 1 xfail -> 114 passed / 38 skipped / 0 xfail。
验证: pytest 114 passed / 38 skipped;ruff All checks passed;pyright 0 errors。
docs: STATUS.md(L0 9/12 + 决策 41 + 下一步);CHANGELOG;
personal-pitfalls 追加第 14 条(把"能看的表"和"能算的表"塞进同一个返回值);
roadmap 记录 correlation_matrix 的两个伴随函数
This commit is contained in:
1 parent
3432bf3e50
commit
95f5266563
7 files changed
+308
-71
No files matched your search
@@ -10,7 +10,7 @@
|
||||
|
||||
**第二阶段(进行中)**:按 `docs/03-roadmap.md` 的 L0 → L1 顺序**逐个实现函数**,
|
||||
用户本人写 `src/`、Codex 负责审查 + `tests/`(决策 25)。
|
||||
当前 8/12:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、`detect_outliers`、`normalize`/`standardize`、`describe`。
|
||||
当前 9/12:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、`detect_outliers`、`normalize`/`standardize`、`describe`、`correlation_matrix`。
|
||||
|
||||
## 任务清单
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
- [x] 推送通道:公网 22 被防火墙过滤 → 改用 Tailscale(`100.115.86.48:22`),
|
||||
`~/.ssh/config` 加了一条域名别名,remote 里仍写域名
|
||||
|
||||
**E. L0 实现进度(8/12)**
|
||||
**E. L0 实现进度(9/12)**
|
||||
- [x] `core/result.py` 的 `Result`
|
||||
- `__repr__`:单行紧凑;`data` 只给类型与规模,**绝不**整数组打印
|
||||
(十万级数组的 repr 实测仍 < 200 字符);`success` 恒在
|
||||
@@ -154,7 +154,7 @@
|
||||
决策 26 说的就是这个,实现写完了却零覆盖),改成逐条 skip 未实现的 5 条,
|
||||
并新写 20 条 describe / zscore / 变异系数用例
|
||||
- [x] `examples/01_clean_and_describe.py`:填掉末尾的 `TODO(L0)`,加一段 `describe` 输出
|
||||
- [ ] `stats/inference.py` 的 `correlation_matrix`(L0 第 9 项,**进行中**;**由用户本人实现**,Codex 审查 + 代修)
|
||||
- [x] `stats/inference.py` 的 `correlation_matrix`(L0 第 9 项,**由用户本人实现**,Codex 审查 + 代修)
|
||||
- **硬 bug**:`if numeric_cols == 0:` 拿 `Index` 和标量比,得到的是布尔**数组** ——
|
||||
0 列时静默不报错、1 列时碰巧能跑、**≥2 列直接 `ValueError` 崩**;
|
||||
而"≥2 个数值列"正是最常见的情况,等于整个函数不可用。改成 `len(numeric_cols) == 0`(易错点 13)
|
||||
@@ -163,18 +163,24 @@
|
||||
`_make_tuple_bunch` 运行时拼的类、存根里看不到 `pvalue`(见 `docs/04-environment.md` 坑 10)。
|
||||
加了 `_pvalue(result: Any)` 小助手统一取 p 值,**一处都没挂 `# type: ignore`**
|
||||
- `method` 校验提到最前、清掉 3 条 F541 / 1 条 RET505 / I001,ruff 归零
|
||||
- **L0 契约待拍板**:返回值现在是**带星标的字符串表**(`1.000***`),
|
||||
而 docstring 契约与 L0 用例都要求**数值矩阵**(对角 = 1.0);
|
||||
该用例已按决策 30 从 `skip` 改成 `xfail(strict=False)`,见「下一步」第 1 条
|
||||
- 另两处待确认:**对角线也被标了 `***`**(自己和自己相关不该有显著性星标);
|
||||
`df_num` 含 NaN 时 `corr` 走成对删失、而 `pearsonr` 不删 → r 与 p 对不上
|
||||
- **现在返回**:`correlation_matrix` → 数值系数矩阵(对角线 1.0);
|
||||
`correlation_pvalues` → 数值 p 值矩阵(**对角线 NaN**);
|
||||
`correlation_table` → 星标字符串表(**对角线 `1.000` 不带星**)。
|
||||
- **返回契约冲突(已解决)**:上一版返回的是**带星标的字符串表**(`1.000***`),
|
||||
而 docstring 契约与 L0 用例都要求**数值矩阵**(对角 = 1.0);按方案 A 拆成三个函数(见下)
|
||||
- [x] `correlation_matrix` 的**返回契约**按**方案 A** 落地(2026-10-03,用户拍板):
|
||||
`correlation_matrix` 只返回**数值**矩阵;星标表拆成 `correlation_table`、p 值拆成
|
||||
`correlation_pvalues`(三者共用 `_check_corr_args` 守卫与**成对删缺失**口径,见决策 41)。
|
||||
顺带修掉两处:**对角线不再带 `***`**(p 值矩阵的对角线是 NaN、"自己和自己"没有显著性);
|
||||
**p 值与 r 用同一批观测**(都成对删缺失、都尊重 `min_periods`),不再各算各的样本量。
|
||||
p 值只算上三角再镜像,省一半重复计算。测试 **+8 条**(含用解析式 `t = r·sqrt((n-2)/(1-r²))` 对照)
|
||||
- [ ] 剩余 3 项:`bootstrap_ci` `monte_carlo` `save_figure`
|
||||
|
||||
### 验证结果
|
||||
|
||||
| 检查 | venv | conda `mcm-toolkit` |
|
||||
|---|---|---|
|
||||
| `pytest` | 见下(单列) | **105 passed, 38 skipped(1 xfail)** |
|
||||
| `pytest` | 见下(单列) | **114 passed, 38 skipped(0 xfail)** |
|
||||
| `import mcm_toolkit` | 0.0.1 / 16 项 | 0.0.1 / 16 项 |
|
||||
| `ruff check` | All checks passed | **All checks passed**(0 error) |
|
||||
| `scripts/doctor.ps1` | 跑通 | — |
|
||||
@@ -184,10 +190,10 @@
|
||||
(修配置之前是 17 errors,全是"无法解析导入 numpy / pandas",见决策 29 与
|
||||
`docs/04-environment.md` 坑 7)
|
||||
- base 亦已复核:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6(与两套环境一致)
|
||||
- **144 条用例:105 passed / 38 skipped / 1 xfail**。收集数按文件:`test_data.py` 52、`test_api_surface.py` 20、
|
||||
`test_core.py` 15、`test_solve.py` 9、`test_viz.py` 8、`test_decision.py` 7、`test_stats.py` 28、
|
||||
- **152 条用例:114 passed / 38 skipped / 0 xfail**。收集数按文件:`test_data.py` 52、`test_api_surface.py` 20、
|
||||
`test_core.py` 15、`test_solve.py` 9、`test_viz.py` 8、`test_decision.py` 7、`test_stats.py` 36、
|
||||
`test_models.py` 5
|
||||
- 目前 **1 条 xfail**:`test_correlation_matrix_diagonal_is_one` —— 契约(数值矩阵)与实现(带星标字符串表)冲突,口径待拍板
|
||||
- **`xfail` 再次清零**:`correlation_matrix` 的契约冲突已按方案 A 解决(决策 41),那条用例转正
|
||||
- 上一轮那 7 条 xfail 已全部转正,决策 30 那套信号(XPASS → 摘 mark)跑完过一个完整闭环
|
||||
- `examples/01_clean_and_describe.py` 实测跑通(exit 0);`00_quickstart.py` 仍等 `stats` / `viz`
|
||||
- 38 条 skip 是等实现的算法(`train_test_split`、`describe`、`bootstrap_ci`、`monte_carlo` 等)
|
||||
@@ -262,25 +268,22 @@
|
||||
均值为负时 `σ/μ` 会给出负值,容易被读成"波动更小",其实只是符号跟着均值走。取绝对值后 CV 恒非负。可比性仍只在均值远离 0 的比率尺度数据上成立(已写进 docstring 的 Notes) |
|
||||
| 39 | 工作约定:**语义 / 非法输入口径由用户拍板,Codex 只列选项与影响、不代选**;**ruff、`STATUS.md`、`CHANGELOG.md`、`tests/`、docstring 这类与算法无关的工程事务,Codex 顺手补掉** |
|
||||
| 40 | scipy 的检验结果统一走 `_pvalue(result: Any) -> float` 助手取 p 值,不逐处挂 `# type: ignore` |
|
||||
| 41 | `correlation_matrix` **只返回数值矩阵**;星标表拆 `correlation_table`、p 值拆 `correlation_pvalues` |
|
||||
用户 2026-10-03 选方案 A。数值矩阵能做的事(挑高相关对、喂聚类 / PCA、画热力图)字符串表都做不了,而反过来随时能格式化 —— 展示需求属于**输出端**,不该侵占返回值;一旦返回字符串,数值信息不可逆地丢失。三个函数共用 `_check_corr_args` 守卫与成对删缺失口径,`correlation_table` 在**最外层**调另外两个(计算型当主、展示型当辅)。见易错点 14 |
|
||||
`PearsonRResult` / `SignificanceResult` 是 `_make_tuple_bunch` 在**运行时**拼出来的类,存根里没有 `pvalue` 字段:解包会退化成 `object`、属性访问会报"未知"。运行时完全正常(实测三个函数返回的都是 `np.float64`)。收成 `Any` 是**一处**收窄,调用点仍然干净;`# type: ignore` 要挂在每一处,且会连带关掉该行后面的检查。见 `docs/04-environment.md` 坑 10 |
|
||||
用户 2026-10-03 明确:没考虑到的非法情况"告诉我我自己改"——因为那是设计决策,得由写实现的人形成判断;而 lint / 进度板 / 测试这类机械活再由用户动手只是浪费。这条与决策 25 / 31 配套:25 定"谁写 src",31 定"Codex 可改的 bug 范围",39 定"非算法事务的默认归口" |
|
||||
docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向,实现里多做的 `.T` 是净负担 —— 且不是"风格不同"那么轻:转置后 `.loc[指标名] = <按变量索引的 Series>` 与列名零交集,pandas **不报错**、把四行整行填成 NaN。方向约定以"能按列名对齐"为准,见易错点 11 |
|
||||
原码写的是 `if offset > 0: raise`,把唯一合法的用例(正 offset 正好抬定义域)全拒了,报错文案却是"offset 必须大于 0",自相矛盾。改成校验真正的前提:`log` 要求自变量取正。`offset=0` 对全正数据合法(`offset=None` 的自动分支本来就取 0)。**Codex 代选、待用户确认** —— 若原意是"只收正 offset",改回即可 |
|
||||
## 下一步
|
||||
|
||||
1. **`correlation_matrix` 的返回契约待你拍板**(这是 L0 第 9 项唯一还没定的地方):
|
||||
现在返回的是**带星标的字符串表**(`1.000***`),方便直接贴论文;
|
||||
但 docstring 的契约与 L0 用例要的是**数值矩阵**(对角 = 1.0),两者只能留一个。
|
||||
三个选项:
|
||||
- **A(推荐)**:返回数值矩阵,另给一个 `correlation_table(corr, p)` 出星标字符串表
|
||||
—— 数值矩阵能做的事(挑高相关对、喂给聚类/PCA、画热力图)字符串表都做不了,
|
||||
而反过来随时能格式化。代价:多一个函数。
|
||||
- **B**:就返回字符串表,把 docstring 契约改掉、L0 用例改成断言 `1.000`。代价:丢数值能力。
|
||||
- **C**:返回数值矩阵,星标表挂到 `df.attrs["display"]` 之类的地方。代价:藏得深,不显式。
|
||||
顺带两处一并定了:**对角线不该带 `***`**;`df_num` 含 NaN 时 `corr` 成对删失但 `pearsonr` 不删,
|
||||
r 与 p 会对不上(要不要先 `dropna` 成对再算 p)。
|
||||
2. 之后 L0 剩余:`bootstrap_ci`(**S 档,要写函数卡片**)→ `monte_carlo`(**S 档**)→ `save_figure`(A)。
|
||||
1. **`bootstrap_ci` 是 L0 里下一个该写的**(**S 档**,`stats/bootstrap.py`)——
|
||||
除了函数本身,还要配一张完整函数卡片(模板 `docs/dev/function-card-template.md`),
|
||||
因为它是"没有解析解时怎么给区间"的通用答案(`docs/03-roadmap.md` §3.1)。
|
||||
注意 `tests/test_stats.py` 里已有 3 条现成的用例在等它(现在挂着 skip),写完就是现成的验收标准。
|
||||
2. 之后 L0 剩余:`monte_carlo`(**S 档**)→ `save_figure`(A)。
|
||||
3. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。
|
||||
4. 已拍板的旧口径(留档):`log_transform` 的 `offset` 维持现状(决策 35);
|
||||
`describe` / `zscore` 默认丢掉非数值列(决策 37);变异系数用 `σ / |μ|`(决策 38);
|
||||
分工见决策 39。
|
||||
分工见决策 39;`correlation_matrix` 走方案 A(决策 41)。
|
||||
5. **仍未答的一个小问题**:`correlation_matrix` 现在只收 `DataFrame`,
|
||||
而 `describe` / `zscore` / `transform` 都收 ndarray —— 要不要也放开?(你说了算)
|
||||
Reference in new issue
Block a user