fix(data): 修掉 transform.py 的 3 处真 bug,并收口 4 个口径

不是编辑器误报:pyright CLI 与 pytest 都能复现(修前 7 failed / 75 passed),
所以一行 # type: ignore 都没加。

- 输入白名单漏了 pd.DataFrame -> standardize/log_transform 直接 TypeError,
  函数体里的 is_df 分支全是死代码;统一成模块级 _NUMERIC_TYPES 并前置到 np.asarray 之前
- normalize 三个 method 分支互相覆盖(minmax/vector 的结果被末尾无条件的 x/max 冲掉,
  实际三种 method 都只走 max),改成 if/elif/else
- one_hot_encode 按 data.columns 重包一层 DataFrame -> 哑变量列名对不上,编码列整列丢失,
  改为直接返回 get_dummies 的结果
- log_transform 的 offset 守卫写反(offset > 0 就抛错,恰好拒掉唯一合法用例),
  改为校验定义域前提 min(x) + offset > 0;offset=0 对全正数据合法
- 四个函数 docstring 按 docs/02-architecture.md 4.7 补齐 Parameters/Returns/Raises
- ruff 4 条 RET505 清掉;pyright 2 errors -> 0;测试 82 passed / 40 skipped

docs: STATUS.md L0 进度 7/12 + 决策 35;CHANGELOG 记录本轮回归与修复;
      personal-pitfalls 追加第 10 条(把真报错当成编辑器误报)
This commit is contained in:
guzhujushi committed 2026-10-02 21:48:27 +08:00
1 parent 6eab0babde
commit 4552aee41c
4 files changed
+229 -56

No files matched your search

