中国研究生数学建模竞赛(华为杯)学习笔记——AI绘图方法

中国研究生数学建模竞赛(华为杯)学习笔记------AI绘图方法

文章目录

解决字体乱码问题

Matplotlib 中文显示为方框,根因在于默认字体(DejaVu Sans)不含中文字形------所以解法只动字体配置、不动数据和样式:在导入 matplotlib 后用 plt.rcParams'font.sans-serif' = 'SimHei', 'Microsoft YaHei' 指定中文字体,再用 plt.rcParams'axes.unicode_minus' = False 让负号用普通连字符显示,避免换字体后负号再次变成方框。

参考代码

python 复制代码
import matplotlib.pyplot as plt  
from matplotlib import rcParams  

# 设置 Matplotlib 使用支持中文的字体  
rcParams['font.sans-serif'] = ['SimHei']        # 黑体(Windows 默认支持)  
rcParams['axes.unicode_minus'] = False          # 解决负号显示为方块的问题

提升图片的高级感

左图的四张子图虽然都有内容,但缺主题、缺编号、缺连贯的线索,读者看着只能拿到一堆零散信息;右图的优化思路其实只有三个动作------统一总标题("孕期相关因素统计")+ 给子图加 a/b/c/d 编号和子标题 + 统一配色,再在散点图里加边缘直方图(marginal histogram),让点的整体走向和单维分布密度一次说清。真正的高级感不是加阴影、加渐变、加装饰,而是读者不需要你解释,就能沿着图的结构读出建模逻辑------装饰是加法,结构才是乘法。

参考代码

python 复制代码
"""模拟数据复刻四联图。运行:python replicate_figure.py
依赖:pip install numpy matplotlib
所有数据均为人工模拟,不能用于医学推断;四个面板为独立的视觉示例。
"""
from pathlib import Path
import csv
import warnings
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib import font_manager

OUT = Path(__file__).resolve().parent
SEED = 20260907


def setup_style():
    fonts = {f.name for f in font_manager.fontManager.ttflist}
    candidates = ["Microsoft YaHei", "SimHei", "Noto Sans CJK SC", "Source Han Sans SC", "PingFang SC", "WenQuanYi Zen Hei"]
    chinese = next((name for name in candidates if name in fonts), None)
    if chinese is None:
        warnings.warn("未找到中文字体,请安装 Noto Sans CJK SC 或黑体后重新运行。")
    plt.rcParams.update({
        "font.family": "sans-serif",
        "font.sans-serif": ([chinese] if chinese else []) + ["DejaVu Sans"],
        "font.size": 10, "axes.titlesize": 12, "axes.titleweight": "bold",
        "axes.labelsize": 10, "axes.linewidth": 1.1,
        "axes.spines.top": False, "axes.spines.right": False,
        "axes.unicode_minus": False, "xtick.direction": "out", "ytick.direction": "out",
        "svg.fonttype": "none", "pdf.fonttype": 42,
        "savefig.facecolor": "white",
    })


