Python案例:如何平衡定性判断与定量分析
在真实的数据科学/AI项目中,纯定量容易"精准地犯错",纯定性容易"有道理但没证据",下面用一个完整的Python案例演示平衡思路。

核心框架:定量打底 + 定性纠偏 + 再验证
定性假设 → 定量建模 → 定性诊断 → 定量迭代 → 综合决策
↑ ↓
└──────────── 领域知识反馈 ──────────────────┘
| 阶段 | 定量贡献 | 定性贡献 |
|---|---|---|
| 问题定义 | 指标口径 | 业务意义、约束 |
| 特征工程 | 相关性/IV值 | 因果合理性 |
| 建模 | 精度指标 | 是否可解释 |
| 评估 | AUC/RMSE | 是否有偏见、可落地 |
| 决策 | 阈值优化 | 风险偏好 |
案例:用户流失预警与挽留决策
背景:某SaaS公司想预测用户流失,并决定"给谁发优惠券"。
Step 1 定量:先建立基线模型
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score, precision_recall_curve
df = pd.read_csv("users.csv")
X = df.drop(columns=["churn"])
y = df["churn"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, stratify=y, random_state=42)
model = GradientBoostingClassifier(random_state=42).fit(X_tr, y_tr)
proba = model.predict_proba(X_te)[:, 1]
print("AUC =", roc_auc_score(y_te, proba))
# AUC = 0.87 —— 定量上"看起来不错"
到这里很多人就停了,但 AUC 高 ≠ 决策对。
Step 2 定性:质疑模型的三个问题
# 1) 特征重要性是否"讲得通"? imp = pd.Series(model.feature_importances_, index=X.columns).sort_values(ascending=False) print(imp.head(8))
假如输出里出现 app_version 是头号特征——业务上荒谬(版本号不该决定流失)。
这说明数据泄漏或过拟合,需要定性判断剔除。
# 2) 分群评估:整体AUC掩盖了什么?
from sklearn.metrics import roc_auc_score
for seg in ["enterprise", "smb", "individual"]:
m = df.loc[X_te.index, "segment"] == seg
print(seg, roc_auc_score(y_te[m], proba[m]))
# enterprise: 0.91 smb: 0.85 individual: 0.62
个体用户预测几乎没用——定性上必须承认:对个体用户不能自动化决策。
# 3) 群体公平性检查
for g in df.loc[X_te.index, "region"].unique():
m = df.loc[X_te.index, "region"] == g
print(g, "预测为正比例:", (proba[m] > 0.5).mean())
Step 3 定量:把定性约束编码进目标
定性得出的结论要变成可计算的约束,而不是嘴上说说。
约束1:决策要"成本×收益"最优,不是AUC最优
# 定性:挽留成本(券) vs 流失损失(CLV)
COST_COUPON = 20 # 发券成本
LOSS_CHURN = 300 # 流失损失
# 只有预期收益>成本才发券
def expected_profit(p, clv=LOSS_CHURN, cost=COST_COUPON):
return p * (clv - cost) - (1 - p) * cost
proba_all = model.predict_proba(X_te)[:, 1]
profit = expected_profit(proba_all)
print("最优阈值 =", proba_all[profit.argmax()], "总收益 =", profit.max())
约束2:控制误伤率(定性风险偏好)
# 定性:营销部要求"误伤非流失用户不超过10%"
prec, rec, thr = precision_recall_curve(y_te, proba_all)
# 反查最保守的阈值
mask = (1 - prec[:-1]) <= 0.10 # 1-precision = 误伤率
thr_safe = thr[mask].max()
print("安全阈值 =", thr_safe)
约束3:分群阈值(承认个体不可靠)
thresholds = {"enterprise": 0.4, "smb": 0.5, "individual": 0.75} # 个体更保守
segment = df.loc[X_te.index, "segment"].values
final_decision = np.array([
proba_all[i] > thresholds[segment[i]] for i in range(len(segment))
])
Step 4 定性:把结果翻译回业务语言
report = pd.DataFrame({
"user_id": X_te.index,
"segment": segment,
"churn_prob": proba_all,
"send_coupon": final_decision
})
print(report.groupby("segment")["send_coupon"].mean())
# enterprise 0.18 smb 0.25 individual 0.03
然后和业务方一起评审:
- "个体用户几乎没有触发,符合预期——数据不够,不硬推。"
- "企业用户阈值最低,因为CLV高,值得多补贴。"
- "需要上线小流量AB测试(定性+定量再验证)。"
# Step 5:AB实验的定量再验证
def ab_test(control_rate, treat_rate, n=5000, alpha=0.05):
from scipy.stats import norm
se = np.sqrt(control_rate*(1-control_rate)/n + treat_rate*(1-treat_rate)/n)
z = (treat_rate - control_rate) / se
return z, 2 * (1 - norm.cdf(abs(z))) # z值、p值
print(ab_test(control_rate=0.05, treat_rate=0.08)) # 挽留率是否显著提升
可复用的平衡清单
| 何时偏定量 | 何时偏定性 |
|---|---|
| 指标可测量、样本足 | 涉及价值判断、伦理 |
| 探索性分析、压缩特征 | 特征解释、特征挑选 |
| 模型对比、超参搜索 | 阈值、成本、权重设定 |
| AB验证效果 | 决定要不要上线 |
三条铁律:
- 定量先行,但不孤立 —— 任何指标都要问"业务上代表什么"。
- 定性必须可计算化 —— "要公平"→ 编码成损失函数或约束,否则无法落地。
- 决策点交给定性,计算点交给定量 —— 阈值、成本、优先级 = 定性;搜索、评估、优化 = 定量。
常见反模式(踩坑清单)
| 反模式 | 症状 | 修正 |
|---|---|---|
| 唯AUC论 | 模型高AUC但决策亏钱 | 用期望收益替代 |
| 忽略分群 | 整体好、子群崩 | 分群评估+分群阈值 |
| 特征无意义 | 重要性榜出现字段ID | 定性审查特征 |
| 一定上线 | 无AB、无回滚 | 灰度实验+监控 |
| 阈值拍脑袋 | 5 一刀切 | 反查成本/风险 |
如果你有具体的Python项目(风控、推荐、医疗、金融等),把场景发给我,我可以按这套框架帮你把定性和定量具体落到代码里。