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:
1 parent
b6dc4bc5a6
commit
5749e50e83
8 files changed
+344
-51
No files matched your search
+35
-5
@@ -238,14 +238,43 @@ def test_detect_outliers_rejects_unknown_method():
|
||||
detect_outliers([1.0, 2.0], method="bogus") # type: ignore[arg-type]
|
||||
|
||||
|
||||
# ------------------- 契约先写进 docstring,实现逐条跟上(剩余 1 条 xfail)
|
||||
def test_detect_outliers_rejects_bad_type():
|
||||
"""§4.7:类型不对必须抛 TypeError,不能让它一路飘进 pandas 里去。"""
|
||||
from mcm_toolkit.data import detect_outliers
|
||||
|
||||
for bad in (42, 3.14, "123", None, {"a": [1, 2]}):
|
||||
with pytest.raises(TypeError):
|
||||
detect_outliers(bad)
|
||||
|
||||
|
||||
def test_detect_outliers_survives_degenerate_columns():
|
||||
"""全 NaN 列与单元素列都不能抛异常,判定一律为 False。"""
|
||||
from mcm_toolkit.data import detect_outliers
|
||||
|
||||
df = pd.DataFrame({"all_nan": [np.nan, np.nan, np.nan], "one": [1.0, 2.0, 3.0]})
|
||||
assert not bool(np.asarray(detect_outliers(df)).any())
|
||||
assert not bool(np.asarray(detect_outliers([5.0])).any())
|
||||
assert not bool(np.asarray(detect_outliers([5.0], method="zscore")).any())
|
||||
|
||||
|
||||
def test_detect_outliers_accepts_tuple_as_1d():
|
||||
"""元组按一维数组型处理,返回值与传 list 一致。"""
|
||||
from mcm_toolkit.data import detect_outliers
|
||||
|
||||
mask = np.asarray(detect_outliers((1.0, 2.0, 3.0, 4.0, 1000.0)))
|
||||
assert mask.shape == (5,)
|
||||
assert mask.dtype == bool
|
||||
assert bool(mask[-1])
|
||||
|
||||
|
||||
# ------------------------ 曾经 xfail 的契约,现已全部实现(留作回归)
|
||||
#
|
||||
# 这些用例写的是**文档里已经承诺的行为**。实现没跟上时用 xfail,让它们照样出现在
|
||||
# 报告里(显示成 x);修好之后会自动变成 XPASS —— 看到 XPASS 就把 mark 摘掉。
|
||||
#
|
||||
# 已摘掉:threshold 范围校验、IQR 的 UnboundLocalError、(n,1) 形状、zscore 默认阈值、
|
||||
# winsorize 的文本列、detect_outliers 的文本列。剩 1 条不是漏改,是**设计还没定**:
|
||||
# - impute_missing 的 mode 分支该不该就地改调用方的表(其余分支都返回新表)
|
||||
# winsorize 的文本列、detect_outliers 的文本列、impute_missing 的 mode 就地修改。
|
||||
# **当前 0 条 xfail** —— 以后新增契约照旧:先写测试 + xfail,实现跟上就摘 mark。
|
||||
|
||||
|
||||
def test_detect_outliers_finds_planted_points():
|
||||
@@ -315,12 +344,13 @@ def test_clean_missing_rejects_out_of_range_threshold():
|
||||
clean_missing(pd.DataFrame({"a": [1.0, np.nan]}), threshold=1.5)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="mode 分支在调用方的表上就地改,其余分支都返回新表", strict=False)
|
||||
def test_impute_missing_mode_does_not_mutate_input():
|
||||
"""mode 分支也必须返回新表:不能顺手把调用方的表改了。"""
|
||||
from mcm_toolkit.data import impute_missing
|
||||
|
||||
df = pd.DataFrame({"a": [1.0, np.nan, 1.0]})
|
||||
impute_missing(df, method="mode")
|
||||
out = impute_missing(df, method="mode")
|
||||
assert out["a"].tolist() == [1.0, 1.0, 1.0]
|
||||
assert bool(df["a"].isna().any())
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user