fix(stats): CV 改用 |mean|,并记录决策 37/38/39 与经验 12

coefficient_of_variation:
- 分母由 mean 改为 |mean|(用户本人改),均值为负时 CV 不再是负值
- docstring 摘要 / Returns / Notes 同步,加一条测试钉住"均值为负时 CV 仍为正"

口径拍板(2026-10-03):
- 决策 37: 非数值列的处置按输出契约分,不按包分 —— describe/zscore 丢掉(吐数值结果表),
  detect_outliers 保留列、掩码恒 False(吐与原表同形的掩码),刻意不统一
- 决策 38: CV 用 sigma/|mean|
- 决策 39: 工作约定 —— 语义/非法输入口径由用户拍板,Codex 只列选项不代选;
  ruff / STATUS.md / CHANGELOG.md / tests / docstring 这类与算法无关的工程事务 Codex 顺手补

经验(docs/dev/personal-pitfalls.md,只追加):
- 第 12 条: 三个并列的 if 让后面的静默覆盖前面的(transform.normalize 三种 method 实际只走 max)
  第 11 条(上一轮)是 descriptive 的 .loc 对齐失败静默填 NaN —— 两条的共同点是"不抛异常"

验证: pytest 103 passed / 39 skipped;ruff All checks passed;pyright 0 errors。

docs: STATUS.md 决策 37/38/39 + 下一步;CHANGELOG 记录
This commit is contained in:
guzhujushi committed 2026-10-03 13:26:13 +08:00
1 parent e9624cb3a7
commit cfc1be31ca
5 files changed
+58 -20

No files matched your search

+6 -6
View File
@@ -173,7 +173,7 @@ def zscore(x: Any, ddof: int = 0) -> Any:
def coefficient_of_variation(x: Any) -> float:
"""变异系数 `std / mean` —— 跨量纲比较"谁更波动"时的唯一正确做法。
"""变异系数 `std / |mean|` —— 跨量纲比较"谁更波动"时的唯一正确做法。
Parameters
----------
@@ -184,7 +184,7 @@ def coefficient_of_variation(x: Any) -> float:
Returns
-------
float
`std / mean`,标准差按总体口径(`ddof=0`)。
`std / |mean|`,标准差按总体口径(`ddof=0`)。恒非负。
Raises
------
@@ -195,9 +195,9 @@ def coefficient_of_variation(x: Any) -> float:
Notes
-----
CV 只有在**均值为正**的比率尺度数据上才有意义:均值为负时本函数返回负值,
那不是"波动更小",只是符号跟着均值走;均值接近 0 时 CV 会被放大到任意大。
这两类数据请换用别的离散度指标。
分母取的是 `|mean|`:均值为负时 CV 仍是正数,不会因为符号反过来被误读成"波动更小"。
但 CV 的**可比性**仍然只在均值远离 0 的比率尺度数据上成立 —— 均值接近 0 时
CV 会被放大到任意大,那种数据请换用别的离散度指标。
"""
allowed_type = (list, tuple, np.ndarray, pd.Series)
if isinstance(x, pd.DataFrame):
@@ -220,4 +220,4 @@ def coefficient_of_variation(x: Any) -> float:
if mean == 0:
raise ValueError("均值为 0,无法计算变异系数(分母为 0)")
return float(std / mean)
return np.abs(float(std / mean))