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:
guzhujushi committed 2026-10-03 18:40:41 +08:00
1 parent 95f5266563
commit f95fbd2726
6 files changed
+422 -24

No files matched your search

+33
View File
@@ -238,6 +238,39 @@
这是刻意的:先定接口,再按 `docs/03-roadmap.md` 的 L0 → L1 顺序填实现。
- 已知缺口:`SALib`(Sobol/Morris)、`pymoo`、`pyright` 已装进 conda 环境 `mcm-toolkit`,`.venv` 里没有。
### Fixed
- `stats.confidence_interval`:修掉一个**会给出错误数字**的硬 bug —— 原码返回
`(float(min(0.0, lower)), float(max(upper, 1.0)))`,`min` / `max` 的**参数写反**(下界被钉死成 `0.0`),
而且**无条件**把区间夹到 `[0, 1]`。实测 `confidence_interval(rng.normal(170, 8, 50), method="t")`
的下界是 `0.0`(正确是 `171.0 ± 2.1`);`confidence=1.5` 被夹成 `(0.0, nan)` 而不报错。
现在**只有 `proportion` 夹到 `[0, 1]`**(决策 42),`normal` / `t` 直接返回 `mean ± z|t·se`;
`method` 非法改抛 `ValueError`、`confidence` 按 `(0, 1)` 开区间校验、样本量 < 2 抛 `ValueError`。
- `stats.confidence_interval` 的入参曾只收 `DataFrame`,而 docstring 的 Examples 传 `list`、
L0 用例传 `ndarray` —— **两条都会 `TypeError`**。现放宽到 `list` / `tuple` / `ndarray` / `Series` / `DataFrame`。
### Added
- `stats.inference._statistic(result: Any) -> float` 助手,与 `_pvalue` 成对(决策 43)。
scipy 的 `TtestResult` 与 `PearsonRResult` 同源,都是 `_make_tuple_bunch` 运行时拼的类,
存根里 `.statistic` / `.pvalue` 都看不到。用户报告的 `t_test` 「307 / 308 两行报错」即此,
**不是真 bug**(探针实测 `TtestResult(statistic=3.873, pvalue=0.0305)`,运行时完全正常);
换成助手后 pyright 归零,**一行 `# type: ignore` 都没挂**。见 `docs/04-environment.md` 坑 10。
- `stats.t_test`(L1 提前落地):docstring 补齐 `Parameters` / `Returns` / `Raises` / `Notes`;
`_clean_data` 改用 `isinstance(data, pd.DataFrame)` 收窄(坑 8 的 `cast`);`alpha` 开区间校验;
`df` 按 Welch–Satterthwaite / pooled / paired 分别给。
- `tests/test_stats.py`:**+10 条(36 → 46)**,含把原先挂着 `skip` 的
`test_confidence_interval_matches_scipy` 转正。对照一律用解析式(z 公式、Welch `df` 公式、
`t = (mean − value)/(s/√n)`),不拿实现对自己。
- `docs/dev/personal-pitfalls.md`:新增第 15 条「区间被无条件夹到 `[0, 1]`,且 `min` / `max` 参数写反」;
并把**停在 6 号就没人管**的索引表补齐到 15(只补索引,正文一个字没动)。
- `docs/04-environment.md`:坑 10 扩写成 `pvalue` / `statistic` 两条;顺手修掉残留的
`[FENCE]` 代码围栏占位符与一处重复的 `---`。
### Changed
- `STATUS.md`:L0 记为 **9/12**(另 L1 的 `confidence_interval` / `t_test` 提前落地);
校正测试数为 **162 条 / 125 passed / 37 skipped**;新增决策 42 / 43;「下一步」列出
**`t_test` 待用户拍板的 4 个口径**(单样本结论文案、`paired` 的 NaN 对齐、`value` 被静默忽略、
`DataFrame` 摊平多列)—— 按决策 39,Codex 只列选项、不代选。
## [0.0.1] - 2026-10-01
### Added