别再只盯着AUC了:用Python手把手教你绘制ROC与PR曲线(附sklearn实战代码)
实战指南用Python绘制ROC与PR曲线的完整流程与深度解析在机器学习模型评估中我们常常陷入对单一指标的盲目崇拜。AUC值固然重要但真正理解模型性能需要更立体的视角。本文将带你用Python从零开始构建完整的评估流程不仅生成曲线更教会你如何从中挖掘关键信息。1. 环境准备与数据加载在开始之前确保你的Python环境已安装以下库pip install scikit-learn matplotlib numpy pandas我们将使用scikit-learn内置的乳腺癌数据集作为示例这是一个经典的二分类问题数据集from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split data load_breast_cancer() X, y data.data, data.target X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.3, random_state42)注意实际项目中建议使用stratify参数保持类别分布一致特别是在样本不均衡时2. 模型训练与概率预测我们以随机森林为例但任何能输出概率的分类器都适用from sklearn.ensemble import RandomForestClassifier model RandomForestClassifier(n_estimators100, random_state42) model.fit(X_train, y_train) # 获取测试集的预测概率正类概率 y_scores model.predict_proba(X_test)[:, 1]关键点在于使用predict_proba而非predict因为曲线绘制需要连续的预测概率而非离散的分类结果。3. ROC曲线绘制与解读3.1 基础绘制方法使用scikit-learn的roc_curve函数计算关键指标from sklearn.metrics import roc_curve, auc fpr, tpr, thresholds roc_curve(y_test, y_scores) roc_auc auc(fpr, tpr) # 绘制基础ROC曲线 import matplotlib.pyplot as plt plt.figure(figsize(8, 6)) plt.plot(fpr, tpr, colordarkorange, lw2, labelfROC curve (area {roc_auc:.2f})) plt.plot([0, 1], [0, 1], colornavy, lw2, linestyle--) plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.title(Receiver Operating Characteristic) plt.legend(loclower right) plt.show()3.2 高级可视化技巧添加更多信息提升图表可读性plt.figure(figsize(10, 8)) # 主曲线 plt.plot(fpr, tpr, colordarkorange, lw2, labelfROC (AUC {roc_auc:.3f})) # 随机线 plt.plot([0, 1], [0, 1], k--, labelRandom (AUC 0.5)) # 标记最佳阈值点 optimal_idx np.argmax(tpr - fpr) optimal_threshold thresholds[optimal_idx] plt.scatter(fpr[optimal_idx], tpr[optimal_idx], markero, colorred, labelfOptimal Threshold ({optimal_threshold:.2f})) # 添加网格和标注 plt.grid(alpha0.3) plt.xlabel(False Positive Rate (1 - Specificity), fontsize12) plt.ylabel(True Positive Rate (Sensitivity), fontsize12) plt.title(Enhanced ROC Curve Analysis, fontsize14) plt.legend(loclower right, fontsize10) # 添加阈值标注 for i in range(0, len(thresholds), 50): plt.text(fpr[i], tpr[i], f{thresholds[i]:.2f}, fontsize8) plt.tight_layout() plt.show()4. PR曲线绘制与样本不均衡分析4.1 基础PR曲线from sklearn.metrics import precision_recall_curve precision, recall, thresholds precision_recall_curve(y_test, y_scores) pr_auc auc(recall, precision) plt.figure(figsize(8, 6)) plt.plot(recall, precision, colorblue, lw2, labelfPR curve (area {pr_auc:.2f})) plt.xlabel(Recall) plt.ylabel(Precision) plt.title(Precision-Recall Curve) plt.legend(locbest) plt.show()4.2 不均衡数据集对比人为创建不均衡数据集进行对比from sklearn.utils import resample # 创建不均衡版本 X_imb, y_imb resample(X[y1], y[y1], replaceTrue, n_samples500, random_state42) X_imb np.vstack([X_imb, X[y0][:50]]) y_imb np.concatenate([y_imb, y[y0][:50]]) # 在新数据上训练和预测 X_train_i, X_test_i, y_train_i, y_test_i train_test_split(X_imb, y_imb, test_size0.3, random_state42) model.fit(X_train_i, y_train_i) y_scores_i model.predict_proba(X_test_i)[:, 1] # 计算两种曲线 fpr_i, tpr_i, _ roc_curve(y_test_i, y_scores_i) roc_auc_i auc(fpr_i, tpr_i) precision_i, recall_i, _ precision_recall_curve(y_test_i, y_scores_i) pr_auc_i auc(recall_i, precision_i) # 绘制对比图 fig, (ax1, ax2) plt.subplots(1, 2, figsize(16, 6)) # ROC对比 ax1.plot(fpr, tpr, labelfBalanced (AUC {roc_auc:.2f})) ax1.plot(fpr_i, tpr_i, labelfImbalanced (AUC {roc_auc_i:.2f})) ax1.set_title(ROC Curve Comparison) ax1.legend() # PR对比 ax2.plot(recall, precision, labelfBalanced (AUC {pr_auc:.2f})) ax2.plot(recall_i, precision_i, labelfImbalanced (AUC {pr_auc_i:.2f})) ax2.set_title(PR Curve Comparison) ax2.legend() plt.show()5. 阈值选择策略与业务应用5.1 常用阈值选择方法方法计算公式适用场景Youden指数argmax(TPR - FPR)平衡敏感性和特异性最小距离argmin√[(1-TPR)² FPR²]理论最优点最大F1argmax(2PR/(PR))精确率和召回率平衡业务定制根据误分类成本调整特定业务需求5.2 实现代码示例from sklearn.metrics import f1_score # Youden指数法 youden_idx np.argmax(tpr - fpr) youden_threshold thresholds[youden_idx] # F1最大法 f1_scores [f1_score(y_test, y_scores t) for t in thresholds] f1_idx np.argmax(f1_scores) f1_threshold thresholds[f1_idx] print(fYouden最佳阈值: {youden_threshold:.4f}) print(fF1最大阈值: {f1_threshold:.4f}) # 可视化阈值选择 plt.figure(figsize(10, 6)) plt.plot(thresholds, tpr, labelSensitivity (TPR)) plt.plot(thresholds, 1 - fpr, labelSpecificity (1-FPR)) plt.plot(thresholds, f1_scores, labelF1 Score) plt.axvline(youden_threshold, colorred, linestyle--, labelYouden Threshold) plt.axvline(f1_threshold, colorgreen, linestyle:, labelF1 Threshold) plt.xlabel(Threshold) plt.ylabel(Metric Value) plt.title(Threshold Selection Analysis) plt.legend() plt.grid(alpha0.3) plt.show()6. 生产环境中的最佳实践在实际项目中我通常会创建这样一个评估类来封装所有功能class BinaryClassifierEvaluator: def __init__(self, model, X_test, y_test): self.model model self.X_test X_test self.y_test y_test self.y_scores model.predict_proba(X_test)[:, 1] self._compute_metrics() def _compute_metrics(self): self.fpr, self.tpr, self.roc_thresholds roc_curve(self.y_test, self.y_scores) self.roc_auc auc(self.fpr, self.tpr) self.precision, self.recall, self.pr_thresholds precision_recall_curve( self.y_test, self.y_scores) self.pr_auc auc(self.recall, self.precision) def plot_curves(self, figsize(12, 6)): fig, (ax1, ax2) plt.subplots(1, 2, figsizefigsize) # ROC曲线 ax1.plot(self.fpr, self.tpr, colordarkorange, labelfROC (AUC {self.roc_auc:.3f})) ax1.plot([0, 1], [0, 1], k--) ax1.set_xlabel(False Positive Rate) ax1.set_ylabel(True Positive Rate) ax1.set_title(ROC Curve) ax1.legend(loclower right) # PR曲线 ax2.plot(self.recall, self.precision, colorblue, labelfPR (AUC {self.pr_auc:.3f})) ax2.set_xlabel(Recall) ax2.set_ylabel(Precision) ax2.set_title(Precision-Recall Curve) ax2.legend(locbest) plt.tight_layout() return fig def find_optimal_threshold(self, methodyouden): if method youden: idx np.argmax(self.tpr - self.fpr) return self.roc_thresholds[idx] elif method f1: f1 [f1_score(self.y_test, self.y_scores t) for t in self.pr_thresholds[:-1]] idx np.argmax(f1) return self.pr_thresholds[idx] else: raise ValueError(Method must be youden or f1) def evaluate_at_threshold(self, threshold): y_pred (self.y_scores threshold).astype(int) report classification_report(self.y_test, y_pred, output_dictTrue) cm confusion_matrix(self.y_test, y_pred) return { classification_report: report, confusion_matrix: cm, threshold: threshold }使用示例evaluator BinaryClassifierEvaluator(model, X_test, y_test) fig evaluator.plot_curves() plt.show() best_threshold evaluator.find_optimal_threshold(f1) eval_results evaluator.evaluate_at_threshold(best_threshold) print(f最佳阈值: {best_threshold:.4f}) print(分类报告:) print(pd.DataFrame(eval_results[classification_report]).T)