chore: 补 S 档卡片 / examples;clean.py 收尾,xfail 清零

实现收尾(Codex 代写,用户授权):
- impute_missing 的 mode 分支改为返回新表(原先就地改调用方的 DataFrame,另外四支都返回新对象)
- detect_outliers 补入参类型校验(§4.7:非法类型抛 TypeError,不静默兜底):
  接受 DataFrame / Series / list / tuple / ndarray;tuple 同时补进 is_ld_array;
  dict 不再被接受(以前靠 pd.DataFrame() 碰巧能过,见决策 33)

测试:
- 摘掉最后一条 xfail(mode 就地改)并加强断言
- 新增 3 条:非法类型抛 TypeError、元组按一维数组型处理、全 NaN 列与单元素列不抛异常
- 64 passed / 42 skipped / 0 xfailed(xfail 清零,7 条契约全部转正);ruff 0;pyright 0

文档:
- 新增 docs/algorithms/detect_outliers.md —— 第一张 S 档函数卡片(roadmap 要求约 15 张)
  除模板九节外多记三条判断:为什么不返回 Result、非数值列恒 False 的两条备选路各错在哪、
  ddof 口径与 stats.zscore 不一致(前者 1、后者 0,小样本差一成)
- 新增 examples/01_clean_and_describe.py —— 六个 data 函数的最小用法,实测跑通(exit 0)
- STATUS.md:E 节收尾、验证结果表、下一步重写(transform/standardize 的待定项)、决策 33
- CHANGELOG.md:把 [Unreleased] 里重复 7 次的 Added/Changed 小节合并成 Added/Changed/Notes;
  并修掉上轮遗留的缺陷 —— pyright 那条 bullet 的开头被吞掉,半句挂在 ruff 那条后面
This commit is contained in:
guzhujushi committed 2026-10-02 15:48:12 +08:00
1 parent b6dc4bc5a6
commit 5749e50e83
8 files changed
+344 -51

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
"""data 包最小用法:读 → 清 → 补 → 判离群 → 缩尾。
每一步都对应 `tests/test_data.py` 里的一条用例;体量刻意压到最小。
`stats.describe`(描述统计)还没实现,最后留了 TODO(L0) 的位置。
"""
from __future__ import annotations
import tempfile
from pathlib import Path
import numpy as np
import pandas as pd
import mcm_toolkit as mcm
def main() -> None:
mcm.set_seed(20260101)
# 1) 假装这是从 Excel 导出的赛题数据:带文本列、带缺失、带两个录入错误
df = pd.DataFrame(
{
"city": ["A", "B", "C", "D", "E", "F"],
"gdp": [1000.0, 1100.0, np.nan, 1300.0, 1400.0, 9999.0],
"pop": [10.0, 11.0, np.nan, np.nan, 20.0, 15.0],
"notes": ["ok", "ok", np.nan, "ok", np.nan, "异常"],
}
)
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "demo.csv"
# 2) 写盘再读回:load_table 按扩展名分派,.csv 会自动探测编码
mcm.data.save_table(df, path)
loaded = mcm.data.load_table(path)
print(f"读回 {loaded.shape[0]} 行 × {loaded.shape[1]} 列")
# 3) 缺失太多的行先删:缺失比例 > 0.5 的走人(4 列,阈值 0.5 -> 至少要 2 个非空)
kept = mcm.data.clean_missing(loaded, axis=0, threshold=0.5)
print(f"按缺失比例删行:{loaded.shape[0]} -> {kept.shape[0]}")
# 4) 剩下的缺失做填补。mean 只作用于数值列,notes 列原样保留缺失
filled = mcm.data.impute_missing(kept, method="mean")
print("填补后每列缺失数:", filled.isna().sum().to_dict())
# 5) 找离群点:只标不删,删不删由人决定
mask = mcm.data.detect_outliers(filled, method="iqr", k=1.5)
print("检出离群点:")
print(mask)
print(f"共 {int(mask.sum().sum())} 个;notes 列恒 False(非数值列不参与统计)")
# 6) 不删也能用:缩尾把极端值压到分位点上,而不是丢掉整行
w = mcm.data.winsorize(filled, lower=0.01, upper=0.99)
print(f"gdp 最大值 {filled['gdp'].max():.1f} -> 缩尾后 {w['gdp'].max():.1f}")
# TODO(L0):stats.describe(filled[["gdp", "pop"]]) 还没实现,实现后在这里加一段描述统计。
if __name__ == "__main__":
main()
+3
View File
@@ -4,6 +4,9 @@
> **当前状态**:这些脚本是**用法规格**,函数实现填好之前跑不通
> (会抛 `NotImplementedError`)。实现顺序见 `docs/03-roadmap.md`。
>
> `00_quickstart.py` 与 `01_clean_and_describe.py` 用到的函数大多已实现,可以跑;
> 两者末尾各有 `TODO(L0)`,等 `stats` / `viz` 补齐。
| 文件 | 对应模块 | 覆盖的 L 档 |
|---|---|---|