refactor(core): 统一入口 as_array —— 六处手抄的入参处理收成一处(决策 44/45)
## 新增 - `core.validate.as_array(x, *, numeric_only=True, dtype=float, ndim=None) -> (arr, kind)`: 一次做完"类型白名单 + 转 ndarray + 报告原来是什么容器"。`kind` 是 `"frame"` / `"series"` / `"array"`。 - **还原不代做**(用户拍的板):`describe` / `correlation_*` / `confidence_interval` / `t_test` 的 输出天生与输入不同形,统一还原只会重演易错点 14;所以只报告 `kind`,调用方自己手写三行。 - `core/__init__.py` 导出 `as_array`;`check_array`(L1 未实现)的 docstring 注明实现时复用它。 - 入口三条硬规矩:**默认绝不摊平**(要摊平显式 `ndim=1`);**不删缺失值**; `numeric_only` 按"输出是否与输入同形"选,不按喜好选。 ## 去重 - `normalize` / `standardize` / `log_transform` / `zscore` / `confidence_interval` / `t_test` 六处手抄的 `isinstance(x, pd.DataFrame)` / `select_dtypes` / `np.asarray` 全部删掉,各剩 1 行入口。 - `data/transform.py` 的 `_NUMERIC_TYPES` 白名单连同 3 份副本一起删除 —— 这类判断原来散在 **5 个文件 31 处**。 - `transform` 的三个函数走 `numeric_only=False`:它们的输出必须与输入同形,跳过非数值列会少列。 ## 修掉 - `zscore` / `normalize` / `standardize` / `log_transform` 的 Series 分支**补回 `name`** —— 此前 `pd.Series(result, index=...)` 会把 `name` 丢掉,管道里 `concat` 会变成无名列。 - "容器对、内容转不成 float"的错误类型,由 numpy 的 `ValueError` 统一成 `TypeError`(决策 45,**待确认**)。 ## 测试与文档 - `tests/test_core.py` 新增 `TestAsArray` 8 条:五种容器的 kind、默认只取数值列、 `numeric_only=False` 遇文本报错、默认不摊平、`ndim=2` 只校验不重塑、白名单外一律 TypeError、 **保留 NaN**、`dtype=None` 保持原 dtype;`tests/test_stats.py` 新增 1 条(Series 保住 index/name)。 - 全量 **171 条:134 passed / 37 skipped / 0 xfail**;ruff 0,pyright 0;`examples/01` 跑通(exit 0)。 - `docs/02-architecture.md` 新增 §4.8「入口只有一个:`core.validate.as_array`」,目录树补 `as_array`; `docs/03-roadmap.md` 的 L1 表下补说明;`docs/dev/personal-pitfalls.md` 新增第 16 条并把索引补到 16。 - `STATUS.md`:决策 44 / 45,E 节记 as_array,「下一步」第 7 条列 3 个待拍板口径。 ## 待用户拍板(Codex 一律未代选) - `int` / `float` 标量是否继续留在白名单(现在会产出 0 维数组,`axis=0` 的算法照样炸)。 - "内容转不成 float" 该用 `TypeError`(现状)还是 `ValueError`。 - 要不要让 `as_array` 顺带返回选中的列(改三元组),省掉 `frame` 分支重写 `select_dtypes`。
This commit is contained in:
1 parent
f95fbd2726
commit
b70a96dd0c
12 files changed
+356
-101
No files matched your search
+27
-1
@@ -24,7 +24,7 @@ src/mcm_toolkit/
|
||||
├── core/ ① 基础设施 —— 所有包的地基
|
||||
│ ├── result.py Result
|
||||
│ ├── random.py set_seed / get_rng
|
||||
│ └── validate.py check_array / check_same_length / check_probability / check_square
|
||||
│ └── validate.py as_array(统一入口)/ check_array / check_same_length / check_probability / check_square
|
||||
│
|
||||
├── data/ ② 数据进出 —— 最高频
|
||||
│ ├── io.py load_table / save_table
|
||||
@@ -183,6 +183,32 @@ docstring 给人看。两处打架时,合法用法会被标红,真正的错
|
||||
|
||||
参考实现:`core/random.py` 的 `get_rng`。
|
||||
|
||||
### 4.8 入口只有一个:`core.validate.as_array`
|
||||
|
||||
**凡是要数值数组的函数,第一行都从 `as_array` 进**,不要各自抄
|
||||
`isinstance(x, pd.DataFrame)` / `select_dtypes` / `np.asarray`:
|
||||
|
||||
```python
|
||||
arr, kind = as_array(x, numeric_only=False) # 入口:白名单 + 转 ndarray + 报告原类型
|
||||
... # 中间只写算法
|
||||
if kind == "frame": # 出口:自己手写,各函数形状不同,没法一刀切
|
||||
return pd.DataFrame(result, index=x.index, columns=x.columns)
|
||||
if kind == "series":
|
||||
return pd.Series(result, index=x.index, name=x.name)
|
||||
return result
|
||||
```
|
||||
|
||||
三条硬规矩:
|
||||
|
||||
1. **`numeric_only` 按"输出契约"选,不按喜好选**:输出要与输入**同形**的函数
|
||||
(`normalize` / `standardize` / `log_transform`)必须 `False` —— 跳过非数值列会让返回的表少列、
|
||||
对不上原表;输出是"一张数值结果表"的(`zscore` / `confidence_interval` / `t_test`)用默认的 `True`。
|
||||
2. **默认不摊平**。要摊平必须显式 `ndim=1`(`t_test` 曾因静默摊平把 `gdp` 和 `pop` 混成一个样本)。
|
||||
3. **`as_array` 不删缺失值**。`df.corr` 成对删、`describe` 整列统计、`paired=True` 的 t 检验必须**成对**删 ——
|
||||
在这一层删掉,行与行的对应关系就永久丢了。删缺失是**语义**,归各函数。
|
||||
|
||||
只走入口、不走出口的(输出与输入不同形):`describe`、`correlation_*`、`confidence_interval`、`t_test`。
|
||||
|
||||
---
|
||||
|
||||
## 五、测试策略
|
||||
|
||||
Reference in new issue
Block a user