未经授权不得转载或抄袭。
转载合作请后台联系授权,侵权必究。


哑铃图(Dumbbell Plot),由两个圆点(代表开始值和结束值)和一条连接线段(代表变化或差异)组成,专门用于展示两个数据点之间的变化或差异。
背景带:在 x 轴方向用背景带表示起点组和终点组的均值 ± 标准差(SD)范围,直观展示两组数据的整体分布集中程度。
均值虚线:用两条垂直虚线分别标注起点组和终点组的算术平均值,为比较两组数据的整体水平提供基准。
注:本文所用数据均为模拟数据,不具有实际意义。
01
全局设置
字体设置
plt.rcParams['font.family'] = 'Arial'plt.rcParams['font.size'] = 15plt.rcParams['axes.unicode_minus'] = False
颜色配置
# ---- 起点(第一数值列)颜色 ----start_colors = {'outer': '#8CC7CB','inner': '#CFE6E7','edge': '#30B2BC','mean': '#8CC7CB','error_band': '#DAEDEE'}# ---- 终点(第二数值列)颜色 ----end_colors = {'outer': '#EEDCA0','inner': '#F8F0D8','edge': '#E7CB75','mean': '#EEDCA0','error_band': '#FBF6E4'}# ---- 差值标签颜色 ----diff_color = '#30B2BC'# ---- 坐标轴与网格 ----spine_color = 'lightgray'grid_alpha = 0.2y_ticklabels_color = '#30B2BC'legend_text_color = 'black'# ---- 其他几何参数 ----connector_height = 0.06dot_screen_radius_px = 15diff_label_y_offset = 0.1
02
读取数据 + 基础计算
# 读取数据df = pd.read_csv('data2.csv')# 自动获取列名cat_col = df.columns[0] # 分类列val_col1 = df.columns[1] # 起点数值列val_col2 = df.columns[2] # 终点数值列# 计算差值 + 生成Y轴坐标(每行一个位置)df['diff'] = df[val_col2] - df[val_col1]df['y'] = np.arange(len(df))# 计算两组数据的均值和标准差(用于背景填充+均值线)mean_start = df[val_col1].mean()sd_start = df[val_col1].std()mean_end = df[val_col2].mean()sd_end = df[val_col2].std()
03
绘制核心部分
绘制背景误差填充带(均值 ± 标准差)
ax.axvspan(mean_start - sd_start, mean_start + sd_start,color=start_colors['error_band'], alpha=0.5)ax.axvspan(mean_end - sd_end, mean_end + sd_end,color=end_colors['error_band'], alpha=0.5)
绘制连接线
# 创建从起点颜色到终点颜色的渐变色图gradient_start_color = start_colors['outer']gradient_end_color = end_colors['outer']cmap_grad = LinearSegmentedColormap.from_list('grad',[gradient_start_color, gradient_end_color], N=256)# 逐行绘制连接线for _, row in df.iterrows():y = row['y']# 确保矩形 x 范围从左到右(起点值到终点值)if row[val_col1] <= row[val_col2]:extent = (row[val_col1], row[val_col2],y - connector_height / 2, y + connector_height / 2)cmap_range = (0, 1) # 颜色映射范围:左端对应起点颜色,右端对应终点颜色else:extent = (row[val_col2], row[val_col1],y - connector_height / 2, y + connector_height / 2)cmap_range = (1, 0) # 方向相反时翻转颜色范围gradient_rect(ax, extent, cmap_grad, direction=0,cmap_range=cmap_range, alpha=0.9, zorder=2)
绘制起点和终点的圆点
for _, row in df.iterrows():# 起点圆点radial_gradient_circle(ax, row[val_col1], row['y'], dot_screen_radius_px,outer_color=start_colors['outer'],inner_color=start_colors['inner'],n_res=70, zorder=4,edgecolor=start_colors['edge'], edgewidth=1.5)# 终点圆点radial_gradient_circle(ax, row[val_col2], row['y'], dot_screen_radius_px,outer_color=end_colors['outer'],inner_color=end_colors['inner'],n_res=70, zorder=4,edgecolor=end_colors['edge'], edgewidth=1.5)
添加数值标签(起点和终点的具体数值)
for _, row in df.iterrows():# 起点数值标签(位于圆点上方)ax.text(row[val_col1], row['y'] + label_y_offset, f"{row[val_col1]:.1f}",va='bottom', ha='center', color=start_colors['label'], zorder=5)# 终点数值标签ax.text(row[val_col2], row['y'] + label_y_offset, f"{row[val_col2]:.1f}",va='bottom', ha='center', color=end_colors['label'], zorder=5)
添加差值标签(变化量,显示在连接线中间)
for _, row in df.iterrows():mid_x = (row[val_col1] + row[val_col2]) / 2 # 连接线中点 X 坐标diff_val = row['diff']# 正值显示 "+X.X",负值显示 "-X.X"label = f"+{diff_val:.1f}" if diff_val > 0 else f"{diff_val:.1f}"ax.text(mid_x, row['y'] + diff_label_y_offset, label,va='bottom', ha='center', color=diff_color, zorder=6)
绘制均值垂直线(虚线)
ax.axvline(mean_start, linestyle='--', color=start_colors['mean'], alpha=0.7,label=f'{val_col1}均值 = {mean_start:.1f}')ax.axvline(mean_end, linestyle='--', color=end_colors['mean'], alpha=0.7,label=f'{val_col2}均值 = {mean_end:.1f}')
“今日分享至此✨”
如有错误或不足之处,欢迎随时在评论区指出,感谢!
完整代码获取,见评论区。
往期推荐

求点赞
求分享
求推荐