feat(data): transform.py 四个函数落地;补 18 条测试、清 5 条 ruff

实现(用户本人写):
- normalize(minmax / vector / max,三种都按列)+ feature_range 校验 + 常量列不出 nan
- standardize(按列,ddof 真的传下去)+ 类型守卫
- log_transform(offset=None 时自动取 1 - min(x),保证 x + offset >= 1)+ 类型守卫
- one_hot_encode(自动识别对象列 / 指定列 / drop_first)
- data/ 下的 L0 至此结清(L0 7/12)

Codex 的轻度修复(不改接口与语义):
- 清掉 5 条 ruff:RET503 / F541 / RET504 / RET505 / SIM108
- supported_method 与 feature_range 的校验提到 np.asarray 之前(守卫集中开头)
- 修掉说反的报错文案(条件是要求下界 < 上界,原文写"上界必须小于下界")
- one_hot_encode 自动识别时显式加上 str / string:只写 object 在 pandas 3 上是弃用路径,
  每次调用发 Pandas4Warning,未来会变成"一列都选不中"的静默错误(详见坑 9)

测试:
- 新写 18 条(transform 段),其中两条是把原先挂着的 skip 转为实跑 ——
  normalize / standardize 的实现此前一条测试都没跑到(详见个人易错点 9)
- 82 passed / 40 skipped / 0 xfailed;ruff 0;pyright 0

文档:
- STATUS.md:E 节 + 验证表(122 条用例分布)+ 下一步重写(transform 待定的 4 个口径)+ 决策 34
- docs/04-environment.md:新增坑 9(pandas 3 的 str dtype 与 select_dtypes 弃用路径,含实测对照表)
- docs/dev/personal-pitfalls.md:只追加第 9 条(实现写完但测试还挂 skip,绿灯是假的)
- CHANGELOG.md:本轮变更 + 记录 transform 待定的 4 个口径
This commit is contained in:
guzhujushi committed 2026-10-02 20:43:03 +08:00
1 parent 5749e50e83
commit 6eab0babde
6 files changed
+330 -32

No files matched your search

+31
View File
@@ -271,6 +271,37 @@ numeric = cast(pd.DataFrame, data[numeric_cols])
**一句诊断口诀**:pyright 说某行"属性不存在"、但运行时确实能跑,先 `reveal_type(...)`
把真实推断打出来,看并集里混进了什么,别急着加 `# type: ignore`。
**坑 9 —— pandas 3 起字符串列的 dtype 是 `str`,`select_dtypes(include=["object"])` 已走弃用路径。**
现象:`one_hot_encode` 自动识别类别列时只写了 `['object', 'category']`,
功能看起来正常,但**每次调用都发一条 `Pandas4Warning`**,而且 warning 里写着这个行为
"will be removed in a future version"。
```text
For backward compatibility, 'str' dtypes are included by select_dtypes when 'object'
dtype is specified. This behavior is deprecated and will be removed in a future version.
```
原因:pandas 3 起字符串列的 dtype 是 `str`(不再是 `object`)。当前版本为了向后兼容
仍然把 `str` 列算进 `object`,但那是**过渡行为**。等它真的移除,`include=['object']`
会一列都选不中 —— 那时 `one_hot_encode(df)` 会**静默**返回未编码的原表,
不报错、不告警,是最难查的那类错。
实测(本机 pandas 3.0.6):
| 写法 | 选中 | 告警 |
|---|---|---|
| `include=['object', 'category']` | ✅ | **1 条** |
| `include=['object', 'category', 'str', 'string']` | ✅ | 0 |
| `include=['str', 'string']` | ✅ | 0 |
修法:把 `'str'` / `'string'` 显式写进 `include`(这也是 pandas 官方迁移指南推荐的写法,
同时兼容 pandas 2 与 3)。配套测试用 `warnings.catch_warnings(record=True)` 断言
**一条告警都不该有**,这样以后 pandas 再收紧行为时会立刻变红,不会静默。
**一句诊断口诀**:`select_dtypes` 选不中东西时,先 `print(df[col].dtype)` 看真实 dtype,
别照着记忆里的 `object` 写。
---
---
+14
View File
@@ -128,3 +128,17 @@
把该收窄的地方一次列全,别改完第一处就收手;
并且以测试信号(XFAIL → XPASS)为准,别以"我改的那一行看起来对了"为准。
- 首次:2026-10-02|复发:0
### 9. 实现写完了,测试还挂着 skip —— 绿灯是假的
- 现象:`transform.py` 四个函数都写完了,`pytest` 一片绿(`64 passed`),
但 `test_normalize_minmax_range` / `test_standardize_has_zero_mean_unit_std` 两条
还挂着 `@pytest.mark.skip(reason=_NOT_IMPLEMENTED)` —— 新代码**一条测试都没跑到**。
顺带还漏掉一个真错:`normalize` 少了兄弟函数都有的类型守卫,`normalize(None)` 静默返回 NaN。
- 为什么会错:`skip` 是"还没写"的意思,写完实现后该由**写的人**把它摘掉;
但摘 mark 这个动作不在"写函数"的肌肉记忆里,跑一次 `pytest` 看到全绿就以为收工了。
这是决策 26 埋下的反面:逐条 skip 比模块级 skip 精确,但**数量得自己盯**。
- 以后怎么防:每次写完实现,用 `pytest -q -rs`(或 `--collect-only`)**核对 skip 条数掉没掉**;
条数没变就说明实现没被验证过。另一个信号是 `pytest --collect-only` 的收集数 ——
新写的测试没进收集列表,就是名字或位置不对。
- 首次:2026-10-02|复发:0