当前位置:首页>python>玻璃质感散点图 | Python

玻璃质感散点图 | Python

  • 2026-10-11 06:45:42
玻璃质感散点图 | Python
今天分享一种“玻璃质感散点图”的Python绘制方法。传统散点图过于单调,我们将将阴影、彩色光晕、半透明主体、深色轮廓、内部柔光和白色高光逐层叠加,让普通圆形散点呈现出轻微悬浮、通透且具有立体感的效果。
测试的话我们采用ANN真实值—预测值对比图为例,使用蓝色和粉色区分训练集与测试集,并添加1∶1参考线以及R²、RMSE和MAE等辅助信息。玻璃质感散点的绘制函数可以独立复用,只需替换横纵坐标数据和颜色,就能应用到相关性分析、模型预测、分组对比等其他科研图形中。
最终图片会同时保存为600 dpi PNG和可编辑PDF
示例数据:
数据代码均文末免费获取,无套路!

01

导入绘图库并设置数据路径
首先导入路径管理、数值计算、表格处理和绘图需要的库。代码只需要Matplotlib、NumPy和pandas,不依赖scikit-learn。
如果尚未安装相关库,可以在命令行中执行:
pip install matplotlib numpy pandas
脚本会以当前Python文件所在位置作为根目录,从data文件夹读取figure_a_scatter.csv,并将结果保存到result文件夹中。
因此,建议将文件整理为以下结构:
plot_A_python.pydata/figure_a_scatter.csvresult/
其中result文件夹不需要提前创建,程序运行时会自动生成。
”””Reproduce panel A: observed versus ANN-predicted concentrations.The marker is deliberately drawn in several layers (halo, drop shadow,translucent body, rim and a small specular highlight) to reproduce the soft,glass-like points in the reference figure.公众号、小红薯、dou音:地数空间  -微信小程序:地学小助手作者:太阳花🌻
”””from pathlib import Pathimport matplotlib.pyplot as pltfrom matplotlib import transformsimport numpy as npimport pandas as pdROOT = Path(__file__).resolve().parentDATA_FILE = ROOT / ”data” / ”figure_a_scatter.csv”RESULT_DIR = ROOT / ”result”

02

统一设置期刊绘图风格
set_paper_style函数用于控制整张图片的字体、字号、坐标轴粗细、刻度方向和输出格式。
字体优先使用Times New Roman;如果当前电脑没有安装,则依次尝试Times和DejaVu Serif。横纵坐标标题设置为15号,刻度标签设置为11.5号,整体更加接近期刊图件的排版比例。
pdf.fonttype和ps.fonttype被设置为42,可以在PDF中嵌入TrueType字体,方便后续使用Adobe Illustrator等软件继续编辑。
def set_paper_style() -> None:    ”””Set a compact journal-figure style shared by the whole panel.”””    plt.rcParams.update(        {            ”font.family”: ”serif”,            ”font.serif”: [”Times New Roman”, ”Times”, ”DejaVu Serif”],            ”mathtext.fontset”: ”custom”,            ”mathtext.rm”: ”Times New Roman”,            ”mathtext.it”: ”Times New Roman:italic”,            ”mathtext.bf”: ”Times New Roman:bold”,            ”mathtext.cal”: ”Times New Roman”,            ”mathtext.default”: ”regular”,            ”font.size”: 11.5,            ”axes.labelsize”: 15,            ”axes.labelweight”: ”normal”,            ”axes.linewidth”: 1.05,            ”xtick.labelsize”: 11.5,            ”ytick.labelsize”: 11.5,            ”xtick.direction”: ”out”,            ”ytick.direction”: ”out”,            ”xtick.major.size”: 4.5,            ”ytick.major.size”: 4.5,            ”xtick.major.width”: 1.0,            ”ytick.major.width”: 1.0,            ”pdf.fonttype”: 42,            ”ps.fonttype”: 42,            ”savefig.facecolor”: ”white”,            ”axes.facecolor”: ”white”,        }    )

03

