feat(data): detect_outliers 收尾(文本列恒 False)+ 补测试与文档
实现(用户本人写 + Codex 轻度收尾): - 统计量(quantile / mean / std)与掩码写入都收窄到数值列,含文本列的赛题表不再崩 - 非数值列在掩码里恒 False(用户定的语义),docstring Notes 补上这条 - 删掉死代码 data=data.copy():下面的 to_frame()/pd.DataFrame() 本就返回新对象, 函数全程只读 data,白复制一份;且它排在类型分派之前,会把错误入参的报错 从 ValueError 变成 AttributeError - 删掉那行死代码后 pyright 反而暴露 1 个真错:df[Index] 被类型存根当成 Series, .std() 于是被判成标量、std.replace(...) 非法。在取列子表处 cast 成 DataFrame 按运行时真值收窄(详见 docs/04-environment.md 坑 8) 测试: - test_detect_outliers_survives_text_columns 摘掉 xfail 并加强断言(文本列必须恒 False) - 新增 test_detect_outliers_all_text_frame_returns_all_false,钉住"整表无数值列"边界 - 58 passed / 2 xfailed -> 60 passed / 1 xfailed;ruff 0;pyright 0 - 仅剩 1 条 xfail:impute_missing 的 mode 分支就地改调用方的表(要选语义,留给用户) 文档: - STATUS.md:L0 5/12、验证结果表、下一步重写、新增决策 32(非数值列恒 False) - docs/04-environment.md:新增坑 8(pandas 存根让 df[Index] 退化成 Series,及三种无效修法) - docs/dev/personal-pitfalls.md:只追加第 8 条(限制掩码 ≠ 限制统计量) - CHANGELOG.md:本轮变更
This commit is contained in:
1 parent
319b16b17c
commit
b6dc4bc5a6
6 files changed
+144
-43
No files matched your search
@@ -10,7 +10,7 @@
|
||||
|
||||
**第二阶段(进行中)**:按 `docs/03-roadmap.md` 的 L0 → L1 顺序**逐个实现函数**,
|
||||
用户本人写 `src/`、Codex 负责审查 + `tests/`(决策 25)。
|
||||
当前 4/12:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`。
|
||||
当前 5/12:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、`detect_outliers`。
|
||||
|
||||
## 任务清单
|
||||
|
||||
@@ -95,15 +95,23 @@
|
||||
- `threshold` 用 `math.ceil` 算 `min_non_na`:3 列、阈值 0.5 时必须是 2,
|
||||
写 `int()` 截断会漏删
|
||||
- 顺手落地兄弟函数 `impute_missing` / `winsorize`(原属 L1)
|
||||
- [ ] **待收尾:** `detect_outliers` 只剩"文本列"1 处,且要先定设计(见「下一步」第 1 条)
|
||||
- [ ] 剩余 8 项:`detect_outliers`(收尾)+ `normalize` `standardize` `describe`
|
||||
`correlation_matrix` `bootstrap_ci` `monte_carlo` `save_figure`
|
||||
- [x] `data/clean.py` 的 `detect_outliers`(L0 第 5 项,S 档,**实现由用户本人写**)
|
||||
- 语义由用户本人定:**非数值列不参与统计,掩码里恒 `False`**(见决策 32)
|
||||
- 三轮修掉的 4 个 bug:`threshold` 范围校验放错函数、IQR 上界符号、
|
||||
`is_series` / `is_ld_array` 判在类型转换之后(永远为 False)、文本列上算分位数直接崩
|
||||
- 最后这轮的关键教训:**限制掩码 ≠ 限制统计量** —— 只把 `mask[numeric_cols] = ...`
|
||||
收窄是不够的,`quantile()` / `mean()` / `std()` 也要一起收窄(见坑 8 / 个人易错点 8)
|
||||
- 返回形状与输入同形;Series 进 Series 出(列名保留)、一维数组进一维布尔数组出
|
||||
- 测试:`tests/test_data.py` 该函数共 11 条,含「全文本表返回全 False」这条边界
|
||||
- [ ] **待补:** `detect_outliers` 的 S 档函数卡片(`docs/algorithms/`)与 `examples/` 最小用法
|
||||
- [ ] 剩余 7 项:`normalize` `standardize` `describe` `correlation_matrix`
|
||||
`bootstrap_ci` `monte_carlo` `save_figure`
|
||||
|
||||
### 验证结果
|
||||
|
||||
| 检查 | venv | conda `mcm-toolkit` |
|
||||
|---|---|---|
|
||||
| `pytest` | 见下(单列) | **58 passed, 42 skipped, 2 xfailed** |
|
||||
| `pytest` | 见下(单列) | **60 passed, 42 skipped, 1 xfailed** |
|
||||
| `import mcm_toolkit` | 0.0.1 / 16 项 | 0.0.1 / 16 项 |
|
||||
| `ruff check` | All checks passed | **All checks passed**(0 error) |
|
||||
| `scripts/doctor.ps1` | 跑通 | — |
|
||||
@@ -113,10 +121,10 @@
|
||||
(修配置之前是 17 errors,全是"无法解析导入 numpy / pandas",见决策 29 与
|
||||
`docs/04-environment.md` 坑 7)
|
||||
- base 亦已复核:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6(与两套环境一致)
|
||||
- 58 条通过 = 20 条公开 API 契约测试 + 11 条 `core` 行为测试 + 27 条 `data` 行为测试
|
||||
- 2 条 `xfail` 是**已知待修**的契约:`detect_outliers` 遇文本列、`impute_missing` 的 mode 就地改。
|
||||
两者都**不是漏改,是设计没定**;修好后会自动变 XPASS,那时把 `xfail` 摘掉即可(决策 30)
|
||||
- 上一轮 5 条 `xfail` 正是靠这个 XPASS 信号确认修好并摘掉的
|
||||
- 60 条通过 = 20 条公开 API 契约测试 + 11 条 `core` 行为测试 + 29 条 `data` 行为测试
|
||||
- 只剩 1 条 `xfail`:`impute_missing` 的 mode 分支就地改调用方的表。
|
||||
这条**不是漏改,是要选语义**;修好后会自动变 XPASS,那时把 `xfail` 摘掉即可(决策 30)
|
||||
- 累计 6 条 `xfail` 正是靠这个 XPASS 信号确认修好并摘掉的(本轮摘掉 1 条 + 撤销 1 条过严的判定)
|
||||
- 42 条 skip 是等实现的算法(`normalize` / `standardize` / `train_test_split` 等)
|
||||
- 三个 `.ps1` 通过 PowerShell AST 语法解析
|
||||
|
||||
@@ -127,8 +135,8 @@
|
||||
只差 Python 补丁号(3.13.9 vs 3.13.15)。详见 `docs/04-environment.md` §5。
|
||||
- [x] ~~Gitea 远程尚未配置~~ → **已配置并推送完成**,见上面 D 节与决策 19/20。
|
||||
note:仓库当前是 **Public**(匿名 `git ls-remote` 可读),要改私有去 Gitea 仓库设置。
|
||||
- [ ] L0 完成 **4/12**(`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`),其余按 `docs/03-roadmap.md` 的 L0 → L1 顺序推进;
|
||||
详情见上面 E 节
|
||||
- [ ] L0 完成 **5/12**(`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、
|
||||
`detect_outliers`),其余按 `docs/03-roadmap.md` 的 L0 → L1 顺序推进;详情见上面 E 节
|
||||
- [ ] 本机 `exec_command` 在沙箱模式下**完全不可用**
|
||||
(`helper_unknown_error: setup refresh had errors`),只有提权
|
||||
(`require_escalated`)模式可用。沙箱模式只允许写 `D:\MCM&ICM_Toolkit`。
|
||||
@@ -172,22 +180,23 @@
|
||||
| 30 | 已知待修的契约用 `@pytest.mark.xfail(strict=False)`,不用 skip | skip 会让"契约还没写"和"写了但实现有问题"混在一起,5 条 skip 曾把 `clean.py` 的 bug 全藏住。xfail 照样进报告,修好后自动变 XPASS,是"修好了"的信号,而且 XPASS 不失败,不会卡住提交 |
|
||||
| 29 | 静态分析器钉死到工作区 `.venv`,并把 base 的 site-packages 用 `.pth` 链进去 | pyright / Pylance **不认** `pyvenv.cfg` 的 `include-system-site-packages`,`.venv` 自己只有 `pip`;不钉的话它们会去 PATH 上找 python,本机 PATH 上那个是 Codex 运行时的 3.14 shim,没有科学栈,于是报"无法解析导入"。`.pth` 是唯一被 pyright 认的转发手段 |
|
||||
| 28 | 不启用 ruff 的 preview 规则集 | `E302`(函数间要 2 个空行)在 ruff 0.16 里是 preview 规则,`select = ["E"]` 默认**不包含**它——所以少一个空行 ruff 照样报 "All checks passed"。行数空行得自己盯,别指望 lint |
|
||||
| 32 | `detect_outliers` 对非数值列恒返回 `False`(既不跳过也不报错) | **用户本人在两条路里选的**。跳过会让 docstring 里"与原数据同形"变成谎话(形状对不上,没法按位置对回原表);报错则让一张带 `city` 列的真题表直接不可用。`False` 的读法是"这一列没有离群点",对文本列是最不坏的解释,也让整表能一次跑完 |
|
||||
| 31 | Codex 可**直接改** `src/` 的范围:纯 bug(不改接口与语义)、格式、死代码 | 决策 25 说实现归用户,但"能跑"是底线:`winsorize(pd.Series)` 因 `select_dtypes` 在 `Series` 上不存在而直接崩、测试变红,这种必须当场修。凡是要**选一个语义**的(文本列返回什么、mode 要不要就地改)一律留给用户本人,Codex 只把选项和影响列清楚 |
|
||||
| 27 | base 里的 zstandard 残骸目录**改名禁用**,不删除、不重装 | 删掉不可逆;`pip install zstandard` 会给 base 塞一个 conda 记录里根本没有的包,下次 `conda update --all` 又要重新判断。改名后 `import` 变回正常 `ModuleNotFoundError`,pandas 自动跳过 |
|
||||
|
||||
## 下一步
|
||||
|
||||
1. **收尾 `detect_outliers`**(`src/mcm_toolkit/data/clean.py`,只剩 1 处,且**要先定设计**):
|
||||
非数值列怎么办 —— 一律返回 `False`,还是只对数值列算掩码?
|
||||
现在 iqr / zscore 两条分支都直接在**全表**上 `quantile()` / `mean()`,赛题表一带文本列就崩。
|
||||
定完把它换成 `data[numeric_cols].quantile(...)`(`winsorize` 已按"只对数值列算"改好,可直接照抄),
|
||||
并把 `tests/test_data.py::test_detect_outliers_survives_text_columns` 的 xfail 摘掉。
|
||||
2. **写 `detect_outliers` 的 S 档函数卡片**(`docs/algorithms/` 下,照 `docs/dev/function-card-template.md`)。
|
||||
roadmap 要求 S 档先写卡片再写码 —— 这次是反着来的,所以三个 bug 都在"该想清楚"的地方翻车:
|
||||
上界符号、布尔优先级、维度。卡片本身就是防这个的。
|
||||
3. 顺手把 `impute_missing` 的 mode 分支改成**返回新表**
|
||||
1. **写 `detect_outliers` 的 S 档函数卡片**(`docs/algorithms/` 下,照 `docs/dev/function-card-template.md`)。
|
||||
roadmap 要求 S 档先写卡片再写码 —— `detect_outliers` 是反着来的,于是 bug 全翻在
|
||||
"该想清楚"的地方:上界符号、布尔优先级、维度。卡片至少要把这几条钉住:
|
||||
两种方法的数学定义与失效场景、非数值列为什么恒 `False`、`k` / `threshold` 默认值从哪来、
|
||||
以及"只标不删"这个设计选择。
|
||||
2. 顺手把 `impute_missing` 的 mode 分支改成**返回新表**
|
||||
(现在就地改调用方的 DataFrame,和另外四个分支不一致;`data = data.copy()` 一行的事)。
|
||||
4. **下一个文件**:`data/transform.py` 的 `normalize` / `standardize`
|
||||
注意它和 `detect_outliers` 恰好相反:那个函数全程只读,加 `copy()` 是纯浪费 ——
|
||||
同一个 `copy()`,一处必须要、一处必须不要。
|
||||
3. **下一个文件**:`data/transform.py` 的 `normalize` / `standardize`
|
||||
(`tests/test_data.py` 里两条 skip 已就位;写之前先定 `method` 的取值集合与 `axis` 语义)。
|
||||
5. 每写完一个函数,在 `examples/` 里加一行最小用法。
|
||||
6. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。
|
||||
4. 补齐 `examples/`:`Result`、`load_table`、`clean_missing`、`detect_outliers` 都还差一行最小用法
|
||||
(roadmap §3.2 第 4 步)。
|
||||
5. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。
|
||||
Reference in new issue
Block a user