fix(stats): correlation_matrix 修硬 bug,两处 pyright 报错按坑 8/10 正经修(无 ignore)

用户报的是"63 行 p 值赋值报错"。查下来:63 行运行完全正常(p 是 np.float64),
pyright 那条是 scipy 存根的缺陷。但顺着看发现了真正跑不起来的第 41 行。

correlation_matrix:
- 硬 bug: `if numeric_cols == 0` 拿 Index 和标量比 -> 得到布尔数组。
  0 列时静默不报错、1 列时碰巧能跑、>=2 列直接 ValueError 崩(而 >=2 列正是常见情况)。
  改成 len(numeric_cols) == 0,并加回归用例
- pyright 两处报错都不是运行时问题,且一处 # type: ignore 都没挂:
  - data[numeric_cols] 是坑 8 的 DataFrame | Series 重载 -> cast 收窄
  - `_, p = stats.pearsonr(...)` 解包退化成 object(scipy 的返回值是 _make_tuple_bunch
    运行时拼的类,存根里没有 pvalue)-> 新增 _pvalue(result: Any) -> float 小助手统一取,
    后面 t_test / anova / chi_square_test 可复用
- method 校验提到最前;add_stars 的 elif 链改 if;清掉 3 条 F541 与 1 条 I001,ruff 归零
- 顺带记录两处待定:对角线被标了 ***;含 NaN 时 corr 成对删失而 pearsonr 不删,r 与 p 对不上

tests:
- test_correlation_matrix_diagonal_is_one 由 skip 改成 xfail(strict=False)(决策 30):
  契约要数值矩阵、实现返回带星标字符串表,口径待拍板
- 新增 2 条:多列数值表能跑通、非法输入报错

验证: pytest 105 passed / 38 skipped / 1 xfail;ruff All checks passed;pyright 0 errors。

docs: STATUS.md(correlation_matrix 进行中 + 决策 40 + 下一步 A/B/C 选项);CHANGELOG;
      personal-pitfalls 追加第 13 条(Index == 标量 得到布尔数组);
      docs/04-environment.md 追加坑 10(scipy 检验结果的存根缺陷)
This commit is contained in:
guzhujushi committed 2026-10-03 14:45:10 +08:00
1 parent cfc1be31ca
commit 3432bf3e50
6 files changed
+215 -24

No files matched your search

+22
View File
@@ -306,6 +306,28 @@ dtype is specified. This behavior is deprecated and will be removed in a future
---
**坑 10 —— scipy 的检验结果在类型检查器里没有 `pvalue` 字段。**
现象:`_, p = stats.pearsonr(x, y)` 运行没问题,但 pyright 报
`无法将 "_T_co@tuple" 类型的参数分配给 ... 的 "float"`;改成 `.pvalue` 又报
`无法访问类 "PearsonRResult" 的属性 "pvalue"`。两条都像"库的锅",其实是存根看不全。
原因:`PearsonRResult` / `SignificanceResult` 不是普通的 `namedtuple`,
而是在 `scipy/stats/_stats_py.py` 里用 `_make_tuple_bunch(...)` 在**运行时**拼出来的类
(`PearsonRResult` 还继承自 `PearsonRResultBase`)。类型检查器看不到运行时才生成的字段,
于是解包会退化成 `object`、属性访问会报"未知"。
**这不是运行时问题** —— `pearsonr` / `kendalltau` / `spearmanr` 返回的 p 实测都是 `np.float64`。
修法:不要逐处挂 `# type: ignore`,写一个小助手把结果显式收成 `Any`:
```python
def _pvalue(result: Any) -> float:
return float(result.pvalue)
```
一处收窄、调用点全干净,后面 `t_test` / `anova` / `chi_square_test` 可以复用。
---
## 五、两套环境的实际版本(已对齐)
**2026-10-01 已把 base 从 Anaconda 2025.12 升到 2026.07,两套环境版本已对齐。**
+19
View File
@@ -198,3 +198,22 @@
- 同一轮的另一个同类例子见第 11 条(`.loc` 对齐失败静默填 NaN):
这两个 bug 的共同点是"**不抛异常**",靠的是断言而不是报错。
- 首次:2026-10-03|复发:0
### 13. `Index == 标量` 得到布尔数组,`if` 要等到第 2 列才炸
- 现象:`correlation_matrix` 里的判空写成了 `if numeric_cols == 0:`,本意是"一个数值列都没有就报错"。
实际行为分成三段,**每一段都不一样**:
- **0 列**:`array([], dtype=bool)` → `bool(...)` 是 `False` → **静默不报错**,带着空表继续往下跑;
- **1 列**:`array([False])` → 单元素数组能转 `bool` → **碰巧正常**;
- **≥2 列**:`array([False, False, ...])` → `ValueError: The truth value of an array
with more than one element is ambiguous` → **直接崩**。
而"≥2 个数值列"恰恰是最常见的情况,所以这一行等于让整个函数不可用。
- 为什么会错:`Index` 重载了 `==`,做的是**逐元素比较**,返回的是**数组**不是 `bool`。
脑子里比的是"这个序列是不是空的",手上写的是"这个序列的每个元素是不是 0"。
更阴的是 1 列时它能跑通 —— 拿单列试一下就会以为这行没问题。
- 没被抓住的原因:对应的 L0 用例当时还挂着 `skip`(见第 9 条),所以实现零覆盖。
- 以后怎么防:① 判"空"一律 `len(x) == 0`(或 `.empty`),绝不用 `== 0`;
② 判断容器里有没有东西,用 `if not len(...)` 而**不是** `if x:` ——
`if x:` 对 `DataFrame` / `Index` / 多元素 `ndarray` 会抛同一个歧义错误;
③ 新写的分支,**至少拿 0 / 1 / 多三种形状各跑一遍**,单列能过不代表多列能过。
- 首次:2026-10-03|复发:0