大家好,我是你们的小帅学长。
有一种场景,在论文和报告里出现频率极高:改进前 vs 改进后、实验组 vs 对照组、男 vs 女、模型A vs 模型B……
这时候你真正想表达的,其实不是“各自多高”,而是它们差多少。但很多人还是会用分组柱状图。结果是什么?读者/审稿人要先看左柱,再看右柱,再自己在脑子里做减法。
而有一种图,可以把“减法”直接画出来:哑铃图(Dumbbell plot)。两个点,一条线。差异就在那条线的长度里。
01.为什么哑铃图更直观?
柱状图强调的是“绝对值”。哑铃图强调的是“差值”。
在视觉感知上,柱状图是比较两根长度;哑铃图是比较一条线的长度。
而人类对“线段长度差异”的判断,远比对“面积差异”更敏感。
02.哑铃图适合什么场景?
场景 1:前后变化
政策实施前后、模型优化前后、训练前后指标变化……
哑铃图可以清楚表达“增长”还是“下降”。
场景 2:两组对比
男 vs 女、城市 vs 农村、方法A vs 方法B……
尤其当类别很多时(比如 8–15 个),哑铃图会比柱状图清爽得多。
场景 3:强调改进幅度
如果你想表达:新方法整体提升明显,那哑铃图能把“提升方向”视觉化。
03.哑铃图的结构逻辑
一张标准的哑铃图包含:左点(组1)、右点(组2)、中间连接线(差异)
它本质上是:横向点图 + 连线
如果你熟悉上一期的Dot plot,那你会发现哑铃图只是“多一个点+一条线”。
04.哑铃图模板(排序 + 水平布局)
import osimport numpy as npimport matplotlib as mplimport matplotlib.pyplot as pltfrom matplotlib import font_manager as fmfrom matplotlib.ticker import MaxNLocator, FormatStrFormatter# == 字体设置 ==win_fonts = r"C:\Windows\Fonts"for p in [ os.path.join(win_fonts, "times.ttf"), os.path.join(win_fonts, "timesbd.ttf"), os.path.join(win_fonts, "timesi.ttf"), os.path.join(win_fonts, "simsun.ttc"),]: if os.path.exists(p): try: fm.fontManager.addfont(p) except Exception: passmpl.rcParams["font.family"] = ["Times New Roman", "SimSun"]mpl.rcParams["axes.unicode_minus"] = FalseOUT_DIR = r"D:\py_figs "os.makedirs(OUT_DIR, exist_ok=True)# == 示例数据 ==labels = [ "Model A / 模型A", "Model B / 模型B", "Model C / 模型C", "Model D / 模型D", "Model E / 模型E"]before = np.array([0.78, 0.82, 0.75, 0.80, 0.77])after = np.array([0.86, 0.88, 0.83, 0.85, 0.84])# == 按差值排序 ==diff = after - beforeorder = np.argsort(diff)[::-1]labels_s = [labels[i] for i in order]before_s = before[order]after_s = after[order]fig, ax = plt.subplots(figsize=(7.2, 4.2))y = np.arange(len(labels_s))# 连接线for yi, b, a in zip(y, before_s, after_s): ax.plot([b, a], [yi, yi], linewidth=1.5)# 两端点ax.scatter(before_s, y, s=70, label="Before / 优化前")ax.scatter(after_s, y, s=70, label="After / 优化后")ax.set_yticks(y)ax.set_yticklabels(labels_s, fontsize=11)ax.set_xlabel("Accuracy / 准确率", fontsize=12)ax.set_title("Dumbbell Plot:两组差异一眼看清", fontsize=14)ax.invert_yaxis()ax.set_xlim(left=0.7)ax.xaxis.set_major_locator(MaxNLocator(nbins=6))ax.xaxis.set_major_formatter(FormatStrFormatter("%.2f"))ax.legend(frameon=False)out_path = os.path.join(OUT_DIR, "dumbbell_plot_basic.jpg")fig.savefig(out_path, dpi=300, bbox_inches="tight", pad_inches=0.05)plt.close(fig)print("Saved:", out_path)

05.哑铃图 vs 分组柱:谁更好?
分组柱强调“两个柱子”,哑铃图强调“中间那条线”。
如果你真正关心的是“差多少”,就不要再让读者自己做减法——把差异画出来。哑铃图让对比更诚实,也更直接。
下一篇我们进入分布表达:《直方图》。很多人画直方图时最头疼的问题是:bin 到底该设多少?太少看不出结构,太多全是噪声。下一篇,我会给你一套清晰可用的 bin 选择规则,而不是“凭感觉”。
——期待你的关注——
往期内容:
用Python做科研级画图——点图替代柱状
用Python做科研级画图——分组对比
用Python做科研级画图——折线图基础模板
用Python做科研级画图——配色与色带:连续离散怎么选
用Python做科研级画图——标注与强调:箭头/框/高亮怎么用