绘制具有玻璃质感的散点
普通的scatter函数通常只能绘制单层散点,而参考图中的点具有阴影、透明度、轮廓和高光,看起来更有立体感。
glass_scatter函数对同一组坐标连续绘制五层图形。第一层是向右下方轻微偏移的深色阴影;第二层是面积稍大的彩色光晕;第三层是带深色边缘的半透明主体;第四层是在散点内部增加柔和的白色区域;第五层是向左上方偏移的小型白色高光。
通过多层标记叠加,最终形成类似半透明玻璃球的效果。size控制散点整体大小,color控制主体颜色,edge控制外轮廓颜色。
def glass_scatter(    ax: plt.Axes,    x: np.ndarray,    y: np.ndarray,    color: str,    edge: str,    size: float = 62,    zorder: float = 3,) -> None:    ”””Draw softly translucent, outlined markers with a tiny highlight.”””# A subtle down-right offset makes the translucent circle feel lifted.    shadow_offset = transforms.ScaledTranslation(        0.90 / 72, -1.00 / 72, ax.figure.dpi_scale_trans    )    ax.scatter(        x,        y,        s=size * 1.22,        color=”#17242b”,        alpha=0.13,        linewidths=0,        transform=ax.transData + shadow_offset,        zorder=zorder - 0.20,        clip_on=True,    )# Diffuse coloured halo, then a translucent body and a crisp dark rim.    ax.scatter(        x, y, s=size * 1.48, color=color, alpha=0.10, linewidths=0,        zorder=zorder - 0.15,    )    ax.scatter(        x,        y,        s=size,        facecolor=color,        edgecolor=edge,        alpha=0.72,        linewidth=0.85,        zorder=zorder,    )    ax.scatter(        x, y, s=size * 0.48, facecolor=”white”, edgecolor=”none”,        alpha=0.075, zorder=zorder + 0.05,    )    highlight_offset = transforms.ScaledTranslation(        -1.25 / 72, 1.30 / 72, ax.figure.dpi_scale_trans    )    ax.scatter(        x,        y,        s=max(size * 0.105, 3.2),        color=”white”,        alpha=0.58,        linewidths=0,        transform=ax.transData + highlight_offset,        zorder=zorder + 0.1,        clip_on=True,    )

04

