importpandas as pd
importmath
importos
save_dir=r"D:\Desktop\Postgraduate\PythonStudy\Python打卡训练营\Python打卡训练营\day10 pic"
frompathlibimportPath
importmatplotlib.pyplot as plt
importnumpy as np
importseaborn as sns
fromsklearn.model_selectionimporttrain_test_split, cross_val_score
fromsklearn.preprocessingimportStandardScaler
fromsklearn.metricsimportconfusion_matrix, accuracy_score, precision_score, recall_score, f1_score
fromsklearn.svmimportSVC
fromsklearn.neighborsimportKNeighborsClassifier
fromsklearn.linear_modelimportLogisticRegression
fromsklearn.naive_bayesimportGaussianNB
fromsklearn.treeimportDecisionTreeClassifier
fromsklearn.ensembleimportRandomForestClassifier
fromxgboostimportXGBClassifier
fromlightgbmimportLGBMClassifier
importwarnings
warnings.filterwarnings("ignore")
plt.rcParams['font.sans-serif']=['SimHei','Microsoft YaHei']# 优先黑体,备选微软雅黑
plt.rcParams['axes.unicode_minus']=False# 解决坐标轴负号显示方框问题
data=pd.read_csv(r"D:\Desktop\Postgraduate\PythonStudy\Python打卡训练营\Python打卡训练营\python60-days-challenge-master\heart.csv")
print(data.info())
col_map={
"age":"年龄",
"sex":"性别",
"cp":"胸痛类型",
"trestbps":"静息血压",
"chol":"血清胆固醇",
"fbs":"空腹血糖是否>120mg/dl",
"restecg":"静息心电图结果",
"thalach":"最大心率",
"exang":"运动是否诱发心绞痛",
"oldpeak":"ST段压低程度",
"slope":"ST段峰值斜率",
"ca":"荧光染色血管数量",
"thal":"地中海贫血",
"target":"是否患心脏病"
}
data.rename(columns=col_map, inplace=True)
print(data.head())
discrete_cols=[
"性别",
"胸痛类型",
"空腹血糖是否>120mg/dl",
"静息心电图结果",
"运动是否诱发心绞痛",
"ST段峰值斜率",
"荧光染色血管数量",
"地中海贫血"
]
target_col=["是否患心脏病"]
continuous_cols=[]
continuous_cols=[colforcolindata.columnsifcolnotindiscrete_cols+target_col]
# ====================== 1. 划分特征标签 + 固定种子拆分数据集 ======================
# 提取特征与标签,标签转为一维数组避免API警告
X=data.drop(columns=target_col)
y=data[target_col].values.ravel()
# 固定随机种子42,分层抽样保证训练/测试集类别比例一致
X_train, X_test, y_train, y_test=train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"训练集样本量:{len(X_train)},测试集样本量:{len(X_test)}")
# ====================== 2. 连续特征IQR异常值处理(缩尾法,不丢失样本) ======================
# 仅用训练集统计分位数,再应用到测试集,杜绝数据泄露
defiqr_winsorize(train_df, test_df, cols_list):
train_df=train_df.copy()
test_df=test_df.copy()
forcolincols_list:
Q1=train_df[col].quantile(0.25)
Q3=train_df[col].quantile(0.75)
IQR=Q3-Q1
upper_bound=Q3+1.5*IQR
lower_bound=Q1-1.5*IQR
# 超出上下限的值截断到边界,即箱线图缩尾处理
train_df[col]=train_df[col].clip(lower=lower_bound, upper=upper_bound)
test_df[col]=test_df[col].clip(lower=lower_bound, upper=upper_bound)
returntrain_df, test_df
X_train, X_test=iqr_winsorize(X_train, X_test, continuous_cols)
print("连续特征IQR异常值缩尾处理完成")
# ====================== 3. 连续特征标准化(SVM/KNN/LR对量纲敏感,树模型无影响) ======================
scaler=StandardScaler()
# 仅对连续特征标准化,离散分类特征保持原值
X_train[continuous_cols]=scaler.fit_transform(X_train[continuous_cols])
X_test[continuous_cols]=scaler.transform(X_test[continuous_cols])
# ====================== 4. 定义8个对比模型 ======================
models={
"SVM": SVC(random_state=42),
"KNN": KNeighborsClassifier(),
"逻辑回归": LogisticRegression(random_state=42, max_iter=1000),
"朴素贝叶斯": GaussianNB(),
"决策树": DecisionTreeClassifier(random_state=42),
"随机森林": RandomForestClassifier(random_state=42),
"XGBoost": XGBClassifier(random_state=42, use_label_encoder=False, eval_metric="logloss"),
"LightGBM": LGBMClassifier(random_state=42, verbose=-1)
}
# ====================== 5. 交叉验证 + 模型训练 + 指标计算 ======================
result_list=[]# 存储评估指标
cm_dict={}# 存储混淆矩阵
label_names=["无心脏病","有心脏病"]# 0=无病,1=患病
print("\n"+"="*60)
print("各模型5折交叉验证(训练集加权F1)与测试集结果")
print("="*60)
formodel_name, modelinmodels.items():
# 训练集5折交叉验证,评估指标为加权F1
cv_f1=cross_val_score(model, X_train, y_train, cv=5, scoring="f1_weighted").mean()
# 全量训练集拟合模型
model.fit(X_train, y_train)
y_pred=model.predict(X_test)
# 计算测试集四大核心指标
acc=accuracy_score(y_test, y_pred)
precision=precision_score(y_test, y_pred, average="weighted")
recall=recall_score(y_test, y_pred, average="weighted")
f1=f1_score(y_test, y_pred, average="weighted")
# 保存结果
result_list.append({
"模型名称": model_name,
"交叉验证平均F1":round(cv_f1,4),
"测试集准确率":round(acc,4),
"测试集精确率":round(precision,4),
"测试集召回率":round(recall,4),
"测试集F1分数":round(f1,4)
})
cm_dict[model_name]=confusion_matrix(y_test, y_pred)
print(f"{model_name:8s} | 交叉验证F1: {cv_f1:.4f} | 测试集F1: {f1:.4f}")
# ====================== 6. 评估指标对比表格(按F1降序) ======================
result_df=pd.DataFrame(result_list).sort_values(by="测试集F1分数", ascending=False).reset_index(drop=True)
print("\n"+"="*80)
print("所有模型测试集评估指标汇总(按F1分数从高到低排序)")
print("="*80)
print(result_df.to_string(index=False))
# ====================== 7. 混淆矩阵总图(2行4列子图) ======================
fig, axes=plt.subplots(2,4, figsize=(22,10))
axes=axes.flatten()# 把二维数组展平,方便循环遍历
foridx, (name, cm)inenumerate(cm_dict.items()):
sns.heatmap(
cm, annot=True, fmt="d", cmap="Blues", ax=axes[idx],
xticklabels=label_names, yticklabels=label_names,
annot_kws={"size":14}
)
axes[idx].set_title(f"{name} 混淆矩阵", fontsize=14, pad=10)
axes[idx].set_xlabel("预测结果", fontsize=12)
axes[idx].set_ylabel("真实结果", fontsize=12)
plt.tight_layout()
plt.savefig(os.path.join(save_dir,"confusion_matrix_all.png"), dpi=300, bbox_inches="tight")
plt.show()
# ====================== 8. 输出F1分数最高的模型 ======================
best_row=result_df.iloc[0]
print("\n"+"="*60)
print(f"✅ 测试集F1分数最高的模型:{best_row['模型名称']}")
print(f" 对应F1得分为:{best_row['测试集F1分数']}")
print(f" 交叉验证平均F1:{best_row['交叉验证平均F1']}")
print("="*60)
# ====================== 补充:各模型特征重要性/系数对比 ======================
# 提取特征名称列表
feature_names=X_train.columns.tolist()
# 1. 可输出特征重要性的模型(树模型 + 逻辑回归)
importance_models={
"逻辑回归": models["逻辑回归"],
"决策树": models["决策树"],
"随机森林": models["随机森林"],
"XGBoost": models["XGBoost"],
"LightGBM": models["LightGBM"]
}
# 2. 绘制特征重要性对比图(2行3列)
fig, axes=plt.subplots(2,3, figsize=(24,14))
axes=axes.flatten()
foridx, (name, model)inenumerate(importance_models.items()):
ifname=="逻辑回归":
# 线性模型取系数绝对值作为重要性
importance=np.abs(model.coef_[0])
title_suffix="(系数绝对值)"
else:
# 树模型取feature_importances_
importance=model.feature_importances_
title_suffix="(特征重要性)"
# 按重要性降序排序
sorted_idx=np.argsort(importance)[::-1]
sorted_features=[feature_names[i]foriinsorted_idx]
sorted_values=importance[sorted_idx]
# 绘制横向柱状图
sns.barplot(x=sorted_values, y=sorted_features, ax=axes[idx], palette="viridis")
axes[idx].set_title(f"{name} 特征贡献度{title_suffix}", fontsize=14, pad=10)
axes[idx].set_xlabel("重要性数值", fontsize=12)
axes[idx].set_ylabel("特征名称", fontsize=12)
# 最后一个子图放SVM和KNN的说明(无直接特征重要性)
axes[-1].axis("off")
axes[-1].text(0.5,0.5,
"SVM / KNN / 朴素贝叶斯\n无原生特征重要性输出\nSVM可通过线性核系数查看\nKNN为距离投票无特征权重",
ha="center", va="center", fontsize=14, linespacing=2)
axes[-1].set_title("无特征重要性的模型", fontsize=14, pad=10)
plt.tight_layout()
plt.savefig(os.path.join(save_dir,"feature_importance_all.png"), dpi=300, bbox_inches="tight")
plt.show()
|