背景
本次尝试旨在复现Fig. 4的可视化思想,通过结合SHAP蜂巢图和柱状图,直观展示各特征对模型输出的影响及其平均重要性分布
代码实现
模型构建
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Times New Roman'
plt.rcParams['axes.unicode_minus'] = False
from sklearn.model_selection import train_test_split
df = pd.read_excel('2024-12-30公众号Python机器学习AI.xlsx')
# 划分特征和目标变量
X = df.drop(['y'], axis=1)
y = df['y']
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,
random_state=42, stratify=df['y'])
from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.metrics import accuracy_score
# 定义 XGBoost 二分类模型
model_xgb = XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=8)
# 定义参数网格
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.1, 0.2],
'subsample': [0.8, 1.0],
'colsample_bytree': [0.8, 1.0]
}
# 定义 K 折交叉验证 (Stratified K-Fold)
kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=8)
# 使用网格搜索寻找最佳参数
grid_search = GridSearchCV(estimator=model_xgb, param_grid=param_grid, scoring='accuracy',
cv=kfold, verbose=1, n_jobs=-1)
# 拟合模型
grid_search.fit(X_train, y_train)
# 使用最优参数训练模型
xgboost = grid_search.best_estimator_
通过网格搜索和交叉验证优化XGBoost二分类模型的超参数,并使用最佳参数在训练集上训练模型,为后续使用SHAP分析特征重要性提供基础的模型构建
通过调用函数绘图
import shap
explainer = shap.TreeExplainer(xgboost)
shap_values = explainer.shap_values(X_test)
plt.figure(figsize=(10, 5), dpi=1200)
shap.summary_plot(shap_values, X_test, plot_type="bar", show=False)
plt.title('SHAP_numpy Sorted Feature Importance')
plt.tight_layout()
plt.savefig("2.pdf", format='pdf',bbox_inches='tight')
plt.show()
分两部分绘制并保存SHAP特征重要性的可视化图:
- 第一部分使用summary_plot函数以"点图"的形式展示每个特征的SHAP值分布和平均重要性。每个点代表数据集中样本在对应特征上的SHAP值,x轴表示SHAP值大小,点沿特征行堆叠显示密度,颜色表示特征原始值的大小,全面体现特征对模型输出的贡献
- 第二部分再次调用summary_plot函数,以"柱状图"的形式可视化特征的平均SHAP值绝对值,展示特征的重要性排序及对模型预测的总体贡献
这里都是通过调用SHAP的summary_plot函数进行绘制
自定义绘制
from matplotlib.colors import LinearSegmentedColormap
from matplotlib import gridspec
from scipy.spatial import KDTree
shap_values_df = pd.DataFrame(shap_values, columns=X_test.columns)
# 计算 mean(|SHAP value|) 并排序特征
mean_abs_shap_values = shap_values_df.abs().mean().sort_values(ascending=False)
sorted_features = mean_abs_shap_values.index
# 创建绘图数据
plot_data = []
for feature in sorted_features:
for shap_val, feature_val in zip(
shap_values_df[feature], X_test[feature]
):
normalized_value = (feature_val - X_test[feature].min()) / (
X_test[feature].max() - X_test[feature].min()
) # 动态标准化
plot_data.append(
{"Feature": feature, "SHAP Value": shap_val, "Normalized Value": normalized_value}
)
plot_df = pd.DataFrame(plot_data)
# 创建深蓝-淡灰-深红渐变色
custom_cmap = LinearSegmentedColormap.from_list("custom", ["#3465A4", "#E6E6E6", "#CC3333"])
# 创建 KDTree 用于计算邻近点
all_points = plot_df["SHAP Value"].values.reshape(-1, 1) # SHAP Value 作为坐标
tree = KDTree(all_points)
# 定义抖动范围和距离阈值
jitter_scale = 0.15 # 抖动强度
distance_threshold = 0.05 # 距离阈值,决定是否抖动
# 创建图形和网格布局
fig = plt.figure(figsize=(12, 8)) # 调整图形大小
gs = gridspec.GridSpec(2, 1, height_ratios=[0.05, 0.85]) # 上方0.05部分用于颜色条
# 创建主绘图区域
ax = plt.subplot(gs[1])
for i, feature in enumerate(sorted_features):
# 筛选每个特征的数据
subset = plot_df[plot_df["Feature"] == feature]
shap_values = subset["SHAP Value"].values
# 初始化抖动数组
jitter = np.zeros(len(shap_values))
# 遍历每个点,判断是否需要抖动
for idx, shap_value in enumerate(shap_values):
# 查询邻近点数量
neighbors = tree.query_ball_point([shap_value], r=distance_threshold)
if len(neighbors) > 1: # 如果邻近点数量大于1,则参与抖动
jitter[idx] = np.random.normal(loc=0, scale=jitter_scale)
# 绘制散点
ax.scatter(
subset["SHAP Value"],
jitter + i, # y 轴位置加上抖动
c=subset["Normalized Value"], # 使用标准化后的值进行颜色映射
cmap=custom_cmap, # 使用自定义配色
s=15,
alpha=0.7,
)
# 添加 SHAP Value = 0 的灰色虚线
ax.axvline(x=0, color="gray", linestyle="--", linewidth=1.5, alpha=0.5)
# 设置图形标签
ax.set_xlabel("SHAP Value (Impact on Model Output)", fontsize=14) # 调整字体大小
ax.set_ylabel("Features", fontsize=14)
ax.set_yticks(range(len(sorted_features)))
ax.set_yticklabels(sorted_features, fontsize=12)
ax.invert_yaxis() # 反转 y 轴,使最重要的特征在顶部
# 移除网格线
ax.grid(False)
# 创建颜色条区域
cax = plt.subplot(gs[0]) # 独立的颜色条轴
sm = plt.cm.ScalarMappable(cmap=custom_cmap)
sm.set_array([]) # 颜色条无固定刻度值
cbar = fig.colorbar(sm, cax=cax, orientation="horizontal")
cbar.outline.set_visible(False) # 去掉颜色条的黑边
cbar.ax.tick_params(labelsize=10) # 调整刻度字体大小
cbar.ax.xaxis.set_ticks([]) # 隐藏刻度
cbar.ax.set_title("Feature Value", fontsize=14, pad=10) # 显示统一的标题
cbar.ax.text(-0.02, 0.5, "Low", ha="center", va="center", transform=cbar.ax.transAxes, fontsize=12)
cbar.ax.text(1.02, 0.5, "High", ha="center", va="center", transform=cbar.ax.transAxes, fontsize=12)
# 调整布局
plt.subplots_adjust(top=0.92) # 为颜色条和标题腾出空间
plt.savefig("3.pdf", format='pdf', bbox_inches='tight', dpi=1200)
plt.show()
手动实现一个自定义的SHAP蜂巢图,通过动态标准化特征值、抖动处理密集点、颜色渐变表示特征值大小,并绘制每个特征的SHAP值分布及其对模型输出的影响
与原始代码的主要区别在于:
- 手动实现:这段代码不依赖SHAP库的summary_plot,而是手动构建了一个更灵活的自定义可视化,支持动态调整布局和配色
- 抖动处理:通过KDTree和距离阈值对重叠点进行抖动,增强了图形的可读性
- 颜色映射和自定义颜色条:使用自定义颜色渐变来表示特征值大小,图形和颜色条的设计更加直观和美观
关于抖动处理:原始绘图通过点沿特征行堆叠来显示密度,但由于其具体堆叠规则不明确,因此这里采用了抖动处理的方式。本质上,这种方法仍然是为了展示在同一个SHAP值(或小范围)下存在多个数据点的情况。如果不进行抖动或堆叠处理,这些数据点可能会完全折叠为一个点,导致信息丢失或图形不够直观,对于shap的解读是不存在影响的
在前面绘制SHAP蜂巢图的基础上,增加右侧的柱状图,用于展示每个特征的平均绝对SHAP值(mean(|SHAP value|)),以更直观地表达特征的重要性排序,实现蜂巢图和柱状图的结合,提供了更丰富的特征贡献解读
这个可视化在前面的基础上添加了一个特征分组规则,并在柱状图中通过颜色对特征分组进行了直观表示,这里为什么采用一个特征分组规则规则来进行可视化用一个案例来展示说明:
假设在研究一个预测患者患某种疾病(例如糖尿病)的模型。模型的特征可以分为两大类:患者的生理指标和生活习惯
特征和分组: Group 1(生理指标):X_1: 血糖水平、 X_2: 血压、 X_3: 胰岛素敏感性、 X_4: BMI, Group 2(生活习惯):X_5: 饮食习惯、 X_6: 每周运动时间、 X_7: 吸烟习惯、 X_8: 每天睡眠时间、 X_9: 压力水平
新规则的实际意义:在右侧柱状图中,生理指标(Group 1)用浅蓝色,生活习惯(Group 2)用深蓝色,这样可以清晰地区分疾病发生是否更受生理指标还是生活习惯的影响。例如,如果浅蓝色柱子较高,说明生理指标对模型预测贡献更大;如果深蓝色柱子较高,则生活习惯的影响更显著,通过分组颜色和SHAP蜂巢图直观展示生理指标和生活习惯对疾病预测的相对重要性。这里只是说明一个案例无任何实际意义,当然也可以根据不同研究背景进行更多分组设计,提取其它维度的信息,代码与数据集获取:如需获取本文的源代码和数据集,请添加作者微信联系
往期推荐
期刊配图:ALE(累积局部效应)模型解释方法解决部分依赖图PDP多重共线性问题
期刊配图:多种机器学习算法结合SHAP特征贡献在递归特征选择中的运用
复现SCI文章 SHAP 依赖图可视化以增强机器学习模型的可解释性
复现 Nature 图表——基于PCA的高维数据降维与可视化实践及其扩展
复现Nature图表——基于PCA降维与模型预测概率的分类效果可视化
如果你对类似于这样的文章感兴趣。
欢迎关注、点赞、转发~
个人观点,仅供参考