fix(stats): t_test/confidence_interval 收口 —— 307/308 是存根误报,CI 的夹取才是真 bug(L1 提前 2 个)
## 用户问的「t_test 307 / 308 两行报错」 - 那两行是 `float(res.statistic)` / `float(res.pvalue)`,报错来自 scipy 的 `TtestResult`: 它和 `PearsonRResult` 一样是 `_make_tuple_bunch` **运行时**拼出来的类,存根里没有这两个字段。 - 探针实测 `TtestResult(statistic=3.873, pvalue=0.0305)`,运行时取值完全正常 —— **不是真 bug**。 - 按决策 40 的路子补 `_statistic(result: Any) -> float`(与 `_pvalue` 成对),一行 `# type: ignore` 都没挂。 详情见 `docs/04-environment.md` 坑 10(已扩写为 pvalue / statistic 两条)。 ## 顺手查出来的真 bug:`confidence_interval` - 原码 `return (float(min(0.0, lower)), float(max(upper, 1.0)))`: ① `min` / `max` 的**参数写反**,下界被钉死成 `0.0`; ② **无条件**把三种 method 的区间都夹到 `[0, 1]`。 - 实测 `confidence_interval(rng.normal(170, 8, 50), method="t")` 下界是 `0.0`(正确 `171.0 ± 2.1`); `confidence=1.5` 被夹成 `(0.0, nan)` 而不报错。 - 现在:**只有 `proportion` 夹**(决策 42),`normal` / `t` 返回 `mean ± z|t·se` 不夹; `method` 非法抛 `ValueError`(§4.7)、`confidence` 走 `(0, 1)` 开区间、样本量 < 2 抛 `ValueError`。 - 入参放宽到 `list` / `tuple` / `ndarray` / `Series` / `DataFrame`: docstring 的 Examples 传 list、L0 用例传 ndarray,原来**两条都直接 `TypeError`**。 ## t_test - docstring 补齐 `Parameters` / `Returns` / `Raises` / `Notes`(§4.7); `_clean_data` 改用 `isinstance(data, pd.DataFrame)` 收窄(坑 8 的 `cast`); `alpha` 开区间校验;`df` 按 Welch–Satterthwaite / pooled / paired 分别给。 - **4 个口径未代选**(决策 39),已记进 `STATUS.md`「下一步」第 6 条待用户拍板: 单样本结论文案、`paired` 的 NaN 对齐、`value` 被静默忽略、`DataFrame` 摊平多列。 ## 测试与文档 - `tests/test_stats.py` +10 条(36 → 46),含把原先挂 skip 的 `test_confidence_interval_matches_scipy` 转正;对照一律用解析式(z 公式 / Welch df / t 公式)。 - 全量:**125 passed / 37 skipped / 0 xfail**,ruff 0,pyright 0。 - 个人易错点新增第 15 条,并把**停在 6 号**的索引表补齐到 15(只补索引,正文未动)。 - `docs/04-environment.md` 顺手修掉残留的 `[FENCE]` 代码围栏占位符与一处重复的 `---`。 - `STATUS.md`:L0 记 9/12;校正测试数为 162 条;新增决策 42 / 43。
This commit is contained in:
1 parent
95f5266563
commit
f95fbd2726
6 files changed
+422
-24
No files matched your search
@@ -28,6 +28,15 @@
|
||||
| 4 | 只覆盖自己的状态,忘了第三方库还在用 numpy 旧全局 | 2026-10-01 |
|
||||
| 5 | 参数校验排在副作用(建目录 / 写文件)之后 | 2026-10-01 |
|
||||
| 6 | 把 lint 的"没报错"当成"没问题" | 2026-10-01 |
|
||||
| 7 | 写"守卫子句"时,忘了不是所有入参都长一样 | 2026-10-02 |
|
||||
| 8 | 改了一处,忘了同源的另一处 | 2026-10-02 |
|
||||
| 9 | 实现写完了,测试还挂着 skip —— 绿灯是假的 | 2026-10-02 |
|
||||
| 10 | 把真报错当成"编辑器误报" | 2026-10-02 |
|
||||
| 11 | pandas 对不齐就静默填 NaN,不报错 | 2026-10-03 |
|
||||
| 12 | 三个并列的 `if`,后面的静默覆盖前面的 | 2026-10-03 |
|
||||
| 13 | `Index == 标量` 得到布尔数组,`if` 要等到第 2 列才炸 | 2026-10-03 |
|
||||
| 14 | 把"能看的表"和"能算的表"塞进同一个返回值 | 2026-10-03 |
|
||||
| 15 | 区间被无条件夹到 `[0, 1]`,且 `min` / `max` 参数写反 | 2026-10-03 |
|
||||
|
||||
---
|
||||
|
||||
@@ -234,3 +243,29 @@
|
||||
③ 拆的时候让**计算型**当主函数、**展示型**去调它(`correlation_table` 调
|
||||
`correlation_matrix` + `correlation_pvalues`),而不是反过来。
|
||||
- 首次:2026-10-03|复发:0
|
||||
|
||||
### 15. 区间被无条件夹到 `[0, 1]`,且 `min` / `max` 的参数写反了
|
||||
|
||||
- 现象:`confidence_interval` 的返回写成了
|
||||
`return (float(min(0.0, lower)), float(max(upper, 1.0)))`,本意是"比例区间不能越界"。
|
||||
三个毛病叠在一起:
|
||||
① **参数写反**:要夹下界应该写 `max(0, lower)`,写成了 `min(0.0, lower)` —— `0.0` 比任何正的下界都小,
|
||||
`min` 取到的永远是它,于是**下界恒为 0**;上界那条 `max(upper, 1.0)` 同样是反的,
|
||||
只是它在 `upper > 1` 时凑巧看不出问题。
|
||||
② **无条件夹**:三种 method(含 `normal` / `t` 这两种**均值**区间)全被夹。
|
||||
均值区间的量纲根本不在 `[0, 1]` 里:实测
|
||||
`confidence_interval(rng.normal(170, 8, 50), method="t")` 的下界是 **0.0**,
|
||||
而正确结果是 `171.0 ± 2.1`。这种错**不抛异常**,只有把数字打印出来才会发现"身高的下界是 0"。
|
||||
③ **夹子会吞掉报错信号**:`confidence=1.5` 时 `z` 是 `nan`,`min(0.0, nan)` 返回 `0.0`
|
||||
(`min` 在遇到 `nan` 之前先锁定了第一个参数),于是本该报错的入参被盖成 `(0.0, nan)`。
|
||||
另外 `confidence=0` 也一路放行(定义域应是 `(0, 1)` **开区间**)。
|
||||
- 为什么会错:脑子里记的是"clamp(x, lo, hi)"这个整体,手上却从两个方向各写了一个 `min` / `max`;
|
||||
更要命的是**试数据选了 `[1, 2, 3, 4, 5]`** —— 它恰好落在 `[0, 1]` 附近,夹不夹都看不出来。
|
||||
换一个量纲明显不在 `[0, 1]` 的例子(身高 170、GDP 十亿)跑第一遍,这个 bug 一秒现形。
|
||||
- 以后怎么防:① 夹子只写一个方向:`np.clip(x, lo, hi)`,或 `max(lo, min(hi, x))`;
|
||||
不要手写两个 `min` / `max` —— 它们看起来对称,其实一个管下界一个管上界,最容易写反。
|
||||
② **写"区间"函数先做量纲检查**:拿一个边界外的数值当输入,结果被夹到 0 / 1 就是 bug。
|
||||
③ 边界参数一律按开区间校验(`0 < confidence < 1`),并用 `np.isnan` 兜住参数异常产生的 nan。
|
||||
④ 记住 `min` / `max` **不传播 nan、结果还取决于参数顺序**(`min(0.0, nan)` → `0.0`、
|
||||
`min(nan, 0.0)` → `nan`)—— "夹一下"就能把 nan 悄悄换成边界值。
|
||||
- 首次:2026-10-03|复发:0
|
||||
Reference in new issue
Block a user