+28 -21
View File
@@ -10,7 +10,7 @@
**第二阶段(进行中)**:按 `docs/03-roadmap.md` 的 L0 → L1 顺序**逐个实现函数**,
用户本人写 `src/`、Codex 负责审查 + `tests/`(决策 25)。
当前 5/12:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、`detect_outliers`。
当前 7/12:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、`detect_outliers`、`normalize`/`standardize`。
## 任务清单
@@ -62,7 +62,7 @@
- [x] 推送通道:公网 22 被防火墙过滤 → 改用 Tailscale(`100.115.86.48:22`),
`~/.ssh/config` 加了一条域名别名,remote 里仍写域名
**E. L0 实现进度(4/12)**
**E. L0 实现进度(7/12)**
- [x] `core/result.py` 的 `Result`
- `__repr__`:单行紧凑;`data` 只给类型与规模,**绝不**整数组打印
(十万级数组的 repr 实测仍 < 200 字符);`success` 恒在
@@ -123,8 +123,18 @@
- Codex 清掉 5 条 ruff(RET503 / F541 / RET504 / RET505 / SIM108),并把 `feature_range`
的两条校验提到 `np.asarray` **之前**、修掉说反的报错文案(条件是"下界必须小于上界")
- 测试:**新写 18 条**,其中两条是把原先挂着的 `skip` 转为实跑 —— 见 `tests/test_data.py` 的 transform 段
- [ ] **待定:** `transform` 的 4 个口径问题(入参类型策略、要不要收 `DataFrame`、
显式 `offset` 的校验、常量列写进 docstring)—— 见「下一步」第 1/2 条
- [x] **`transform.py` 的 4 个口径已收口**(2026-10-02,Codex 代修,用户授权"报错顺手修掉"):
- 输入白名单统一成模块级 `_NUMERIC_TYPES`,**`DataFrame` 补进去**并移到 `np.asarray` 之前
(此前 `DataFrame` 不在白名单 → `standardize(df)` / `log_transform(df)` 直接 `TypeError`,
函数体里所有 `is_df` 分支都是**死代码**)
- 三个函数都保持"DataFrame 进 DataFrame 出"(列名 + 索引原样带回),Series 进 Series 出
- `normalize` 的三个 method 分支原来**互相覆盖**(`minmax` / `vector` 算完的结果被末尾
无条件的 `x / max` 冲掉,三种 method 实际都只走 max),改成 `if / elif / else`
- `one_hot_encode` 不再拿 `data.columns` 重包一层(哑变量列名对不上 → 编码列整列丢失)
- `log_transform` 的 `offset` 守卫**写反了**(`offset > 0` 就抛错,恰好拒掉唯一合法用例),
改成校验真正的定义域前提 `min(x) + offset > 0`(见决策 35)
- docstring 按 §4.7 补齐 `Parameters` / `Returns` / `Raises`(Codex 代写,与 `io.py` 同一套 numpydoc 格式)
- 口径定稿后 ruff 4 条 RET505 一并清掉 → `All checks passed`;pyright **2 errors → 0**
- [ ] 剩余 5 项:`describe` `correlation_matrix` `bootstrap_ci` `monte_carlo` `save_figure`
### 验证结果
@@ -148,6 +158,9 @@
决策 30 那套信号(XPASS → 摘 mark)跑完了一个完整闭环
- `examples/01_clean_and_describe.py` 实测跑通(exit 0);`00_quickstart.py` 仍等 `stats` / `viz`
- 40 条 skip 是等实现的算法(`train_test_split`、`describe`、`bootstrap_ci`、`monte_carlo` 等)
- **本轮回归(已修)**:`transform.py` 上一版有 3 处真 bug —— 修前 **7 failed / 75 passed**,
且 pyright 在同一处报 `reportArgumentType`。**不是静态误报**:pyright CLI 复现 + 测试真红。
结论:见到红线先跑 CLI 与 `pytest`,**不许直接挂 `# type: ignore`**(见个人易错点 10)
- 三个 `.ps1` 通过 PowerShell AST 语法解析
### ⚠️ 需要注意 / 遗留
@@ -208,22 +221,16 @@
| 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 自动跳过 |
| 35 | `log_transform` 的显式 `offset` 按**定义域前提**校验:`min(x) + offset > 0` 才放行 |
原码写的是 `if offset > 0: raise`,把唯一合法的用例(正 offset 正好抬定义域)全拒了,报错文案却是"offset 必须大于 0",自相矛盾。改成校验真正的前提:`log` 要求自变量取正。`offset=0` 对全正数据合法(`offset=None` 的自动分支本来就取 0)。**Codex 代选、待用户确认** —— 若原意是"只收正 offset",改回即可 |
## 下一步
1. **`transform.py` 还剩 4 个口径要你拍板**(都要落到 docstring 的 `Raises` / `Notes` 里):
- **入参类型策略三个函数不一致**:`standardize` / `log_transform` 有守卫,只收
`int/float/list/tuple/ndarray/Series`;`normalize` 没有,全靠 `np.asarray` 自己崩 ——
`normalize('abc')` 抛 numpy 的 `ValueError`(不是 §4.7 要的 `TypeError`),
`normalize(None)` 更糟:**静默返回 NaN**。建议照 `standardize` 的元组抄一份守卫,三处统一。
- **要不要收 `DataFrame`**:`normalize(df)` 现在能跑,但返回 ndarray、**列名丢了**;
`standardize(df)` 直接 `TypeError`。`load_table` 出来的就是 DataFrame,
建议三个函数都收 DataFrame、按列处理、并保持 DataFrame 返回(列名留着才好回填原表)。
- **显式 `offset` 没校验**:`log_transform(x, offset=-10.0)` 静默返回 NaN(`np.log` 负数 + RuntimeWarning)。
要么校验(`x + offset <= 0` 就抛 `ValueError`),要么在 docstring 里写清"由调用方保证"。
- **常量列行为写进 docstring**:行为已被测试钉住(决策 34),但 docstring 还是空白。
2. 四个函数的 docstring 都缺 `Parameters` / `Returns` / `Raises`(§4.7 硬要求)。
上面 4 点定了之后一次补齐 —— 现在补也会因为口径未定而要返工。
3. `stats.describe` + `stats` 其余函数:`examples/00_quickstart.py` 与
`examples/01_clean_and_describe.py` 末尾的 `TODO(L0)` 都在等它。
4. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。
1. **`stats.describe` 是 L0 里下一个该写的**(A 档,`stats/descriptive.py`)——
`examples/00_quickstart.py` 与 `examples/01_clean_and_describe.py` 末尾的 `TODO(L0)` 都在等它。
写法照 `data/clean.py` 的 `clean_missing`:先想"替代了哪段手写代码" → 先写一条会红的测试
→ 再实现 → 补 `examples/` 一行(`docs/03-roadmap.md` §3.2)。
2. 之后按 L0 剩余顺序推进:`correlation_matrix`(A)→ `bootstrap_ci`(**S 档,要写函数卡片**)
→ `monte_carlo`(**S 档**)→ `save_figure`(A)。
3. L0 跑通后,找一道真题(建议 2025 C)走完整流程,反向沉淀函数。
4. **待你确认(决策 35)**:`log_transform` 的显式 `offset` 现在按"定义域前提"校验
(`min(x) + offset > 0`,否则 `ValueError`)。若你原意是"只收正 offset",说一声我改回去。