本文详解如何在scipy.stats.mannwhitneyu中安全、可靠地处理缺失值(nan),重点介绍nan_policy参数的三种策略及其对检验结果的影响,并结合药物敏感性分析等真实场景提供可复用的代码范式。
本文详解如何在scipy.stats.mannwhitneyu中安全、可靠地处理缺失值(nan),重点介绍nan_policy参数的三种策略及其对检验结果的影响,并结合药物敏感性分析等真实场景提供可复用的代码范式。
在使用Mann-Whitney U检验进行两独立样本比较(如“耐药 vs 敏感”肿瘤的logIC50差异分析)时,临床数据中常见的NaN值若未被显式处理,极易导致检验失败或结果失真——这正是你遇到“结果不佳”的核心原因。scipy.stats.mannwhitneyu默认采用nan_policy='propagate',即只要任一输入数组含NaN,函数直接返回NaN统计量与p值,而不会抛错、也不会自动忽略,极易造成静默失效(silent failure),使后续统计推断完全失效。
✅ 正确做法:显式指定 nan_policy='omit'
该选项会自动过滤掉两组样本中所有NaN观测,仅基于有效数据执行秩和检验,逻辑等价于手动执行:
resistant_log_ic50 = resistant_log_ic50.dropna() sensitive_log_ic50 = sensitive_log_ic50.dropna()
但更简洁、安全且与SciPy内部实现一致。修改你的原代码如下:
from scipy.stats import mannwhitneyu
import numpy as np
p_values = {}
significance_level = 0.05
for drug in logIC50_df.columns:
# 提取对应分组数据(注意:此处假设test_dr_new.iloc[:,0]与logIC50_df索引对齐)
mask_resistant = test_dr_new.iloc[:, 0] == "Resistant"
mask_sensitive = test_dr_new.iloc[:, 0] == "Sensitive"
resistant_log_ic50 = logIC50_df.loc[mask_resistant, drug].dropna().to_numpy()
sensitive_log_ic50 = logIC50_df.loc[mask_sensitive, drug].dropna().to_numpy()
# 关键:显式设置 nan_policy='omit',并验证样本量
if len(resistant_log_ic50) == 0 or len(sensitive_log_ic50) == 0:
print(f"Warning: {drug} has insufficient data in one group after NaN removal.")
p_values[drug] = np.nan
continue
try:
stat, p_val = mannwhitneyu(
resistant_log_ic50,
sensitive_log_ic50,
alternative='greater',
nan_policy='omit' # ← 必须显式声明!
)
p_values[drug] = p_val
print(f"P-value for {drug}: {p_val:.4f} (n_res={len(resistant_log_ic50)}, n_sen={len(sensitive_log_ic50)})")
except ValueError as e:
print(f"Error in {drug}: {e}")
p_values[drug] = np.nan
# 后续统计
num_significant = sum(1 for p in p_values.values() if p is not None and not np.isnan(p) and p < significance_level)
print(f"Number of drugs with statistically significant discrimination: {num_significant}")⚠️ 注意事项与最佳实践
- 绝不依赖默认行为:nan_policy='propagate'是默认值,但其“返回NaN”特性在循环中极易被忽略,导致p_values字典混入NaN却无报错;
- 务必校验样本量:nan_policy='omit'虽自动剔除NaN,但若某组剔除后样本量 ≤ 1,则mannwhitneyu会抛出ValueError(因秩和检验至少需每组2个有效观测)。建议前置检查;
- 避免隐式类型转换陷阱:Pandas Series经.dropna()后仍为Series,SciPy 1.12+虽支持,但为兼容性及明确性,推荐转为numpy.ndarray(.to_numpy());
- 单侧检验方向需语义严谨:alternative='greater' 意味着检验“resistant组的分布位置是否显著大于sensitive组”(即logIC50更高 → 耐药性更强),此设定与你的科学假设一致,无需调整;
- 补充探索性诊断:建议在循环中打印每组有效样本量(如示例代码中的n_res/n_sen),快速识别数据稀疏问题。
? 总结
处理NaN不是“技术细节”,而是统计稳健性的基石。nan_policy='omit'是面向真实生物医学数据(高缺失率、小样本)的首选策略,但必须配合显式调用、样本量校验与结果日志,才能确保A/B对比结论可信。记住:没有被看见的NaN,才是最危险的NaN。

















