论文:Uncovering the multiple socio-economic driving factors of carbon emissions in nine urban agglomerations of China based on machine learning对之前的一个shap图进行了一些改造,主要的部分封装成了函数,同时加上了颜色库,便于更自由的切换配色。左侧图(a)是特征重要性条形图,它依据平均绝对SHAP值对特征进行了降序排列,条形的长度代表特征的全局重要性,即该特征对模型预测结果的平均贡献幅度,条形越长且颜色越红说明该特征越关键;左下角的嵌入式玫瑰图则以百分比形式补充展示了各特征的相对贡献占比。右侧图(b)是SHAP蜂巢图,用于展示特征的具体影响方向和分布:每一行对应左侧的一个特征,行中的每一个点代表一个样本;右侧最边缘的色条指示了特征本身的数值大小,红色代表该特征取值高,蓝色代表取值低;X轴代表特征对模型输出的影响值,点位于0轴右侧表示该样本的特征值导致预测结果增加,位于左侧则表示导致结果减少。
import pandas as pdimport numpy as npimport xgboostimport shapimport matplotlib.pyplot as pltimport matplotlib.colors as mcolorsimport matplotlib.ticker as tickerfrom matplotlib.cm import ScalarMappablefrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import StandardScalerfrom shap.plots import beeswarmfrom sklearn.model_selection import GridSearchCV
COLOR_SCHEMES = { 1: ["blue", "#4B0082", "red"], }CURRENT_SCHEME_ID = 15
特征重要性条形图和径向图/玫瑰图组合图绘制函数的开始部分。创建画布,手动计算布局参数来精确控制子图位置,添加两个坐标轴:一个用于左侧的颜色条,一个用于主要的条形图。绘制颜色条,去掉了默认刻度,并手动在颜色条的上下添加了“High”和“Low”文本,以及侧面的标题,用于指示SHAP值的大小方向。def draw_bar_and_radial(sorted_features, sorted_shap_values, bar_colors, cmap, color_norm): fig = plt.figure(figsize=(16, 15)) left_margin, right_margin, bottom_margin, top_margin = 0.08, 0.08, 0.12, 0.12 colorbar_width = 0.02 plot_bottom = bottom_margin plot_height = 1.0 - bottom_margin - top_margin cbar_left = left_margin main_ax_left = cbar_left + colorbar_width + 0.04 main_ax_width = 1.0 - main_ax_left - right_margin ax_cbar = fig.add_axes([cbar_left, plot_bottom, colorbar_width, plot_height])
ax_cbar.text(0.5, -0.01, 'Low' '', transform=ax_cbar.transAxes, ha='center', va='top', fontsize=24) cbar.outline.set_visible(False)
ax_cbar.text(-1.4, 0.5, 'Contribution for CEs ($10^4$ t)', transform=ax_cbar.transAxes, fontsize=24, rotation=90, va='center')
特征重要性条形图和径向图/玫瑰图组合图绘制函数的条形图部分,这一部分负责绘制特征重要性的水平条形图,根据SHAP值大小反转了Y轴,使得最重要的特征显示在顶部,反转了X轴,隐藏了Y轴的默认刻度标签,使用 text 函数手动在条形图的右侧添加特征名称。
ax_bar.xaxis.tick_bottom() ax_bar.xaxis.set_label_position("bottom")
ax_bar.invert_xaxis() ax_bar.barh(y=range(len(sorted_features)), width=sorted_shap_values, color=bar_colors, height=0.6)
ax_bar.invert_yaxis()
ax_bar.set_xlabel('Contribution for CEs ($10^4$ t)', size=24, labelpad=20) ax_bar.set_yticks([]) ax_bar.spines[['left', 'top']].set_visible(False) ax_bar.spines['right'].set_position(('data', 0)) ax_bar.spines['right'].set_visible(True) ax_bar.spines['bottom'].set_visible(True)
ax_bar.tick_params(axis='x', which='major', direction='in', labelsize=24, length=6, pad=8)
ax_bar.text(0.02, 0.98, '(a)', transform=ax_bar.transAxes, fontsize=30, weight='bold', ha='left', va='top')
特征重要性条形图和径向图/玫瑰图组合图绘制函数的径向图/玫瑰图部分,这一部分在图表的左下角创建了一个嵌入的径向图/玫瑰图。使用创建极坐标系。计算了每个特征重要性占比,以此决定扇形的角度宽度。扇形由两部分组成:内部的灰白交替背景和外部根据SHAP值着色的环。计算了角度和半径位置,精确放置每个扇形和百分比标签。最后,隐藏了极坐标系的轴线和网格,调整了方向,并将生成的图片保存到指定路径。 inset_left = main_ax_left - 0.15 inset_bottom = plot_bottom - 0.05 inset_size = min(main_ax_width, plot_height) * 0.85
inset_ax_rect = [inset_left, inset_bottom, inset_size, inset_size] ax_radial_inset = fig.add_axes(inset_ax_rect, projection='polar') ax_radial_inset.patch.set_alpha(0)
percentages = (sorted_shap_values / sorted_shap_values.sum()) * 100 widths = (sorted_shap_values / sorted_shap_values.sum()) * 2 * np.pi
thetas = np.cumsum([0] + widths[:-1].tolist()) - one_oclock_offset
ax_radial_inset.bar(x=thetas, height=inner_heights, width=widths, color=inner_colors, align='edge', edgecolor='white', linewidth=1.5) ax_radial_inset.bar(x=thetas, height=[colored_ring_width] * num_vars, width=widths, bottom=inner_heights, color=bar_colors, align='edge', edgecolor='white', linewidth=1.5)
ax_radial_inset.set_yticklabels([]) ax_radial_inset.set_xticklabels([])
ax_radial_inset.spines['polar'].set_visible(False) ax_radial_inset.grid(False)
ax_radial_inset.set_theta_zero_location('N') ax_radial_inset.set_theta_direction(-1) ax_radial_inset.set_ylim(0, max(total_lengths) + 2)
SHAP蜂巢图绘制函数,使用 shap.summary_plot 绘制标准的SHAP蜂巢图。
def draw_native_beeswarm(shap_values, X, cmap): plt.figure(figsize=(16, 15))
shap.summary_plot(shap_values, X, plot_type="dot", show=False, cmap=cmap)
if len(plt.gcf().axes) > 1: cbar_ax = plt.gcf().axes[-1] cbar_ax.set_ylabel('Feature Value', size=16, rotation=-90, labelpad=20) cbar_ax.tick_params(labelsize=14)
plt.tight_layout()
无Y轴标签的SHAP蜂巢图绘制函数,这个函数的功能与上一个类似,但有一个区别,显移除了Y轴的标签。用于组合图的右侧部分,因为左侧的图表已经包含了特征名称。def draw_beeswarm_no_labels(shap_values, X, cmap): plt.figure(figsize=(16, 15)) shap.summary_plot(shap_values, X, plot_type="dot", show=False, cmap=cmap)
ax_third_plot = plt.gca() ax_third_plot.set_yticklabels([]) ax_third_plot.set_ylabel('')
ax_third_plot.set_xlabel("SHAP Value (impact on model output)", fontsize=18) ax_third_plot.tick_params(axis='x', labelsize=14)
if len(plt.gcf().axes) > 1: cbar_ax_third = plt.gcf().axes[-1] cbar_ax_third.set_ylabel('Feature Value', size=16, rotation=-90, labelpad=20) cbar_ax_third.tick_params(labelsize=14)
plt.tight_layout()
组合图绘制函数左侧,创建了一个大画布,设置了左右两个主绘图区域。按6:4的比例分配左侧(条形图+玫瑰图)和右侧(蜂巢图)的空间。def draw_combined_plot(sorted_features, sorted_shap_values, shap_values, bar_colors, cmap, color_norm): fig_combined = plt.figure(figsize=(34, 25)) left_margin, right_margin, bottom_margin, top_margin = 0.05, 0.05, 0.02, 0.1
space_between = 0.01 plot_bottom = bottom_margin plot_height = 1 - bottom_margin - top_margin total_plot_width = 1 - left_margin - right_margin - space_between
ax_cbar_new = fig_combined.add_axes([cbar_left, plot_bottom, colorbar_width, plot_height]) sm = ScalarMappable(cmap=cmap, norm=color_norm)
cbar = fig_combined.colorbar(sm, cax=ax_cbar_new, orientation='vertical') cbar.set_label('', size=18, labelpad=5) cbar.set_ticks([])
cbar.outline.set_visible(False)
ax_cbar_new.text(-1.4, 0.5, 'Contribution for CEs ($10^4$ t)', transform=ax_cbar_new.transAxes, fontsize=30, rotation=90, va='center')
main_ax_left = cbar_left + colorbar_width + 0.05 ax_bar_new = fig_combined.add_axes([main_ax_left, plot_bottom, left_plot_width, plot_height]) ax_bar_new.xaxis.tick_bottom() ax_bar_new.xaxis.set_label_position("bottom") ax_bar_new.invert_xaxis() ax_bar_new.barh(y=range(len(sorted_features)), width=sorted_shap_values, color=bar_colors, height=0.6) ax_bar_new.invert_yaxis() ax_bar_new.set_xlabel('Contribution for CEs ($10^4$ t)',size=30, labelpad=20)
ax_bar_new.spines['right'].set_visible(True) ax_bar_new.spines['bottom'].set_visible(True)
ax_bar_new.tick_params(axis='x', which='major', direction='in', labelsize=30, length=6, ax_bar_new.text(0.02, 0.98, '(a)', transform=ax_bar_new.transAxes, fontsize=30, weight='bold', ha='left', va='top')
组合图绘制函数的中部嵌入径向图,在组合图的左侧区域下方嵌入了径向图/玫瑰图。 num_vars = len(sorted_features) percentages = (sorted_shap_values / sorted_shap_values.sum()) * 100 widths = (sorted_shap_values / sorted_shap_values.sum()) * 2 * np.pi base_length, fixed_increment, colored_ring_width = 3.0, 0.5, 2.0 total_lengths = [base_length + i * fixed_increment for i in range(num_vars)] inner_heights = [max(0, tl - colored_ring_width) for tl in total_lengths] inner_colors = ['#EAEAEA', '#FFFFFF'] * (num_vars // 2 + 1) inset_ax_rect = [inset_left,inset_bottom, inset_size, inset_size] ax_radial_inset_new = fig_combined.add_axes(inset_ax_rect, projection='polar') ax_radial_inset_new.patch.set_alpha(0) ax_radial_inset_new.bar(x=thetas, height=inner_heights, width=widths, color=inner_colors, align='edge', edgecolor='white', linewidth=1.5) ax_radial_inset_new.bar(x=thetas, height=[colored_ring_width] * num_vars, width=widths,
bottom=inner_heights, color=bar_colors, align='edge', edgecolor='white', linewidth=1.5)
ax_radial_inset_new.spines['polar'].set_visible(False) ax_radial_inset_new.grid(False)
ax_radial_inset_new.set_theta_zero_location('N') ax_radial_inset_new.set_theta_direction(-1) ax_radial_inset_new.set_ylim(0, max(total_lengths) + 2)
组合图绘制函数的右侧蜂巢图与保存,调用 shap.plots.beeswarm,将图形绘制在指定的坐标轴上。手动增大了散点的大小。移除了Y轴标签,并添加了X轴标签。添加子图编号,并调整了蜂巢图自带的颜色条的标签和旋转角度。将这张包组合图保存到指定文件夹。 right_plot_left = main_ax_left + left_plot_width + space_between ax_beeswarm = fig_combined.add_axes([right_plot_left, plot_bottom, right_plot_width, plot_height])
beeswarm(shap_values, max_display=len(sorted_features), ax=ax_beeswarm, show=False, color=cmap, plot_size=None)
ax_beeswarm.set_yticklabels([]) ax_beeswarm.set_ylabel('')
cbar_ax_right.tick_params(labelsize=30)
数据预处理与模型训练部分,从Excel文件读取数据。分离特征(X)和目标变量(y),并将数据划分为训练集和测试集。使用 StandardScaler 对特征进行标准化处理,并将其转回带有列名的 DataFrame 格式(以便SHAP能识别特征名)。初始化 XGBoost 回归器,设置参数网格,并使用 5 折交叉验证和网格搜索寻找最佳超参数。最后输出找到的最佳参数。if __name__ == '__main__': data_df = pd.read_excel(r'data.xlsx') target_column_name = 'Target_y' y = data_df[target_column_name] X = data_df.drop(columns=[target_column_name]) feature_names = X.columns.tolist() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) xgb_reg = xgboost.XGBRegressor(objective='reg:squarederror', random_state=42)
grid_search = GridSearchCV(estimator=xgb_reg, param_grid=param_grid, cv=5, scoring='neg_mean_squared_error',n_jobs=-1, verbose=2) grid_search.fit(X_train_df, y_train) best_model = grid_search.best_estimator_ print(f"找到的最佳参数: {grid_search.best_params_}")
SHAP分析与绘图,使用 TreeExplainer 计算测试集的SHAP值。计算每个特征的平均绝对SHAP值(代表全局重要性),并按降序排列,为绘图做准备。根据之前定义的颜色方案和SHAP值的大小,生成对应的颜色映射和每个条形的具体颜色。依次调用之前定义的四个绘图函数,生成并保存四张图片。 model = best_model explainer = shap.TreeExplainer(model) shap_values = explainer(X_test_df) mean_abs_shap = np.abs(shap_values.values).mean(axis=0) shap_series = pd.Series(mean_abs_shap, index=feature_names) shap_series.sort_values(ascending=False, inplace=True) bar_colors = cmap(color_norm(sorted_shap_values))
print(pd.DataFrame(shap_values.values[:5, :3], columns=feature_names[:3]).round(4)) print("\n测试集特征平均重要性 (Mean |SHAP|):") print(np.round(sorted_shap_values, 4))
draw_bar_and_radial(sorted_features, sorted_shap_values, bar_colors, cmap, color_norm) draw_native_beeswarm(shap_values, X_test_df, cmap) draw_beeswarm_no_labels(shap_values, X_test_df, cmap) draw_combined_plot(sorted_features, sorted_shap_values, shap_values, bar_colors, cmap, color_norm)
1.设置颜色方案:
plt.savefig(fr'shap_bar_radial{CURRENT_SCHEME_ID}.png', dpi=208, bbox_inches='tight')plt.savefig(fr'shap_bar_radial{CURRENT_SCHEME_ID}.png', dpi=208, bbox_inches='tight')
3.设置原始数据的路径:
data_df = pd.read_excel(r'simulated_data.xlsx')
target_column_name = 'Target_y'
5.设置超参数的网格:
param_grid = { 'n_estimators': [100, 200],}
公众号中的所有所有的免费代码都已经下架了,都并入到付费部分里了,付费合集代码和数据的购买通道已经开通,全部合集100元,后续将会持续更新,决定购买请后台私信我,注意只会分享练习数据和代码文件,不会提供答疑服务,代码文件中已经包含了每行代码的完整注释,购买前请确保真的需要!!!