代码解读与逐行注释
1. 导入必要的库
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder
from sklearn.compose import ColumnTransformer
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score, classification_report
from matplotlib import pyplot as plt
# 添加SMOTE相关导入
from imblearn.over_sampling import SMOTE
from collections import Counter
-
目的:导入数据分析、机器学习和可视化所需的库。
- 关键点:
-
pandas和numpy用于数据处理。 -
train_test_split用于数据集分割。 -
StandardScaler和OneHotEncoder用于特征预处理。 -
XGBClassifier是 XGBoost 分类器。 -
SMOTE用于处理类别不平衡问题。
-
2. 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
-
目的:确保 Matplotlib 图表中正确显示中文和负号。
3. 定义异常值处理函数
def handle_outliers(df, num_cols):
"""处理异常值"""
medical_limits = {
'HGB': (30, 200), # 血红蛋白范围
'PLT': (10, 1000), # 血小板范围
'PT': (5, 50), # 凝血酶原时间范围
'TT': (5, 100), # 凝血时间范围
'FIB': (0.5, 10), # 纤维蛋白原范围
}
for col in num_cols:
if col in medical_limits:
min_val, max_val = medical_limits[col]
df[col] = df[col].clip(min_val, max_val) # 限制范围
return df
-
目的:对数值列的异常值进行裁剪,确保数据在医学合理范围内。
- 关键点:
-
使用
clip方法将数值限制在指定范围内。 -
适用于医学指标(如血红蛋白、血小板等)。
-
4. 读取数据
file_path = r"D:\1-2025年项目\11-血库信息管理系统\Data\test_data1.xls"
data = pd.read_excel(file_path) # 读取Excel文件
-
目的:从指定路径读取 Excel 文件。
5. 数据预处理
# 1. 删除无关列
to_drop = ['住院号', '申请医师', '产品码', '献血码', '申请日期','血量']
data = data.drop([col for col in to_drop if col in data.columns], axis=1)
# 合并血液名称类别
data['血液名称'] = data['血液名称'].replace({
'病毒灭活冰冻血浆': '病毒灭活新鲜冰冻血浆'
})
# 2. 转换数值列和处理缺失值
num_cols = ['HGB', 'PLT', 'PT', 'TT', 'FIB']
for col in num_cols:
data[col] = pd.to_numeric(data[col], errors='coerce')
# 使用中位数填充缺失值
data[col] = data[col].fillna(data[col].median())
# 3. 处理异常值
data = handle_outliers(data, num_cols)
-
目的:清理数据,包括删除无关列、合并类别、处理缺失值和异常值。
- 关键点:
-
删除无关列(如住院号、申请医师等)。
-
合并相似的血液名称类别。
-
将数值列转换为数值类型,并用中位数填充缺失值。
-
调用
handle_outliers函数裁剪异常值。
-
6. 特征工程
# 1. 基础交互特征
data["HGB_PLT_interaction"] = data["HGB"] * data["PLT"]
data["PT_TT_ratio"] = data["PT"] / data["TT"] # 凝血相关指标比率
data["FIB_PT_interaction"] = data["FIB"] * data["PT"] # 纤维蛋白原与凝血酶原时间交互
# 2. 血红蛋白相关特征
data['HGB_risk'] = pd.cut(data['HGB'],
bins=[-float('inf'), 70, 100, float('inf')],
labels=['high_risk', 'medium_risk', 'low_risk'])
data['HGB_risk'] = data['HGB_risk'].map({'high_risk': 2, 'medium_risk': 1, 'low_risk': 0})
# 3. 血小板相关特征
data['PLT_risk'] = pd.cut(data['PLT'],
bins=[-float('inf'), 50, 80, 100, float('inf')],
labels=['very_high_risk', 'high_risk', 'medium_risk', 'low_risk'])
data['PLT_risk'] = data['PLT_risk'].map({
'very_high_risk': 3, 'high_risk': 2, 'medium_risk': 1, 'low_risk': 0})
# 4. 凝血功能相关特征
data['PT_risk'] = (data['PT'] > data['PT'].mean() * 1.5).astype(int)
data['FIB_risk'] = pd.cut(data['FIB'],
bins=[-float('inf'), 1.0, 1.5, 2.0, float('inf')],
labels=['high_risk', 'medium_risk', 'low_risk', 'normal'])
data['FIB_risk'] = data['FIB_risk'].map({
'high_risk': 3, 'medium_risk': 2, 'low_risk': 1, 'normal': 0})
# 5. 复合特征
data["HGB_PLT_ratio"] = data["HGB"] / data["PLT"] # 血红蛋白与血小板比值
data["PT_FIB_interaction"] = data["PT"] * data["FIB"] # 凝血时间与纤维蛋白原交互
data["Coagulation_index"] = (data["PT"] * data["TT"]) / data["FIB"] # 凝血综合指数
-
目的:通过特征工程创建新的特征,增强模型的预测能力。
- 关键点:
-
创建交互特征(如
HGB_PLT_interaction)。 -
使用
pd.cut对数值列进行分箱,并映射为风险等级。 -
创建复合特征(如
Coagulation_index)。
-
7. 打印血液名称分布
print("血液名称分布:")
print(data['血液名称'].value_counts())
# 移除样本数过少的类别(少于2个样本)
min_samples = 2
value_counts = data['血液名称'].value_counts()
valid_labels = value_counts[value_counts >= min_samples].index
data = data[data['血液名称'].isin(valid_labels)]
print("\n处理后的血液名称分布:")
print(data['血液名称'].value_counts())
-
目的:统计血液名称的分布,并移除样本数过少的类别。
8. 数据集分割
X = data.drop('血液名称', axis=1)
y = data['血液名称']
# 使用分层抽样进行分割
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y # 添加分层抽样
)
-
目的:将数据集分为训练集和测试集,保持类别分布一致。
- 关键点:
-
stratify=y确保训练集和测试集的类别分布一致。
-
9. 标签编码
# 然后对训练集和测试集分别进行标签编码
le = LabelEncoder()
y_encoded_train = le.fit_transform(y_train)
y_encoded_test = le.transform(y_test)
-
目的:将类别标签转换为整数编码。
10. 特征预处理
numeric_features = [
"HGB", "PLT", "PT", "TT", "FIB",
"HGB_PLT_interaction", "PT_TT_ratio", "FIB_PT_interaction",
"HGB_risk", "PLT_risk", "PT_risk", "FIB_risk",
"HGB_PLT_ratio", "PT_FIB_interaction", "Coagulation_index"
]
categorical_features = ["血型", "科室", "临床诊断", "申请类型"]
preprocessor = ColumnTransformer(
transformers=[
("num", StandardScaler(), numeric_features),
("cat", OneHotEncoder(handle_unknown='ignore', sparse_output=False), categorical_features),
]
)
# 先进行特征处理
X_train_processed = preprocessor.fit_transform(X_train)
X_test_processed = preprocessor.transform(X_test)
-
目的:对数值特征进行标准化,对类别特征进行独热编码。
- 关键点:
-
StandardScaler用于数值特征的标准化。 -
OneHotEncoder用于类别特征的独热编码。
-
11. 应用 SMOTE 处理类别不平衡
print("应用SMOTE前的类别分布:")
print(Counter(y_train))
smote = SMOTE(random_state=42, sampling_strategy={
'病毒灭活新鲜冰冻血浆': 341 # 修改为新的类别名称,样本数与去白细胞悬浮红细胞相同
})
# 对处理后的训练集进行过采样
X_train_resampled, y_train_resampled = smote.fit_resample(X_train_processed, y_train)
print("\n应用SMOTE后的类别分布:")
print(Counter(y_train_resampled))
-
目的:通过 SMOTE 算法对少数类别进行过采样,解决类别不平衡问题。
12. 定义并训练模型
model = XGBClassifier(
objective="multi:softmax",
num_class=len(le.classes_),
max_depth=6,
learning_rate=0.03,
n_estimators=300,
min_child_weight=5,
subsample=0.7,
colsample_bytree=0.7,
gamma=0.1,
reg_alpha=0.1,
reg_lambda=1,
eval_metric="mlogloss",
early_stopping_rounds=30,
random_state=42
)
# 训练模型
model.fit(
X_train_resampled,
y_encoded_train,
eval_set=[(X_test_processed, y_encoded_test)],
verbose=True
)
-
目的:使用 XGBoost 分类器训练模型。
- 关键点:
-
objective="multi:softmax"表示多分类任务。 -
num_class指定类别数量。 -
early_stopping_rounds用于防止过拟合。
-
13. 模型评估
y_pred_encoded = model.predict(X_test_processed)
y_pred_proba = model.predict_proba(X_test_processed)
y_pred = le.inverse_transform(y_pred_encoded)
# 获取类别数量并确保标签编码一致
n_classes = len(le.classes_)
# 修正标签二值化过程
y_test_bin = np.zeros((len(y_encoded_test), n_classes))
y_pred_bin = np.zeros((len(y_pred_encoded), n_classes))
for i in range(n_classes):
y_test_bin[:, i] = (y_encoded_test == i).astype(int)
y_pred_bin[:, i] = (y_pred_encoded == i).astype(int)
# 计算每个类别的ROC曲线和AUC
fpr = dict()
tpr = dict()
roc_auc = dict()
for i in range(n_classes):
fpr[i], tpr[i], _ = roc_curve(y_test_bin[:, i], y_pred_proba[:, i])
roc_auc[i] = auc(fpr[i], tpr[i])
-
目的:对模型进行评估,包括准确率、F1 分数、ROC 曲线等。
14. 混淆矩阵可视化
plt.figure(figsize=(10, 8))
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=le.classes_,
yticklabels=le.classes_)
plt.title('混淆矩阵')
plt.xlabel('预测标签')
plt.ylabel('真实标签')
plt.tight_layout()
plt.show()
-
目的:绘制混淆矩阵,直观展示分类结果。
15. 精确率-召回率曲线
plt.figure(figsize=(10, 8))
for i in range(n_classes):
precision, recall, _ = precision_recall_curve(y_test_bin[:, i], y_pred_proba[:, i])
avg_precision = average_precision_score(y_test_bin[:, i], y_pred_proba[:, i])
plt.plot(recall, precision,
label=f'{le.classes_[i]} (AP = {avg_precision:.2f})')
plt.xlabel('召回率')
plt.ylabel('精确率')
plt.title('各类别精确率-召回率曲线')
plt.legend(loc='lower left')
plt.grid(True)
plt.show()
-
目的:绘制精确率-召回率曲线,评估模型在不同阈值下的性能。
16. 各类别详细指标
print("\n=== 各类别详细评估指标 ===")
for i, class_name in enumerate(le.classes_):
print(f"\n{class_name}:")
print(f"AUC: {roc_auc[i]:.4f}")
print(f"平均精确率: {average_precision_score(y_test_bin[:, i], y_pred_proba[:, i]):.4f}")
-
目的:输出每个类别的 AUC 和平均精确率。
17. 绘制 ROC 曲线
plt.figure(figsize=(10, 8))
colors = ['blue', 'red', 'green', 'yellow', 'purple']
for i, color in zip(range(n_classes), colors):
plt.plot(fpr[i], tpr[i], color=color, lw=2,
label=f'ROC curve of {le.classes_[i]} (AUC = {roc_auc[i]:.2f})')
plt.plot([0, 1], [0, 1], 'k--', lw=2)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curves for Multi-class Classification')
plt.legend(loc="lower right")
plt.show()
-
目的:绘制 ROC 曲线,评估模型的分类性能。
18. 打印每个类别的 AUC 值
print("\n各类别的AUC值:")
for i in range(n_classes):
print(f"{le.classes_[i]}: {roc_auc[i]:.4f}")
-
目的:输出每个类别的 AUC 值,进一步评估模型性能。
总结
这段代码实现了一个完整的机器学习流程,包括数据预处理、特征工程、模型训练和评估。通过 SMOTE 处理类别不平衡问题,并使用 XGBoost 分类器进行多分类任务。最后,通过多种评估指标(如准确率、F1 分数、ROC 曲线等)对模型性能进行了全面分析。
1万+

被折叠的 条评论
为什么被折叠?



