fix(stats): correlation_matrix 修硬 bug,两处 pyright 报错按坑 8/10 正经修(无 ignore)
用户报的是"63 行 p 值赋值报错"。查下来:63 行运行完全正常(p 是 np.float64),
pyright 那条是 scipy 存根的缺陷。但顺着看发现了真正跑不起来的第 41 行。
correlation_matrix:
- 硬 bug: `if numeric_cols == 0` 拿 Index 和标量比 -> 得到布尔数组。
0 列时静默不报错、1 列时碰巧能跑、>=2 列直接 ValueError 崩(而 >=2 列正是常见情况)。
改成 len(numeric_cols) == 0,并加回归用例
- pyright 两处报错都不是运行时问题,且一处 # type: ignore 都没挂:
- data[numeric_cols] 是坑 8 的 DataFrame | Series 重载 -> cast 收窄
- `_, p = stats.pearsonr(...)` 解包退化成 object(scipy 的返回值是 _make_tuple_bunch
运行时拼的类,存根里没有 pvalue)-> 新增 _pvalue(result: Any) -> float 小助手统一取,
后面 t_test / anova / chi_square_test 可复用
- method 校验提到最前;add_stars 的 elif 链改 if;清掉 3 条 F541 与 1 条 I001,ruff 归零
- 顺带记录两处待定:对角线被标了 ***;含 NaN 时 corr 成对删失而 pearsonr 不删,r 与 p 对不上
tests:
- test_correlation_matrix_diagonal_is_one 由 skip 改成 xfail(strict=False)(决策 30):
契约要数值矩阵、实现返回带星标字符串表,口径待拍板
- 新增 2 条:多列数值表能跑通、非法输入报错
验证: pytest 105 passed / 38 skipped / 1 xfail;ruff All checks passed;pyright 0 errors。
docs: STATUS.md(correlation_matrix 进行中 + 决策 40 + 下一步 A/B/C 选项);CHANGELOG;
personal-pitfalls 追加第 13 条(Index == 标量 得到布尔数组);
docs/04-environment.md 追加坑 10(scipy 检验结果的存根缺陷)
This commit is contained in:
1 parent
cfc1be31ca
commit
3432bf3e50
6 files changed
+215
-24
No files matched your search
@@ -7,7 +7,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import scipy.stats as stats
|
||||
|
||||
__all__ = [
|
||||
"anova",
|
||||
@@ -20,20 +24,98 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
def correlation_matrix(data: Any, method: str = "pearson", min_periods: int = 1) -> Any:
|
||||
def _pvalue(result: Any) -> float:
|
||||
"""把 scipy 检验结果取成 p 值(float)。
|
||||
|
||||
scipy 的 `SignificanceResult` / `PearsonRResult` 是 `_make_tuple_bunch` 在**运行时**
|
||||
拼出来的类,类型检查器看不到 `pvalue` 字段 —— 这里显式收成 `Any` 再取,
|
||||
比给每一处调用挂 `# type: ignore` 干净,后面 `t_test` / `anova` 也能复用。
|
||||
"""
|
||||
return float(result.pvalue)
|
||||
|
||||
|
||||
def correlation_matrix(
|
||||
data: Any,
|
||||
method: Literal["pearson", "kendall", "spearman"] = "pearson",
|
||||
min_periods: int = 1,
|
||||
) -> Any:
|
||||
"""相关矩阵 + 显著性星标。
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data:
|
||||
必须是 `pandas.DataFrame`。**只有数值列参与**,非数值列自动跳过。
|
||||
method:
|
||||
`"pearson"` / `"kendall"` / `"spearman"`。
|
||||
min_periods:
|
||||
每对变量至少要有这么多个**共同非缺失**的观测才给相关系数,透传给 `DataFrame.corr`。
|
||||
|
||||
Returns
|
||||
-------
|
||||
DataFrame,值 = 相关系数;`min_periods` 控制缺失值容差。
|
||||
⚠️ 当前实现返回的是**带星标的字符串表**(`1.000***` 这种),与上面这句契约不一致 ——
|
||||
口径待定,见 `STATUS.md`。
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
`data` 不是 `pandas.DataFrame`。
|
||||
ValueError
|
||||
`data` 里没有数值列,或 `method` 不在支持列表内。
|
||||
|
||||
Notes
|
||||
-----
|
||||
星标读法:`***` p < 0.001,`**` p < 0.01,`*` p < 0.05,其余不带星。
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> correlation_matrix(df).round(3) # doctest: +SKIP
|
||||
"""
|
||||
# TODO(L0)
|
||||
raise NotImplementedError
|
||||
if not isinstance(data, pd.DataFrame):
|
||||
raise TypeError(f"data必须是 pd.DataFrame,而你传的是{type(data)}")
|
||||
|
||||
valid_method = ["pearson", "kendall", "spearman"]
|
||||
if method not in valid_method:
|
||||
raise ValueError(f"不支持的方法:{method},仅支持{valid_method}")
|
||||
|
||||
numeric_cols = data.select_dtypes(include=[np.number]).columns
|
||||
if len(numeric_cols) == 0:
|
||||
raise ValueError("没有找到数值列")
|
||||
# data[Index] 在 pandas 存根里是 DataFrame | Series 重载,按运行时真值收窄(坑 8)
|
||||
df_num = cast(pd.DataFrame, data[numeric_cols])
|
||||
|
||||
corr_matrix = df_num.corr(method=method, min_periods=min_periods)
|
||||
|
||||
p_values = pd.DataFrame(0.0, index=corr_matrix.index, columns=corr_matrix.columns, dtype=float)
|
||||
for i in range(len(corr_matrix.columns)):
|
||||
for j in range(len(corr_matrix.columns)):
|
||||
if i == j:
|
||||
continue
|
||||
if method == "pearson":
|
||||
p = _pvalue(stats.pearsonr(df_num.iloc[:, i], df_num.iloc[:, j]))
|
||||
elif method == "kendall":
|
||||
p = _pvalue(stats.kendalltau(df_num.iloc[:, i], df_num.iloc[:, j]))
|
||||
else:
|
||||
p = _pvalue(stats.spearmanr(df_num.iloc[:, i], df_num.iloc[:, j]))
|
||||
p_values.iloc[i, j] = p
|
||||
|
||||
def add_stars(val: float, p: float) -> str:
|
||||
# 保留三位小数
|
||||
val_str = f"{val:.3f}"
|
||||
if p < 0.001:
|
||||
return val_str + "***"
|
||||
if p < 0.01:
|
||||
return val_str + "**"
|
||||
if p < 0.05:
|
||||
return val_str + "*"
|
||||
return val_str
|
||||
|
||||
formatted_corr = corr_matrix.copy().astype(str)
|
||||
for i in range(len(corr_matrix)):
|
||||
for j in range(len(corr_matrix)):
|
||||
formatted_corr.iloc[i, j] = add_stars(corr_matrix.iloc[i, j], p_values.iloc[i, j])
|
||||
|
||||
return formatted_corr
|
||||
|
||||
def confidence_interval(
|
||||
x: Any,
|
||||
|
||||
Reference in new issue
Block a user