def write_csv(name, header, rows):
    with (OUT / name).open("w", encoding="utf-8-sig", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(header)
        writer.writerows(rows)


def kde(samples, grid, bandwidth=0.42):
    """高斯核密度估计,避免额外依赖 scipy。"""
    z = (grid[:, None] - samples[None, :]) / bandwidth
    return np.exp(-0.5 * z**2).mean(axis=1) / (bandwidth * np.sqrt(2 * np.pi))


def joint_axes(fig, spec, title):
    sub = spec.subgridspec(2, 2, height_ratios=[1, 4], width_ratios=[4, 1], hspace=0.015, wspace=0.015)
    ax = fig.add_subplot(sub[1, 0])
    top = fig.add_subplot(sub[0, 0], sharex=ax)
    right = fig.add_subplot(sub[1, 1], sharey=ax)
    top.set_title(title, loc="left", pad=7)
    top.axis("off")
    right.axis("off")
    return ax, top, right


def scatter_marginals(ax, top, right, x, y, color, histcolor):
    ax.scatter(x, y, s=13, color=color, alpha=0.28, linewidths=0)
    slope, intercept = np.polyfit(x, y, 1)
    xx = np.linspace(x.min(), x.max(), 200)
    ax.plot(xx, slope * xx + intercept, color=color, lw=1.6)
    top.hist(x, bins=28, color=histcolor, edgecolor="white", linewidth=0.4)
    right.hist(y, bins=28, orientation="horizontal", color=histcolor, edgecolor="white", linewidth=0.4)
    ax.axhline(4, color="#ad8984", ls="--", lw=1.2, alpha=0.85)


def main():
    OUT.mkdir(parents=True, exist_ok=True)
    setup_style()
    rng = np.random.default_rng(SEED)
    fig = plt.figure(figsize=(8.4, 6.96), facecolor="white")
    gs = fig.add_gridspec(2, 2, left=0.074, right=0.992, bottom=0.088,
                          top=0.952, height_ratios=[1, 1.30], wspace=0.255, hspace=0.36)

    # a:混合正态分布产生不同形状;各曲线按自身峰值归一化高度。
    ax = fig.add_subplot(gs[0, 0])
    configs = [
        ([3.0, 5.8, 8.6, 10.7], [1.0, 1.1, 0.9, 0.65], [0.25, 0.35, 0.31, 0.09]),
        ([2.7, 4.3, 6.5, 8.7], [0.40, 0.45, 0.52, 0.65], [0.19, 0.23, 0.25, 0.33]),
        ([2.0, 4.0, 6.1, 8.8], [0.95, 0.7, 0.75, 0.90], [0.18, 0.22, 0.34, 0.26]),
        ([1.0, 3.3, 5.3, 7.6], [0.46, 0.74, 0.9, 0.92], [0.14, 0.25, 0.23, 0.38]),
    ]
    colors = ["#d6e6f1", "#a9cbe2", "#85adcb", "#638eb2"]
    grid = np.linspace(0, 13.3, 600)
    all_rows = []
    for i, (means, sigmas, weights) in enumerate(configs):
        component = rng.choice(4, 1600, p=weights)
        sample = rng.normal(np.array(means)[component], np.array(sigmas)[component])
        sample = sample[(sample >= 0) & (sample <= 13.3)]
        density = kde(sample, grid, bandwidth=0.35)
        base = 3 - i
        curve = base + density / density.max() * 0.57
        ax.fill_between(grid, base, curve, color=colors[i], alpha=0.88)
        ax.plot(grid, curve, color=colors[i], lw=1.5)
        all_rows.extend((i + 1, float(value)) for value in sample)
    ax.axvline(4, color="#ad8984", ls="--", lw=1.3)
    ax.text(4.08, 3.41, "4%", color="#ad8984", fontsize=9)
    ax.set(xlim=(0, 13.3), ylim=(-0.18, 3.72), xticks=np.arange(0, 13, 2),
           yticks=[3.1, 2.1, 1.1, 0.1], yticklabels=["第1组", "第2组", "第3组", "第4组"],
           xlabel="Y 染色体浓度(%)")
    ax.tick_params(axis="y", length=0, pad=7)
    ax.set_title("a  BMI 分组的 Y 浓度分布", loc="left", pad=10)
    write_csv("panel_a.csv", ["bmi_group", "y_percent"], all_rows)

    # b:各孕周独立生成样本,误差棒为均值 ± 1.96 × 标准误(近似 95% CI)。
    ax = fig.add_subplot(gs[0, 1])
    weeks = np.arange(12, 25)
    targets = np.array([2.2, 2.8, 3.75, 3.95, 4.9, 5.35, 6.05, 6.75, 7.1, 7.6, 8.35, 8.9, 9.3])
    values = [rng.normal(mu, 0.72, 55) for mu in targets]
    means = np.array([v.mean() for v in values])
    ci = np.array([1.96 * v.std(ddof=1) / np.sqrt(len(v)) for v in values])
    ax.axhline(4, color="#ad8984", ls="--", lw=1.3)
    ax.plot(weeks, means, color="#6d91ac", lw=1.2, zorder=1)
    for i, (week, mean, error) in enumerate(zip(weeks, means, ci)):
        ax.errorbar(week, mean, yerr=error, fmt="o", ms=5, capsize=0,
                    color=plt.cm.Blues(0.16 + 0.80 * i / 12), ecolor="#8ea9b9", elinewidth=1.15)
    ax.set(xlim=(11.4, 24.6), ylim=(1.5, 10), xticks=np.arange(12, 25, 2),
           yticks=np.arange(2, 11), xlabel="孕周(周)", ylabel="Y 浓度均值(%)")
    ax.set_title("b  孕周分箱均值与 95% CI", loc="left", pad=10)
    write_csv("panel_b_raw.csv", ["week", "y_percent"], ((w, float(v)) for w, vals in zip(weeks, values) for v in vals))
    write_csv("panel_b_summary.csv", ["week", "mean", "ci95_half_width", "n"], zip(weeks, means, ci, [55] * len(weeks)))

    # c:孕周与浓度正相关,并保留随机散布。
    ax, top, right = joint_axes(fig, gs[1, 0], "c  孕周与 Y 浓度")
    x = rng.uniform(11, 25, 900)
    y = 1.6 + 0.60 * (x - 11) + rng.normal(0, 1.12, x.size)
    scatter_marginals(ax, top, right, x, y, "#426f96", "#d4e6f2")
    ax.set(xlim=(10.8, 25.2), ylim=(0, 13.5), xticks=np.arange(12.5, 25.1, 2.5),
           yticks=np.arange(0, 13, 2), xlabel="孕周(周)", ylabel="Y 染色体浓度(%)")
    write_csv("panel_c.csv", ["week", "y_percent"], zip(x, y))

    # d:独立构造孕周校正后的浓度,呈现与 BMI 的负相关。
    ax, top, right = joint_axes(fig, gs[1, 1], "d  BMI 与校正浓度")
    bmi = rng.normal(31, 5.2, 1300)
    bmi = bmi[(bmi >= 20) & (bmi <= 47)][:1000]
    adjusted_y = 7.0 - 0.102 * (bmi - 20) + rng.normal(0, 0.97, bmi.size)
    scatter_marginals(ax, top, right, bmi, adjusted_y, "#448985", "#dfefef")
    ax.set(xlim=(20, 47.5), ylim=(-1.2, 9.5), xticks=np.arange(20, 46, 5),
           yticks=np.arange(0, 9, 2), xlabel="BMI(kg/m²)", ylabel="孕周校正 Y 浓度(%)")
    write_csv("panel_d.csv", ["bmi", "adjusted_y_percent"], zip(bmi, adjusted_y))

    for ext in ["png", "svg", "pdf"]:
        fig.savefig(OUT / f"replicated_figure.{ext}", dpi=300)
    fig.savefig(OUT / "preview.png", dpi=120)
    plt.close(fig)
    print(f"已保存图片和模拟数据到:{OUT}")


if __name__ == "__main__":
    main()

复现仓库:https://github.com/kaixin-aa/figure-code/tree/main/examples/001-bmi-y-concentration

提升图片的配色搭配

这张 BMI--孕周妊娠达标概率热力图,把"配色服务变量"这件事讲得非常彻底:原图主体用了彩虹 jet 色带,把一个 0--1 的连续概率硬切成蓝→青→绿→黄→红五六个"伪类别",读者会本能以为每段色带代表一种类型,但这个变量根本没有类别含义;右边的 BMI 分组条又用红/黄/绿/紫四种毫无语义关联的强饱和色硬配,四组之间没有顺序感,眼睛也无法快速定位"第几组属于哪一段";更要命的是,背景这么花,想突出的两条阈值曲线(黑、白)反而被彩噪吞掉了。

优化稿只做三件事------把主体换成单色蓝渐变(Blues),用"越深=越高"的单调直觉替代假的色带分箱;把 BMI 四组改成浅蓝→深蓝的离散色阶,让组间顺序一眼可读;让阈值曲线用黑+红强调线落到安静下来的背景上,瞬间成为全场最显眼的元素。所以选色真正的判断流程只有三步:先识别变量类型(连续单调就用单色渐变、偏离零即变差就用发散色板、有序分类用同色系离散色阶、风险阈值才用强调色),再看颜色是否编码了方向(深=多、浅=少这种直觉是否能直接焊死在色板上),最后强调色只留给最想讲的 1--2 个元素。

出图前只用一句话自检:去掉所有颜色后图还能不能讲清楚故事------能讲清,彩色就是增益;讲不清,彩色就是在掩盖结构问题。好配色的目标从来不是漂亮,而是让数据自己说话。

参考提示词

你是数学建模竞赛的可视化配色助教。下面是一份 BMI-孕周达标概率热图代码,它的问题是使用了默认彩虹色和跳跃的分组颜色。

请只升级"配色系统",不要改变热图数据、概率公式、坐标轴范围、等值线水平、BMI 分组边界和图表结构。

具体要求:

  1. 把 cmap="jet" 改成低饱和、亮度连续、适合论文打印的概率色带。
  2. 颜色语义要清楚:浅色表示低达标概率,深色表示高达标概率。
  3. BMI 分组带使用同一色系的层级颜色,不要红黄绿紫混用。
  4. 80% 和 90% 等概率线只使用少量强调色,不能喧宾夺主。
  5. 白底,坐标轴简洁,中文正常显示。
  6. 不添加大段解释文字,不改变为其他图形。
  7. 输出完整可运行 Python 代码。
  8. 导出 PNG,保存后调用 plt.show()。

提升图片的场景创新性

左边是一张规规矩矩的方框流程------母体采血→血浆 cfDNA 提取→高通量测序→染色体比例计算→最后分支出 13/18/21 染色体筛查和 Y 染色体浓度与胎儿性别判定,干净、规范,但读起来像一张抽象骨架,评委看不出"这是在解决一个真实的产前检测临床问题";右边把同一套数据流嵌进了真实场景------左侧的孕妇与胎儿插画交代问题域("你在帮谁"),中间的采血管、测序读段与质控流程交代方法步骤("你怎么做"),右侧的染色体核型(13/18/21 + XY)和 QC 报告圈交代产出("你最终给人什么答案"),一层视觉就把问题---方法---结果全部铺满。真正适合论文的场景融合图,核心不在美术而在这三层结构:背景层放领域真实元素(孕妇、车流、风机、河流、电网),流程层放方法步骤并就近贴标签,产出层放具体可量化的对象(染色体编号、车流量、发电量、污染指标),所有元素必须服务于"讲清楚你在解决什么"。

参考提示词

你是数学建模竞赛的论文图设计助教。下面是一份普通 NIPT 流程框图代码,它能说明步骤,但缺少题目背景对象和医学机制图的视觉表达。

请只升级"赛题场景融合",重点放在 NIPT 检测流程和题目背景,不要把推荐检测时点作为主图重点,不要编造新检测结论。

请把普通流程框图升级为医学机制示意图:

  1. 左侧绘制孕妇/胎儿或子宫胎儿示意,作为题目背景对象。
  2. 中间绘制母体外周血样管、血浆 cfDNA、NIPT、测序读段、染色体比例检测和 Y 染色体浓度判定。
  3. 右侧使用简洁虚线结果框,呈现 13/18/21 染色体 DNA 片段比例异常、Y 染色体浓度/胎儿性别辅助判定、样本质控与筛查结论。
  4. 图内只用短标签,不写大段说明文字。
  5. 白底、医学论文示意图风格,颜色克制,箭头清楚。
  6. 不做宣传海报,不使用装饰性背景。
  7. 输出完整可运行 Python 代码。
  8. 导出 PNG,保存后调用 plt.show()。
  9. 只输出代码,不要解释。

学习参考

https://www.bilibili.com/video/BV1SMhV6PEQC/?spm_id_from=333.1007.tianma.1-1-1.click\&vd_source=b305d6a8930c4bc504711f7d564a096f
https://github.com/kaixin-aa/figure-code/tree/main/examples/001-bmi-y-concentration

相关推荐
龙亘川19 分钟前
AI + 人社新范式:智慧人社系统如何为民生治理数字化难题提供帮助
人工智能·智慧城市·数据可视化·政务
水如烟19 分钟前
孤能子视角:蓝星文明篇·市——交换机制的运行化:从偶发交换到日常运行的制度化
人工智能
技灵AI24 分钟前
Wan 3.0 API怎么做多参考商品视频?从图片、视频、音频分工到30秒交付
人工智能·prompt·aigc·音视频·wan 3.0
武子康25 分钟前
CLAUDE.md 引用 AGENTS.md 后,两边真的读到同一套规则吗?
人工智能·llm·agent
2601_9499506331 分钟前
练题簿在线练题全流程:从资料导入到模拟考试与错题复盘
学习·考研·小程序·刷题·小程序推荐
动恰客流统计36 分钟前
线下零售数字化浪潮下,客流统计的3个核心发展趋势
大数据·前端·人工智能
johnsong42 分钟前
效率的边界:当推理突破遇见语言革命
人工智能·语言模型
染指111043 分钟前
119.Agent-LangChain核心组件-Runtime运行时
人工智能·langchain·agent
2601_9653842644 分钟前
口腔黏膜清洁的工程化路径:从表面形貌表征到清洁策略匹配
经验分享·笔记
DevNo1 小时前
英文会议音视频转中文纪要:我近期的几款工具使用记录
人工智能