@浙大疏锦行
一、前置代码
# 先运行之前预处理好的代码 import pandas as pd import pandas as pd #用于数据处理和分析,可处理表格数据。 import numpy as np #用于数值计算,提供了高效的数组操作。 import matplotlib.pyplot as plt #用于绘制各种类型的图表 import seaborn as sns #基于matplotlib的高级绘图库,能绘制更美观的统计图形。 import warnings warnings.filterwarnings("ignore") # 设置中文字体(解决中文显示问题) plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体 plt.rcParams['axes.unicode_minus'] = False # 正常显示负号 data = pd.read_csv('E:\study\PythonStudy\python60-days-challenge-master\data.csv') #读取数据 # 先筛选字符串变量 discrete_features = data.select_dtypes(include=['object']).columns.tolist() # Home Ownership 标签编码 home_ownership_mapping = { 'Own Home': 1, 'Rent': 2, 'Have Mortgage': 3, 'Home Mortgage': 4 } data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping) # Years in current job 标签编码 years_in_job_mapping = { '< 1 year': 1, '1 year': 2, '2 years': 3, '3 years': 4, '4 years': 5, '5 years': 6, '6 years': 7, '7 years': 8, '8 years': 9, '9 years': 10, '10+ years': 11 } data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping) # Purpose 独热编码,记得需要将bool类型转换为数值 data = pd.get_dummies(data, columns=['Purpose']) data2 = pd.read_csv("E:\study\PythonStudy\python60-days-challenge-master\data.csv") # 重新读取数据,用来做列名对比 list_final = [] # 新建一个空列表,用于存放独热编码后新增的特征名 for i in data.columns: if i not in data2.columns: list_final.append(i) # 这里打印出来的就是独热编码后的特征名 for i in list_final: data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名 # Term 0 - 1 映射 term_mapping = { 'Short Term': 0, 'Long Term': 1 } data['Term'] = data['Term'].map(term_mapping) data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列 continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist() #把筛选出来的列名转换成列表 # 连续特征用中位数补全 for feature in continuous_features: mode_value = data[feature].mode()[0] #获取该列的众数。 data[feature].fillna(mode_value, inplace=True) #用众数填充该列的缺失值,inplace=True表示直接在原数据上修改。 # 最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,你如果想要不交叉反而实现起来会麻烦很多 # 所以这里我们还是只划分一次数据集 from sklearn.model_selection import train_test_split X = data.drop(['Credit Default'], axis=1) # 特征,axis=1表示按列删除 y = data['Credit Default'] # 标签 # 按照8:2划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 80%训练集,20%测试集 from sklearn.ensemble import RandomForestClassifier #随机森林分类器 from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 用于评估分类器性能的指标 from sklearn.metrics import classification_report, confusion_matrix #用于生成分类报告和混淆矩阵 import warnings #用于忽略警告信息 warnings.filterwarnings("ignore") # 忽略所有警告信息 # --- 1. 默认参数的随机森林 --- # 评估基准模型,这里确实不需要验证集 print("--- 1. 默认参数随机森林 (训练集 -> 测试集) ---") rf_model = RandomForestClassifier(random_state=42) rf_model.fit(X_train, y_train) # 在训练集上训练 rf_pred = rf_model.predict(X_test) # 在测试集上预测 print("\n默认随机森林 在测试集上的分类报告:") print(classification_report(y_test, rf_pred)) print("默认随机森林 在测试集上的混淆矩阵:") print(confusion_matrix(y_test, rf_pred))二、ROC曲线
from sklearn.metrics import roc_curve, auc import matplotlib.pyplot as plt # 预测测试集上的概率(注意:ROC曲线需要的是预测概率,而不是最终的类别预测) # predict_proba() 返回一个 n_samples x n_classes 的数组,我们需要正类(1)的概率,即第二列 rf_pred_proba = rf_model.predict_proba(X_test)[:, 1] # 1. 计算 ROC 曲线的 FPR 和 TPR # y_test 是真实标签,rf_pred_proba 是正类的预测概率 fpr, tpr, thresholds = roc_curve(y_test, rf_pred_proba) # 2. 计算 AUC (Area Under the Curve) roc_auc = auc(fpr, tpr) # 3. 绘制 ROC 曲线 plt.figure(figsize=(8, 6)) plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC curve (area = %0.4f)' % roc_auc) plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', label='Random Guess') # 对角线 plt.xlim([0.0, 1.0]) # 设置 x 轴范围 plt.ylim([0.0, 1.05]) # 设置 y 轴范围,稍微超出 1.0 plt.xlabel('False Positive Rate (FPR)') # x 轴标签 plt.ylabel('True Positive Rate (TPR)') # y 轴标签 plt.title('Receiver Operating Characteristic (ROC) Curve') # 图表标题 plt.legend(loc="lower right") # 图例位置 plt.grid(True) # 显示网格 plt.show() print("\n模型在测试集上的 AUC (Area Under the Curve): %0.4f" % roc_auc) import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import roc_curve, auc # 设置中文字体 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False def plot_distribution_and_roc(mu_neg, mu_pos, title_prefix): # --- 1. 模拟数据 (完全沿用之前的逻辑) --- # 负样本分布 (好人,分数低) n_neg = 10000 neg_scores = np.random.normal(loc=mu_neg, scale=0.15, size=n_neg) # 正样本分布 (坏人,分数高) n_pos = 1000 pos_scores = np.random.normal(loc=mu_pos, scale=0.15, size=n_pos) # 合并 y_true = np.array([0] * n_neg + [1] * n_pos) y_scores = np.concatenate([neg_scores, pos_scores]) # 归一化 y_scores = (y_scores - y_scores.min()) / (y_scores.max() - y_scores.min()) # --- 2. 计算 ROC 曲线的关键差异点 --- # 这里的输入和 PR 曲线一模一样,但输出变成了 FPR 和 TPR fpr, tpr, thresholds = roc_curve(y_true, y_scores) roc_auc = auc(fpr, tpr) # --- 绘图 --- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 5)) # === 左图:分布图 (保持不变) === ax1.hist(y_scores[y_true==0], bins=50, alpha=0.5, color='blue', label='负样本 (好人/蓝山)', density=True) ax1.hist(y_scores[y_true==1], bins=50, alpha=0.5, color='red', label='正样本 (坏人/红山)', density=True) ax1.set_title(f'{title_prefix}:预测分数分布 (重叠度)', fontsize=14) ax1.set_xlabel('预测概率 (阈值刀从右往左切)') ax1.set_ylabel('密度') ax1.legend() # === 右图:ROC 曲线 (核心变化) === # 绘制 ROC 曲线 ax2.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})') # 绘制对角线 (随机猜测基准线) ax2.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', label='随机猜测 (AUC=0.5)') # 填充曲线下面积 ax2.fill_between(fpr, tpr, color='darkorange', alpha=0.2) ax2.set_title(f'{title_prefix}:对应的 ROC 曲线', fontsize=14) ax2.set_xlabel('FPR (假正率 - 误伤的好人比例) -> 代价', fontsize=12) ax2.set_ylabel('TPR (真正率 - 抓到的坏人比例) -> 收益', fontsize=12) ax2.set_xlim([-0.02, 1.0]) ax2.set_ylim([0.0, 1.05]) ax2.legend(loc="lower right") ax2.grid(True) plt.show() # --- 运行对比 --- # 情况一:模型很差 (山峰严重重叠) # 逻辑:你想抓红球,必然会抓到一大把蓝球。 plot_distribution_and_roc(mu_neg=0.4, mu_pos=0.5, title_prefix="情况A:模型较差(山峰重叠)") # 情况二:模型很好 (山峰分得很开) # 逻辑:你可以先把红球抓干净,都还碰不到蓝球。 plot_distribution_and_roc(mu_neg=0.3, mu_pos=0.8, title_prefix="情况B:模型优秀(山峰分离)")三、PR曲线