importos
importnumpyasnp
importpandasaspd
importmatplotlib.pyplotasplt
frommatplotlib.collectionsimportLineCollection
frommatplotlib.colorsimportLinearSegmentedColormap,Normalize
# 1. 虚构数据 ------------------------------------------------------
df=pd.DataFrame({
"Feature":[
"Body Weight",
"Blood Pressure",
"Heart Rate",
"Cholesterol",
"Blood Glucose",
"BMI",
"Inflammation",
"Sleep Score"
],
"Baseline":[12,18,15,24,21,14,17,20],
"After_Treatment":[48,34,41,52,45,50,39,56]
})
df["Diff"]=df["After_Treatment"]-df["Baseline"]
# 2. 配色方案 ------------------------------------------------------
COLOR={
"left_fill":"#F6A3C2",
"left_edge":"#C2185B",
"right_fill":"#FFE082",
"right_edge":"#F9A825",
"bg_left":"#FCE4EC",
"bg_mid":"#FFF7E6",
"bg_right":"#FFFDE7",
"line_left":"#EC407A",
"line_right":"#FFD54F",
"grid":"#DDDDDD"
}
# 3. 绘制渐变线函数 -----------------------------------------------
defdraw_gradient_line(ax,x1,x2,y,color1,color2,lw=7,alpha=0.85):
"""Draw one horizontal gradient line from x1 to x2."""
xs= np.linspace(x1,x2,120)
ys= np.full_like(xs,y)
points= np.array([xs,ys]).T.reshape(-1,1,2)
segments= np.concatenate([points[:-1],points[1:]],axis=1)
cmap=LinearSegmentedColormap.from_list("line_gradient",[color1,color2])
lc=LineCollection(
segments,
cmap=cmap,
norm=Normalize(0,1),
linewidth=lw,
alpha=alpha,
zorder=2
)
lc.set_array(np.linspace(0,1,len(segments)))
ax.add_collection(lc)
# 4. 主绘图函数 ----------------------------------------------------
defdraw_dumbbell_plot(data):
data=data.copy()
data=data.iloc[::-1].reset_index(drop=True)
y_pos= np.arange(len(data))
x_min=0
x_max=max(data["After_Treatment"].max(),data["Baseline"].max())+8
y_min=-0.8
y_max=len(data)-0.2
mean_left=data["Baseline"].mean()
mean_right=data["After_Treatment"].mean()
fig,ax= plt.subplots(figsize=(11,8))
# 渐变背景
bg= np.linspace(0,1,1200).reshape(1,-1)
bg_cmap=LinearSegmentedColormap.from_list(
"soft_background",
[COLOR["bg_left"],COLOR["bg_mid"],COLOR["bg_right"]]
)
ax.imshow(
bg,
extent=[x_min,x_max,y_min,y_max],
aspect="auto",
cmap=bg_cmap,
alpha=0.95,
zorder=0
)
# 水平辅助线
foryiny_pos:
ax.hlines(
y,
x_min,
x_max,
color="white",
linewidth=1.1,
alpha=0.75,
zorder=1
)
# 渐变连接线
fori,rowindata.iterrows():
draw_gradient_line(
ax,
row["Baseline"],
row["After_Treatment"],
y_pos[i],
COLOR["line_left"],
COLOR["line_right"],
lw=7,
alpha=0.78
)
# 两组散点
ax.scatter(
data["Baseline"],
y_pos,
s=620,
facecolor=COLOR["left_fill"],
edgecolor=COLOR["left_edge"],
linewidth=2.6,
zorder=4,
label="Baseline"
)
ax.scatter(
data["After_Treatment"],
y_pos,
s=620,
facecolor=COLOR["right_fill"],
edgecolor=COLOR["right_edge"],
linewidth=2.6,
zorder=4,
label="After Treatment"
)
# 均值虚线
ax.axvline(
mean_left,
color=COLOR["left_edge"],
linestyle="--",
linewidth=1.7,
alpha=0.8,
zorder=2
)
ax.axvline(
mean_right,
color=COLOR["right_edge"],
linestyle="--",
linewidth=1.7,
alpha=0.8,
zorder=2
)
ax.text(
mean_left,
y_max-0.15,
f"Mean: {mean_left:.1f}",
ha="center",
va="top",
fontsize=12,
fontweight="bold",
color=COLOR["left_edge"]
)
ax.text(
mean_right,
y_max-0.15,
f"Mean: {mean_right:.1f}",
ha="center",
va="top",
fontsize=12,
fontweight="bold",
color=COLOR["right_edge"]
)
# 数值与差值标注
fori,rowindata.iterrows():
y=y_pos[i]
x1=row["Baseline"]
x2=row["After_Treatment"]
mid=(x1+x2)/2
ax.text(
x1,
y+0.24,
f"{x1:.0f}",
ha="center",
va="bottom",
fontsize=12,
fontweight="bold",
color=COLOR["left_edge"]
)
ax.text(
x2,
y+0.24,
f"{x2:.0f}",
ha="center",
va="bottom",
fontsize=12,
fontweight="bold",
color=COLOR["right_edge"]
)
ax.text(
mid,
y-0.25,
f"+{row['Diff']:.0f}",
ha="center",
va="top",
fontsize=11.5,
fontweight="bold",
color="#222222"
)
# 坐标轴设置
ax.set_yticks(y_pos)
ax.set_yticklabels(data["Feature"],fontsize=13,fontweight="bold")
ax.set_xlim(x_min,x_max)
ax.set_ylim(y_min,y_max)
ax.set_xlabel(
"Measurement Value",
fontsize=14,
fontweight="bold"
)
ax.set_ylabel(
"Clinical Features",
fontsize=14,
fontweight="bold"
)
ax.set_title(
"Gradient Dumbbell Plot of Clinical Indicators",
fontsize=19,
fontweight="bold",
pad=24
)
# 图例
leg=ax.legend(
loc="center right",
bbox_to_anchor=(0.98,0.50),
frameon=True,
fontsize=12,
borderpad=0.8
)
leg.get_frame().set_edgecolor("black")
leg.get_frame().set_linewidth(1.0)
leg.get_frame().set_alpha(0.92)
# 美化边框和网格
ax.grid(
axis="x",
linestyle=":",
linewidth=0.9,
color=COLOR["grid"],
alpha=0.85
)
forspineinax.spines.values():
spine.set_linewidth(1.8)
spine.set_color("black")
ax.tick_params(axis="x",labelsize=12,width=1.5)
ax.tick_params(axis="y",width=1.5)
plt.tight_layout()
# 保存到桌面
desktop=os.path.join(os.path.expanduser("~"),"Desktop")
png_path=os.path.join(desktop,"gradient_dumbbell_plot.png")
plt.savefig(png_path,dpi=300,bbox_inches="tight",facecolor="white")
draw_dumbbell_plot(df)