单独绘制图例中的玻璃标记
由于数据点使用了多层叠加效果,Matplotlib的普通图例无法完整保留这种玻璃质感。因此,代码没有直接使用legend函数,而是专门建立glass_legend_marker函数。
该函数与真实散点使用相同的阴影、光晕、主体、轮廓和高光结构,只是坐标系统从数据坐标改为坐标轴相对坐标。这样可以精确控制训练集和测试集图例在图内的位置。
def glass_legend_marker(    ax: plt.Axes,    x: float,    y: float,    color: str,    edge: str,    size: float = 53,) -> None:    ”””Draw an axes-coordinate key with the same rim and highlight as the data.”””    shadow_offset = transforms.ScaledTranslation(        0.78 / 72, -0.82 / 72, ax.figure.dpi_scale_trans    )    ax.scatter(        [x], [y], transform=ax.transAxes + shadow_offset,        s=size * 1.22, color=”#17242b”, alpha=0.13, linewidths=0,        clip_on=False, zorder=5.80,    )    ax.scatter(        [x], [y], transform=ax.transAxes, s=size * 1.48,        color=color, alpha=0.10, linewidths=0, clip_on=False, zorder=5.85,    )    ax.scatter(        [x], [y], transform=ax.transAxes, s=size,        facecolor=color, edgecolor=edge, alpha=0.72, linewidth=0.85,        clip_on=False, zorder=6.00,    )    ax.scatter(        [x], [y], transform=ax.transAxes, s=size * 0.48,        facecolor=”white”, edgecolor=”none”, alpha=0.075,        clip_on=False, zorder=6.05,    )    highlight_offset = transforms.ScaledTranslation(        -1.05 / 72, 1.08 / 72, ax.figure.dpi_scale_trans    )    ax.scatter(        [x], [y], transform=ax.transAxes + highlight_offset,        s=max(size * 0.105, 3.2), color=”white”, alpha=0.58,        linewidths=0, clip_on=False, zorder=6.10,    )

05

自动计算三种模型评价指标
metrics函数根据真实值和预测值计算R²、RMSE和MAE。
R²反映模型对观测值变化的解释能力,越接近1,说明预测值与真实值整体越一致。
RMSE是均方根误差,对较大的预测偏差更加敏感;MAE是平均绝对误差,更直观地反映模型平均偏离真实值的程度。RMSE和MAE越小,通常表示预测误差越低。
这里直接使用NumPy按照公式计算,不需要额外调用scikit-learn。需要注意,当某一组所有观测值完全相同时,R²的分母会变为0,因此实际数据应具有一定变化范围。
def metrics(observed: np.ndarray, predicted: np.ndarray) -> tuple[float, float, float]:    ”””Return R-squared, RMSE and MAE using the definitions in the source data.”””    residual = predicted - observed    r_squared = 1.0 - np.sum(residual**2) / np.sum((observed - observed.mean()) ** 2)    rmse = np.sqrt(np.mean(residual**2))    mae = np.mean(np.abs(residual))    return r_squared, rmse, mae

06

读取预测数据并建立画布
main函数是整套程序的执行主体。程序首先调用前面的期刊风格设置,然后读取CSV数据并自动创建结果文件夹。
输入数据需要包含三列:
set:标记样本属于Training还是Test;
observation_mg_per_L:实际观测值;
ann_prediction_mg_per_L:ANN模型预测值。
图形尺寸被设置为6.0×4.95英寸,并通过subplots_adjust调整四周留白。set_box_aspect设置绘图区宽高比例,使最终坐标框与参考图一致。
def main() -> None:    set_paper_style()    data = pd.read_csv(DATA_FILE)    RESULT_DIR.mkdir(parents=True, exist_ok=True)# The reference panel's plotting box is distinctly wider than tall# (approximately 1.24:1), even though both axes cover the same 0--10 range.    fig, ax = plt.subplots(figsize=(6.0, 4.95), constrained_layout=False)    fig.subplots_adjust(left=0.145, right=0.985, bottom=0.135, top=0.945)    ax.set_box_aspect(1 / 1.24)

07

添加1∶1参考线并绘制两组散点
灰色对角线表示理想状态下“预测值等于真实值”。散点落在线上说明预测完全准确;位于参考线上方表示模型高估,位于参考线下方则表示模型低估。
训练集使用蓝色,测试集使用粉红色。程序根据set列筛选两组数据,再分别调用glass_scatter函数完成绘制。
需要注意,CSV中set列的内容必须准确填写为Training或Test,包括大小写也要保持一致,否则程序无法识别对应数据。
# Thin, pale 1:1 reference line, visually behind every point.    ax.plot([0, 10], [0, 10], color=”#c9cdd0”, lw=1.05, zorder=0)    styles = {        ”Training”: {”color”: ”#2185a7”, ”edge”: ”#12617d”},        ”Test”: {”color”: ”#ca6880”, ”edge”: ”#a74361”},    }    for set_name in (”Training”, ”Test”):        group = data.loc[data[”set”] == set_name]        glass_scatter(            ax,            group[”observation_mg_per_L”].to_numpy(),            group[”ann_prediction_mg_per_L”].to_numpy(),            **styles[set_name],        )

08

设置坐标轴范围和标题
参考图的横纵坐标范围均为0—10,主刻度间隔为2。横坐标表示实际观测浓度,纵坐标表示ANN预测浓度,单位均为mg/L。
代码关闭顶部和右侧刻度,但保留完整的四边坐标框。所有边框统一设置为深灰色,线宽为1.05,使图形保持简洁、规范的期刊风格。
如果自己的数据范围并不是0—10,需要同步修改set_xlim、set_ylim、set_xticks、set_yticks以及前面的1∶1参考线坐标。
ax.set_xlim(0, 10)    ax.set_ylim(0, 10)    ax.set_xticks(np.arange(0, 11, 2))    ax.set_yticks(np.arange(0, 11, 2))    ax.set_xlabel(”Observation (mg/L)”, labelpad=8)    ax.set_ylabel(”ANN Prediction (mg/L)”, labelpad=8)    ax.tick_params(top=False, right=False, pad=3)    for spine in ax.spines.values():        spine.set_color(”#303437”)        spine.set_linewidth(1.05)

09

分别计算训练集与测试集指标
程序再次按照set列提取训练集和测试集,并调用metrics函数计算两组数据的R²、RMSE和MAE。
训练集指标主要反映模型对参与训练样本的拟合程度,测试集指标则更能说明模型对新样本的泛化能力。如果训练集表现非常好,而测试集指标明显下降,可能意味着模型存在过拟合。
评价模型时不能只看R²,还要结合RMSE、MAE、训练集与测试集差异,以及散点在1∶1线两侧的分布共同判断。
# Stacked in-panel labels match the compact layout of the reference panel.    text_x = 0.078    legend_x = 0.020    training = data.loc[data[”set”] == ”Training”]    test = data.loc[data[”set”] == ”Test”]    train_stats = metrics(        training[”observation_mg_per_L”].to_numpy(),        training[”ann_prediction_mg_per_L”].to_numpy(),    )    test_stats = metrics(        test[”observation_mg_per_L”].to_numpy(),        test[”ann_prediction_mg_per_L”].to_numpy(),    )

10

标注训练集图例和评价指标
训练集图例被放置在坐标轴内部左上方。程序先绘制一个蓝色玻璃质感标记,再添加“Training set”文字,随后将训练集的R²、RMSE和MAE按照两行形式显示。
所有指标均由前面的计算结果自动生成并保留两位小数,因此更换输入数据后不需要手动修改图中文字。
text_x和legend_x分别控制文字与图例点的横向位置,0.955和0.902等参数用于控制纵向位置。
glass_legend_marker(        ax, legend_x, 0.955,        styles[”Training”][”color”], styles[”Training”][”edge”],    )    ax.text(        text_x, 0.955, ”Training set”, transform=ax.transAxes,        ha=”left”, va=”center”, fontsize=10.7, color=”#151719”,    )    ax.text(        text_x, 0.902,        rf”$R^2$={train_stats[0]:.2f}  RMSE={train_stats[1]:.2f}” ”\n”        rf”MAE={train_stats[2]:.2f}”,        transform=ax.transAxes, ha=”left”, va=”top”, fontsize=10.7,        linespacing=1.18, color=”#151719”,    )

11

标注测试集图例和评价指标
测试集图例位于训练集信息下方,采用粉红色玻璃标记。测试集的R²、RMSE和MAE同样会自动写入图片。
将两组指标纵向排列,可以在不额外增加图例框的情况下,直接比较模型在训练集和测试集上的表现,同时避免图例遮挡主要散点。
glass_legend_marker(        ax, legend_x, 0.770,        styles[”Test”][”color”], styles[”Test”][”edge”],    )    ax.text(        text_x, 0.770, ”Test set”, transform=ax.transAxes,        ha=”left”, va=”center”, fontsize=10.7, color=”#151719”,    )    ax.text(        text_x, 0.717,        rf”$R^2$={test_stats[0]:.2f}  RMSE={test_stats[1]:.2f}” ”\n”        rf”MAE={test_stats[2]:.2f}”,        transform=ax.transAxes, ha=”left”, va=”top”, fontsize=10.7,        linespacing=1.18, color=”#151719”,    )

12

添加子图编号并导出结果
代码在绘图区左上角外侧添加“(A)”子图编号,便于后续与其他模型或其他指标图组合成多面板图。
最终图片会分别保存为figure_A_python.png和figure_A_python.pdf。其中PNG按照600 dpi输出,可以直接用于论文和公众号;PDF保留矢量文字与图形,适合后期排版。
保存完成后主动关闭画布,避免批量生成多张图时占用过多内存。程序还会在控制台输出图片位置,以及训练集和测试集的具体评价指标,方便核对。
ax.text(        -0.095, 1.035, ”(A)”, transform=ax.transAxes, ha=”left”, va=”center”,        fontsize=17, fontweight=”normal”, color=”#17191b”, clip_on=False,    )    png_file = RESULT_DIR / ”figure_A_python.png”    pdf_file = RESULT_DIR / ”figure_A_python.pdf”    fig.savefig(png_file, dpi=600)    fig.savefig(pdf_file)    plt.close(fig)    print(f”Saved: {png_file}”)    print(f”Saved: {pdf_file}”)    print(        f”Training: R2={train_stats[0]:.3f}, RMSE={train_stats[1]:.3f}, ”        f”MAE={train_stats[2]:.3f}”    )    print(        f”Test: R2={test_stats[0]:.3f}, RMSE={test_stats[1]:.3f}, ”        f”MAE={test_stats[2]:.3f}”    )

13

设置脚本运行入口
最后通过Python标准入口判断调用main函数。这样直接运行plot_A_python.py时会执行完整绘图流程;如果以后将该文件作为模块导入其他Python程序,则不会自动执行main函数。
if __name__ == ”__main__”:    main()
实际使用时需要修改的内容
第一,将预测数据保存为data/figure_a_scatter.csv,或者直接修改DATA_FILE指向自己的CSV文件。
第二,输入数据需要包含set、observation_mg_per_L和ann_prediction_mg_per_L三列。
第三,set列中的训练集必须写为Training,测试集必须写为Test。
第四,如果变量单位不是mg/L,需要修改横纵坐标标题中的单位。
第五,如果数据范围不是0—10,需要同时修改横纵坐标范围、刻度和1∶1参考线。
第六,可以在styles中修改训练集和测试集的颜色,也可以通过glass_scatter函数中的size和alpha参数调整散点大小与透明度。
END
“今日分享至此✨”
完整代码与测试数据免费无套路在微信小程序“地学小助手”直接免费获取!
免费分享,希望大家点点赞 与关注 支持一下!

最新文章

随机文章