下载地址
https://www.zhengdianxue.com/wp-content/uploads/skills/user_aabe55f8--prism-style-plot.zip将提示词发送给你的 AI 安装该 skills
请根据 https://skillhub.cn/install/skillhub.md,安装 @user_aabe55f8/prism-style-plot。按 GraphPad Prism 的作图逻辑绘制投稿级科研图表。支持柱状图、箱线图、小提琴图、分组柱状图、XY 散点线图、生存曲线、饼图等 8 种数据表类型对应的图形。内置统计检验流程(t 检验、ANOVA + Tukey 事后比较、log-rank 等),自动完成显著性标注与精确 p 值输出。所有图表默认叠加个体数据点、使用色盲安全配色(Okabe-Ito)、L 形坐标轴、统一无衬线字体,符合 Nature/Cell 等期刊规范。
用户只需上传实验数据并描述需求,即可获得可直接进论文的 PNG(300 dpi)+ PDF 矢量图及完整统计报告。 权限声明 本技能仅运行于本地环境,不联网、不需要任何外部 API 密钥或账号授权。 读取本地文件:读取用户提供的实验数据(CSV / Excel / 文本表格) 写入本地文件:在输出目录生成图片(PNG / PDF)和 Markdown 统计报告 安装 Python 依赖:在隔离虚拟环境中安装 matplotlib、seaborn、scipy、pandas、numpy 等绘图与统计库 执行本地脚本:运行技能内置的 Python 脚本生成图表 使用说明(可选但推荐) 上传或粘贴你的实验数据(CSV / Excel 均可) 用自然语言描述想要的图形类型,例如:「画一张柱状图,三组比较」「做一张生存曲线」 技能会自动识别数据表类型、选择合适的统计方法、生成 Prism 风格图表 输出包含:PNG 预览图、PDF 投稿矢量图、完整统计报告(含 Figure Legend)、精确 p 值
阅读全文 (展开查看完整说明)
概述
Prism Style Plot
概述
把 GraphPad Prism 的作图心智模型翻译成 Python(matplotlib + seaborn + scipy):先看数据是什么表,再决定画什么图和做什么统计,最终输出符合期刊规范的投稿级图表。目标是让不会写代码的科研人员,说出"我要一张 Prism 那种图",就能得到一张可以直接进论文的图。
你的工作方式
🔴 底线(压倒一切):图和统计的正确性 > 美观 > 效率。 统计方法选错、p 值/星号标错、图形与数据不符是事故;样式瑕疵只是瑕疵。任何改动(含本技能自身的迭代优化)不得损害统计方法选择的正确性与图形呈现的正确性;拿不准时回对话定夺,不静默取舍。
GraphPad 的精髓不是某个图形,而是五件事,这五件事贯穿本技能所有流程:
- 数据表驱动,而不是图形驱动。Prism 的第一步永远是选择数据表格式——表的类型决定了你能画什么图、能做什么统计。拿到数据后先识别它属于 8 种表里的哪一种,而不是先问用户"想要什么图"。
- 统计与绘图联动。图上的误差棒和显著性星号不是装饰,而是统计结果的视觉呈现。统计方法由数据结构决定(组数、因素数、是否配对),画图前必须先完成统计。
- 展示原始数据点。现代期刊(Nature 系列、Cell 系列)普遍要求图上叠加个体数据点,纯空心柱状图已不被推荐。本技能所有统计图默认叠加散点。
- 期刊规范优先。L 形坐标轴(去掉上边和右边框)、色盲友好配色、统一无衬线字体、明确标注误差线类型——这些是审稿人看得见的"专业感"。
- 配色与导出。默认
okabe_ito(色盲安全,8 色);另有 npg/aaas/lancet/jama/tol_light/tol_vibrant/set2/dark2 扩展色板(各色板色数与色盲安全口径见 Phase 3「配色」),组数超色板会自动 UserWarning 提示撞色。导出格式硬规则(矢量字体嵌入 / SVG 文字转曲 / TIFF LZW 压缩)见 Phase 4 第 8 条。
环境准备(Phase 0 之前必做,防止反复重装依赖)
每次任务第一步:先运行 scripts/ensure_env.py 得到受管理 venv 解释器路径,之后所有 python xxx.py 都用这个解释器,不要再回到基础解释器。
PY=$(python scripts/ensure_env.py) # 打印 venv 解释器绝对路径
$PY your_plot.py # 之后一律用 $PY 运行绘图脚本
# 要跑 pytest / verify 套件时,加 --dev(额外装 pytest):
PY=$(python scripts/ensure_env.py --dev)ensure_env.py 已做四件关键事(分享给他人才不会重蹈覆辙):
- 只用 venv 自己的解释器判定依赖是否齐全,装过就秒退(约 3 秒、不重新下载),并打印路径。
- venv 路径不硬编码用户名:从
sys.executable动态推导受管理结构<root>/binaries/python/versions/<ver>/python[.exe]→ venv 落在<root>/binaries/python/envs/default,跨机器/跨平台(WindowsScripts、POSIXbin)通用;推导不到时再依次尝试环境变量WORKBUDDY_HOME/CODEBUDDY_HOME,最后才兜底用户级目录。 - 当前解释器若已能 import 全部依赖则直接复用(含"已身处正确 venv"的情况),绝不重复建 venv;且绝不自我嵌套——若当前解释器本身就是 venv 解释器(父目录名
bin/Scripts),直接返回它自己。 - 畸形 venv 自愈:若目标目录存在却缺解释器(历史 bug 留下的双重嵌套产物),自动
rmtree重建;若 venv 内无 pip,先ensurepip引导,且吞掉 ensurepip/pip 的 stdout——因为本脚本 stdout 专门承载"解释器路径",被污染会让PY=$(...)拿到多行垃圾。
依赖清单分两档:
REQUIRED(运行期必需,默认装):matplotlib/scipy/numpy/pandas<3.0/seaborn/statsmodels/openpyxl(读.xlsx与read_excel_sheets的硬依赖,缺了直接ModuleNotFoundError)。REQUIRED_DEV(开发验证用,--dev才装):pytest+lifelines(Cox 回归专用;纯绘图用户不装——引擎在 Cox 路径经_ensure_lifelines()按需自动补装,log-rank 检验零依赖。lifelines 不进 REQUIRED 的原因:它连带拖入 autograd/formulaic 一串依赖,是首次安装最大的体积/耗时/断网失败源)。- pip 安装两级回退:默认源失败自动换镜像源重试(
PRISM_PIP_MIRROR可自定义,默认清华 TUNA);均失败打印中文排查指引并退出非零。 - 批量重出图:数据更新后
$PY scripts/batch_rerun.py(默认./output/,--dir指定、--dry-run预览)一键按各脚本 PARAMS 重跑全部交付图。
⚠️ 致命坑(务必转达给使用者):切勿用基础解释器(如
binaries/python/versions/<ver>/python.exe)去import matplotlib做"装没装"的判定——它看不到 venv 里的包,会误报ModuleNotFoundError,从而每次任务都触发重装。依赖是否存在,一律以 venv 解释器(ensure_env.py的输出)为准。
⚠️ 跑测试前必须先
--dev:pytest不随REQUIRED安装。直接$PY -m pytest tests/会报No module named pytest;先跑PY=$(python scripts/ensure_env.py --dev)即可。
引擎导入(唯一标准姿势)
画图脚本开头必须先把引擎目录挂进 sys.path,否则 from prism_theme import ... 会报 ModuleNotFoundError:
import sys, pathlib
sys.path.insert(0, str(pathlib.Path(r"<技能根目录>") / "scripts")) # ← 唯一需改的一行
import matplotlib.pyplot as plt
import numpy as np
from prism_theme import apply_prism_theme, prism_bars, save_figure<技能根目录>= 本 SKILL.md 所在目录(references/、scripts/的父目录)。找法:技能目录里就是SKILL.md/references//scripts//templates/这几项,与 venv 路径没有祖先关系(venv 在~/.workbuddy/binaries/python/envs/default,技能在~/.workbuddy/skills/prism-style-plot,两者是兄弟目录)——别从解释器路径往上回溯。- 零摩擦首选路径(强制):直接复制
templates/<表型>_template.py到你的工作目录改PARAMS即可——模板的路径定位是自适应的(PRISM_SKILL_DIR环境变量 → 从脚本位置向上找 → 用户级安装目录~/.workbuddy/skills/、~/.codebuddy/skills/、~/.claude/skills/),不需要你改任何路径,复制到任何目录都能直接跑。这是交付脚本的唯一正规来源:recipes/ 片段只是引擎用法参考(缺 GUI 微调台所需的PARAMS块),照抄手写会被emit_run_script(with_launcher=True)的 v2.17.2 硬闸拒绝落盘。 - 环境变量(可选):设
PRISM_SKILL_DIR=<技能根目录>后,上述三种方式都能定位到引擎。 - ⚠️
import matplotlib.pyplot as plt与import numpy as np不能省:引擎的__all__不含plt/np,所以from prism_theme import *拿不到它们;虽然from prism_theme import plt恰好能取到(显式具名导入不受__all__约束),但那依赖引擎内部的 import 顺序,不该依赖——请自己 import。漏 import 直接NameError: name 'plt' is not defined。
Phase 0:识别数据表类型
这是最关键的一步,决定了后续所有动作。读取 references/table-mapping.md,按其中的判别规则把用户数据归类到 8 种表之一:
- XY 表:每个点由 X 和 Y 两个值定义(如剂量-响应、标准曲线)
- Column 表:一个分组变量(Control vs 处理组)
- Grouped 表:两个分组变量(如 时间×药物)
- Contingency 表:计数/频数数据(如 存活 vs 死亡的例数)
- Survival 表:生存时间 + 事件/删失标记
- Parts of whole 表:各部分占整体的比例(饼图)
- Multiple variables 表:每行一个观测、每列一个变量的宽表
- Nested 表:层级嵌套设计(如 方法→教室→学生)
数据来源可能是 CSV/Excel 文件、粘贴的文本表格,或对话中描述的结构。
🔴 CHECKPOINT · 🛑 STOP:表类型判别不确定时,必须暂停,把判别结果和依据展示给用户确认后再继续——表类型认错,后面全错。用户描述已足以判定时(如明确说"三组独立样本")可直接判定,不追问。
🔴 CHECKPOINT · 🛑 STOP(配对性):配对 vs 非配对必须在 Phase 2 之前定下来——它直接决定用
ttest_two_groups(paired=True)还是paired=False,选错会把 p 值和结论一起带偏。判定依据(按序):
- 用户明说"同一批/同一只/配对/重复测量/前后对照" → 配对,直接判定不追问;
- 明说"独立/平行孔/不同批次/每组 n 只动物" → 非配对,直接判定不追问;
- 两者都看不出(如"每组 6 个重复")→ 必须暂停问用户:是"6 个平行孔的独立培养"还是"同一批 6 个样本分成 3 个剂量"?
- 判断依据:同一实验单元(同一只动物 / 同一皿细胞 / 同一个体的多次测量)→ 配对;不同实验单元 → 非配对。
- 提示:剂量梯度实验里"同一批样本分多个剂量"属配对,而"每剂量另取一批平行样本"属非配对——两者结论常不一致,猜错的代价是整个统计口径作废。
默认约定:读表自动设 y 轴标签 — 当数据来自 CSV/Excel 文件、且用户未显式指定 y 轴标签时,自动用数值列表头作为 ylabel 默认值:
read_table(path)读取文件后会返回ylabel(long 格式的 value 列名,已做基础清洗:去换行、合并多余空格;wide 格式无单一数值列名时ylabel=None)。- 拿到
ylabel后直接ax.set_ylabel(res["ylabel"])即可,无需用户再给。 - 用户仍可随时覆盖此默认(显式给标签时优先用用户的)。
- 宽格式(每列一组)因没有"数值列名",
ylabel回退None→ 此时沿用该图型模板默认(如 Column→"Response (a.u.)")或请用户指定。 - 列名常非投稿级(缩写/缺单位),自动 ylabel 只是合理默认,最终投稿标签建议你确认/润色(如补
(fold change))。 - 注意:仅"来自文件"时自动套用;对话里粘贴的数值表不触发此默认(按既有规则处理)。
读表健壮性(坏输入不再直接崩) — read_table / read_excel_sheets 已内建以下容错,不要再手写临时补丁去预处理用户文件:
- 编码自动回退:
utf-8-sig → utf-8 → gbk → gb18030 → big5 → latin-1。Windows 版 Excel「另存为 CSV」默认 GBK,中文表头不再抛UnicodeDecodeError。 - 分隔符自动嗅探:逗号 → 制表符 → 分号 → 竖线。仪器/统计软件导出的 Tab 分隔
.txt可直接读,无需先转 CSV。 - 数值清洗:去首尾空格;千分位
"1,234"→1234(仅"数字,三位数字"形态,不误伤1,5这类欧式小数);ND/—/缺失/NA/null等占位符 → NaN 自动剔除。 - 友好报错(替代裸 traceback):文件不存在、空文件、CSV 改名成
.xlsx、列名/sheet 名写错 → 均抛中文可行动错误,并在提示里列出可用列 / 可用 sheet 名。 - 绝不静默产出错误数据(关键):分组列必须满足"去重取值数 ∈ [2, max(2, 行数//2)]"才算分组变量——否则像「备注」「编号」这类常量/自由文本列会被误判为分组列,静默丢掉真正的实验组(真实 bug:含备注列的宽表被误判为长表,Model 组整列消失且无任何报错)。
- 数值型行索引列(ID / 编号 / 序号)不会被静默当作测量值(v2.16.2):长格式下引擎按"第一个数值列"选测量列——若表格首列是 1..n 的整数序号(Excel 行号 / 样本编号),它会被选成测量列,
ylabel直接变成"ID",画出来的"数据"其实是行号。引擎现会在notes里显式警示(行为不变,公开 API 稳定):⚠️ 该列疑似行索引,请显式传read_table(path, group_col=..., value_col="<测量列>")。- 凡是表里带 ID / 编号 / 序号这类数值列,一律显式传
value_col=——这是零成本、最保险的做法,别依赖自动选列。
- 凡是表里带 ID / 编号 / 序号这类数值列,一律显式传
- 被忽略的列写进
notes:read_table返回值新增"notes": list[str](自动识别的分隔符、被忽略的列名等)。拿到notes后必须原样转述给用户——被忽略的列可能意味着数据没被完整使用,属学术严谨性范畴,严禁静默吞掉。典型写法:
res = read_table(path)
for n in res.get("notes", []):
print("提示:", n) # 或拼进给用户的回复里- 生存数据三列自动识别(v2.15.2):生存数据 CSV 的标准布局就是
time / event / group三列。read_table检测到二值事件列(取值 ⊆ {0,1})时,会把三列原样放进res["survival"],并在notes里写明该列的处理方式;该列不再参与普通分组绘图(否则会被当成第二组数据)。
res = read_table(path)
sv = res["survival"] # {"time": [...], "event": [...], "group": [...]}
fig, ax = plt.subplots()
prism_survival(ax, time=sv["time"], event=sv["event"], group=sv["group"])⚠️ 旧版会把 event 当成"第二个数值列"静默丢掉——画出来的"生存曲线"里全是删失。凡是做 KM,务必检查 res["survival"] 是否存在;不存在说明该表不是标准三列布局,改用 pd.read_csv 显式取列。
Phase 1:选择图形类型
按表类型查 references/table-mapping.md 的映射表,得到该表支持的图型列表。默认选择推荐的"Prism 默认图型"。
🔴 CHECKPOINT · 🛑 STOP:除用户已明确指定图型外,先给出 2-3 个备选供其确认后再出图;用户说"你定/按默认即可"时直接继续,不追问。
| 表类型 | 默认图型 | 常见备选 |
|---|---|---|
| Column | 带散点的柱状图(Mean±SEM) | 箱线图+散点、小提琴图+散点、带散点的线图 |
| Grouped | 分组柱状图+散点 | 分组箱线图、意大利面条图(重复测量时) |
| XY | 散点+连线 | 仅散点、折线图、拟合曲线 |
| Survival | Kaplan-Meier 曲线 | — |
| Contingency | 分组柱状图(计数) | 堆叠柱状图 |
| Parts of whole | 饼图/环形图 | 堆叠柱状图 |
| Nested | 嵌套柱状图 | — |
| Multiple variables | 按用户意图选图 | 散点矩阵、箱线图分面 |
涉及"统计显著性展示"的图(Column/Grouped)默认展示个体散点 + 误差棒 + 星号;纯展示性的图(Parts of whole、Contingency)不标注显著性。映射表里的三个"备选图型"均有实现:堆叠柱 / 百分比堆积 → prism_stacked_bars;水平柱状图(组名长、标签挤)→ prism_horizontal_bars;配对前后连线图 → prism_paired_lines。用法见 Phase 3 速查表与 references/recipes/。
两处常规科研绘图缺口已补齐:① 时间序列折线 + 误差带 → prism_series(个体轨迹用 prism_spaghetti,组均值折线 + mean±SEM 阴影带用 prism_series);② 森林图 / 效应量汇总 → prism_forest(把 cox_regression() 的多组 HR 一次画成点 + 水平 95% CI + 无效线)。另有:prism_survival 的 cumulative=(累积发生率 = 1−KM,同一数据的另一视角)、prism_pairplot 上三角默认镜像散点(upper="mirror")。详见 references/recipes/series.md。
原始记录类图型(钙成像 / 电生理):① 多通道平铺轨迹 → prism_traces(很多条波形逐条错位竖排、互不遮挡,适合 ΔF/F 多细胞轨迹、多通道/多 sweep 原始记录;offset 自动 = 最大峰-谷幅度 × 1.25,y 刻度默认隐藏,可加 scale_bar);② 事件栅格 + PSTH → prism_raster(spike raster / 钙瞬变事件,每个 trial 一行画短竖线,psth=True 自动在上 raster 下 PSTH 的两行画布出图)。与 prism_series/prism_spaghetti 的分工:series 画组均值+误差带(聚合视角)、spaghetti 画个体轨迹叠加误差棒(异质性视角)、traces 画逐条平铺的原始波形(形态视角)。详见 references/recipes/traces.md。
时间序列画布自动宽度:固定 wide(7×5 cm)在时间点多时显局促,故提供 series_figsize()——高度恒定 5 cm、宽度按时间点数自动分档(分档表见 Phase 3 series_figsize() 一节,唯一真源)。prism_series 与 prism_spaghetti 均支持 ax=None 自建画布并自动套用该阶梯,返回值含 "ax" 键;显式优先级为 figsize > shape > 自动阶梯,传 ax 时完全不干预画布。
Phase 2:统计分析
读取 references/statistics-guide.md,按决策树选择统计方法:
- 2 组比较 → t 检验(先判定配对/非配对;非正态或小样本考虑 Mann-Whitney / Wilcoxon 配对检验)
- ≥3 组、单因素 → One-way ANOVA + 事后多重比较(Tukey 默认,方差不齐时用 Welch ANOVA + Games-Howell)
- ≥3 组且前提不满足(任一组 Shapiro-Wilk p<0.05、存在极端离群值、或等级型数据)→ 秩检验:独立样本
kruskal_wallis_test(Kruskal-Wallis + Dunn's test + Holm),配对/重复测量friedman_test(Friedman + 简单效应 Wilcoxon + Holm)。报告入口build_stats_report("kruskal"/"friedman"/"nonparametric")。这一步不是可选项——小样本偏态数据用参数检验会失控,审稿人会直接质疑 - 两个因素 → Two-way ANOVA(含交互作用项,报告主效应与交互效应)
- 生存数据 → log-rank 检验(Kaplan-Meier 曲线;≥3 组自动含整体 χ² 检验 + 两两比较,逐对结果在
prism_survival返回值res["pairwise"]中——未做多重比较校正,正文/图注须注明) - ⚠️ Cox 完全分离防护(小样本生存数据必读):
cox_regression()返回值出现 HR>10⁶ 或 95% CI 含 inf(如[0.00, inf])即判定完全分离——一组全部事件早于另一组全部事件,MLE 不存在,Wald 检验输出假性 p 值(实测:log-rank p=1.79e-4 的数据,Cox Wald 竟给 p=0.995 ns,方向完全相反)。处置(硬性):① 报告与图注注明"完全分离,HR 不可估计";② 结论只采信 log-rank p 与似然比整体overall_p,严禁引用该 Cox Wald p;③ 需要 HR 时外置做 Firth 惩罚化 Cox(引擎未内置),或只报告 log-rank + 风险方向判断 - 计数列联 → 卡方检验 / Fisher 精确检验
- 比较两个/多组的比率(存活率、命中率、阳性率)→
build_stats_report("proportion", ...)。关键是给效应量与区间(率差 Δ+95% CI、RR、OR),卡方只给"有没有差异"。2×2 时 z² ≡ χ²,显著性判断与卡方完全相同,不要为同一个 p 跑两套;k 组则先整体 χ²、显著才做两两(Holm 校正)
硬性规则:多组比较严禁两两 t 检验(成倍放大假阳性),必须 ANOVA + 事后检验一次完成。计算每组 n、均值、误差(SD 或 SEM,默认 SEM 用于柱状图、SD 用于箱线图外的分布展示),生成显著性标注(混合规则,见 Phase 3标注细节):
🔴 CHECKPOINT · 🛑 STOP(统计方法切换):统计方法由数据结构自适应决定,但以下三种情况必须先把方案与依据展示给用户确认,再执行统计:
- 结论可能因方法选择而翻转:前提检验不通过导致改用非参数检验,且改用后显著性与参数检验不一致——此时必须把两套口径与差异展示给用户确认。仅是换了检验族但结论一致时不必打断,直接执行并写明"方法选择依据"(引擎已自动写进报告与图注)。
- 小样本 + 方差不齐:
var_equal="auto"在 n 小时检验力极低(实测 n=3 时 Levene 检出率 0%),若实验设计提示方差不齐(如处理组方差系统性更大),必须显式传var_equal="welch",不要依赖 auto 自动判定;- 生存数据疑似完全分离:
cox_regression()返回 HR>10⁶ 或 95% CI 含 inf 时,必须先告知用户"完全分离、HR 不可估计",再按上面「Cox 完全分离防护」三条处置。不触发上述任一条时直接执行、不要追问——检查点是给"会翻转结论"的决策设的,不是流程摩擦。
⚠️ 报告纪律:一次比较只给一个主 p 值(勿过度设防)
这是本技能最容易被违反的一条。"审稿人会问"不等于"必须提前回答"——把每个可能的质疑都预先塞进报告,反而会制造疑虑。硬性要求:
- 每组比较只报一个主 p 值。不要并列 t 检验 / Welch / MW / 置换 四五个值。
实测教训:曾对同一次比较并列 4 个 p 值(0.066 / 0.106 / 0.029 / 0.029),看似周全,实则暴露"拿不准该用哪个检验"——这本身就是审稿风险。
- 前提检验通过 → 就用参数检验,不要再加非参数/置换佐证。Shapiro/Levene
未拒绝前提时,额外检验纯属噪音。报告里写清"前提检验通过,故采用参数检验"即可。
- 结论对方案敏感时才做敏感性分析,且只针对一个维度(通常是多重比较
校正方案:Tukey vs Holm vs Bonferroni),以表格形式呈现各方案下的 p 值,并声明本报告采用哪个。这不是"再叠加新检验"。
- n 小是局限,不是需要额外检验来弥补的缺口。正确做法是在"局限"中写明
检验力有限、建议扩大 n,并把边缘结果表述为"趋势"。
- p < 0.05(显著):星号
*(p<0.05)、**(p<0.01)、***(p<0.001)、****(p<0.0001) - 0.05 ≤ p < 0.1(边缘显著):直接标注精确 p 值(如
p=0.073、p=0.098) - p ≥ 0.1:不标注(默认不标 ns;
include_ns=True可恢复 "ns" 文本)
统计函数返回值类型(调用时最容易踩的坑):
ttest_two_groups(a, b)(2 组)返回 tuple(statistic, p_value, 方法名),如stat, p, method = ttest_two_groups(a, b);paired=True时同样返回 tuplemann_whitney_u(a, b)(非参数独立样本)返回 tuple(stat, p, "Mann-Whitney U")wilcoxon_signed_rank(a, b)(非参数配对)返回 tuple(stat, p, "Wilcoxon signed-rank")oneway_anova_tukey(groups, labels, var_equal="auto", posthoc="auto")(≥3 组)返回 dict{"method", "anova_p", "pairwise": [(label_i, label_j, p), ...], "f_stat", "df1", "df2", "welch": bool, "posthoc": str, "var_test"}。var_equal(方差不齐处理,对齐 GraphPad Prism 8+):"auto"(默认):先做 Levene(center='median',即 Brown-Forsythe,Prism 推荐)检验,p<0.05 自动切 Welch,否则回落标准 ANOVA"assumed":假设方差齐 → 标准 One-way ANOVA + Tukey HSD(保留以复现旧结果,或用户明确要按方差齐处理)"welch":不假设方差齐 → Welch ANOVA(Welch-Satterthwaite 校正 df),事后由posthoc决定- 为什么把默认从
"assumed"改成"auto":Tukey HSD 的前提是方差齐性,方差不齐时它对 I 类错误的控制失效。实测三组、一组 SD=4 其余 SD=0.3 的设计下,两个分支的结论分歧率达 57.5%、p 值最大相差 19 亿倍——旧默认会把"有差异"静默报成 p=0.27 的"无差异"。默认改 auto 后这类数据自动走 Welch,无需用户记得手动指定。需要锁定旧行为的既有脚本请显式传var_equal="assumed"。 posthoc(仅 Welch 分支生效;assumed 分支永远是 Tukey):"auto"(默认,按 Prism 推荐:max(组内 n) > 50 → Games-Howell,≤50 → Dunnett T3)、"games_howell"(q 统计量 + 学生化极差分布)、"dunnett_t3"(Welch t + 未取整 df + 单步 Sidak 校正,等价 SMM 分布,Dunnett 1980)、"welch_t"(不校正,每对直接 Welch 校正 t 检验 = Prism "Don't correct for multiple comparisons")- ⚠️ 小样本(如 n=3)下 Levene 检验力极低、几乎检不出方差不齐——auto 模式在 n 小时通常不切换;若实验设计或先验知识提示方差不齐(如处理组方差系统性更大),应显式用
var_equal="welch",不要依赖 auto 自动判定 build_stats_report(..., var_equal=..., anova_posthoc=...)同样支持该参数;方差不齐但未切 Welch 时报告会明确标注"下方为假设方差齐性的标准 ANOVA 结果",不再自相矛盾;Welch 分支的校正表(Dunnett T3 / Games-Howell)额外附一列p (Welch t, uncorr)(未做多重比较校正的参考值,审稿人对照友好;anova_posthoc="welch_t"本身即未校正,不重复加列)
build_stats_report("ttest")若检验本身切到非参数(Shapiro p<0.05 或 min(n)<30 → Mann-Whitney U / Wilcoxon 符号秩),报告与图注的"方法选择依据"会点名触发原因(哪一组 / 差值偏离正态);主检验已是非参数时不再重复输出一遍同值的补充检验;仅当主检验为参数但前提提示偏态(如 n≥30 未触发切换)才追加非参数作稳健性对照 + rank-biserial r 效应量,不替换主检验结论- 方法选择依据:本技能的检验方法是自适应的(Shapiro 判正态、Levene/Brown-Forsythe 判方差齐),自动切换虽更稳健,但读者/审稿人无法从图注看出"为什么用了 Mann-Whitney 而不是 t 检验"——审稿人常见的质问就是 "Why was a non-parametric test used? Please justify."。因此
build_stats_report的每一个_report_*现在都会:① 在报告正文输出### 方法选择依据段落(点明前提检验结论 + 触发切换的具体原因);② 把同一句话写入 Figure Legend。format_legend(..., method_rationale="...")是对外入口(新增可选参数,不传时输出与旧版逐字节一致)。配对 t 检验的正态性检验对象是配对差值 d = a − b(不是两组各自正态),报告与切换判定同一口径 - 一组返回值可直接喂给
add_pairwise_brackets(ax, res["pairwise"], labels=...)(后者接收(label_i, label_j, p)元组列表)
统计扩充(完整方案表见 references/statistics-guide.md):
oneway_anova_tukey(groups, labels, posthoc=..., control=...)—— 方差齐分支也支持 7 种事后方案:tukey(默认,全两两)/dunnett/sidak/bonferroni/holm/holm_sidak/none。Dunnett 是最常用的"处理组 vs 对照组"方案(比较次数 k−1 而非 k(k−1)/2,比 Tukey 敏感),对照组用control="Ctrl"(标签名或下标,默认第 1 组)指定;posthoc="auto"一旦同时给了control=就自动切 Dunnett,不会静默忽略。返回值新增效应量eta_sq(η²)与omega_sq(ω²);twoway_posthoc新增partial_eta_sq(偏 η²)- 离群值识别(Prism "Identify outliers"):
detect_outliers_grubbs(values, alpha=0.05)(迭代 ESD)、detect_outliers_robust_fdr(values, q=0.01)(ROUT 等价:稳健中位数 + MAD + BH-FDR,q即 Prism 的 Q 值)。只报告不自动删除——剔不剔由实验者按专业判断决定,并在方法中声明 - RM one-way ANOVA(同一批受试者的单因素重复测量):
rm_oneway_anova(df, subject, within, value)(长格式;GG 球形性校正 + Sidak 事后)。仅"一个组内因素、无组间因素"时用它;含组间因素(如 group × time)仍走build_stats_report("twoway_rm", ...)- ⚠️ 整数型 within 水平已修复:
within列若为整数(Day1 解析成 1)而非常见字符串("0h"/"24h"),旧版会因str键查Int64索引而 KeyError 崩溃。现已统一索引 dtype,整数与字符串水平给出逐位一致的 F/p。传入长表时within用哪种 dtype 都可以。
- ⚠️ 整数型 within 水平已修复:
- 混合设计的组间简单效应:
rm_twoway_between_simple_effects(df, subject, within, between, value, correction="holm")返回{within水平: [(组A, 组B, meanA, meanB, t, df, p_uncorrected, p_adjusted), ...]},即每个时间点内的组间两两比较。- 为什么需要:
rm_twoway_anova的pairwise只给两类——组内时间两两、组间主效应两两(把全部 within 水平混在一起算)。但交互显著时最该看的恰恰缺了:固定 within 水平比较各between 组,它回答"三组从哪一天开始分开/差距是否随时间变化",是解释 Group×Day 交互的直接证据。 - 校正默认 Holm(任意依赖结构下控制 FWER,比 Sidak 稳健),
correction="sidak"可切换。 - 与组间主效应的关键区别:若把同一批鼠的多天观测当独立样本做 t 检验,会低估标准误、虚高显著性(伪重复);正确做法是用每受试者的 within 均值作为分析单元。
- 为什么需要:
- 报告章节自动编号:
head, report_md = next_section(report_md, "结果解读")返回下一个可用编号(扫现有## N.取最大值+1),再report_md += head + 内容。- 为什么需要:
build_stats_report的章节数随图型而变(twoway_rm 是 1–5,其中第 5 节是 Figure Legend)。手工写死"## 5. …"极易与引擎章节撞号——实测连续踩坑三次,其中一次撞上 Figure Legend 导致整节内容被覆盖丢失(静默失效级)。用本函数则无论引擎生成多少节、顺序如何都安全。
- 为什么需要:
- k≥3 非参数检验:
kruskal_wallis_test(groups, labels, posthoc="dunn")返回 dict(h/p/df/epsilon_sq/n_ties/tie_corr/pairwise);friedman_test(...)同结构但含n_subjects且要求各组长度相同;auto_nonparametric_test(..., paired=...)按设计分派并附reason/normality/warning。- ⚠️ 配对设计必须走 Friedman,不能用 Kruskal:重复测量数据里受试者间方差很大时,Kruskal 因全局排名而损失大量检验力(实测同数据 p 大了约 160 倍);反之 Dunn 的 z 近似对配对数据也不成立——配对两两比较用简单效应 Wilcoxon + Holm(
dunn_posthoc(..., paired=True)) - 主检验不显著时,Dunn 的两两 p 不可用于声称差异(最常见误用),报告会自动加警告
- ε² 参考量级:0.01 极小 / 0.06 小 / 0.14 中 / 0.26 大
- ⚠️ 不要用它去"调和"结论分歧:小样本下参数与非参数检验
- ⚠️ 配对设计必须走 Friedman,不能用 Kruskal:重复测量数据里受试者间方差很大时,Kruskal 因全局排名而损失大量检验力(实测同数据 p 大了约 160 倍);反之 Dunn 的 z 近似对配对数据也不成立——配对两两比较用简单效应 Wilcoxon + Holm(
结论不一致是常见且合理的(检验力差异所致),应如实报告主检验结果并在局限中说明,而不是再跑一个检验去把 p 值推向显著一侧——那是选择性报告。
- 精确置换 ANOVA(默认不用——按需取用):
permutation_anova_test(groups, labels=None, max_exact=None, seed=None)返回 dict(statistic/p_value/method/n_perm/exact/observed_f)。不依赖正态与方差齐假设;内部向量化(利用"F 的分母组内 SS 在置换下恒定、只需比较分子 SSB"),3 组×n=4 的 34650 种排列实测 0.17s(朴素写法 29.4s),p 值逐位一致。组合数过大时自动降级为蒙特卡洛,seed可复现。- ⚠️ 使用边界(避免过度设防):
- 前提检验通过时不要用。Shapiro/Levene 未拒绝前提 → 参数检验本就成立,额外报置换 p 值只会让读者困惑(实测教训:曾对同一次比较并列 4 个 p 值——t 检验 0.066、Welch 0.106、MW 0.029、置换 0.029——看似周全,实则暴露"拿不准用哪个检验",反而是审稿风险)。
- 只在两种情况用:① 前提检验不通过且需一个不依赖分布假设的检验;② 审稿人明确要求 permutation test。
- 不要用它去"调和"参数与非参数的结论分歧。分歧本身应如实报告并解释原因(常见于小样本检验力差异),换个检验把 p 值推到显著一侧等于选择性报告。
- 一次比较只给一个主 p 值。敏感性分析(如 Holm vs Tukey)只针对"校正方案"这一个维度,且以表格形式给出,不是再叠加新检验。
- ⚠️ 使用边界(避免过度设防):
- 出图耗时预期(实测):Python 启动 ~1.05s +
import prism_theme~2.3s(matplotlib/scipy 为主;seaborn 与 lifelines 已是惰性导入,仅对应图型才加载)→ ~5s 是固定地板,不可再压。常规单图端到端 6–9s。若某脚本显著慢于此(如 30s+),先查是不是在循环里调了stats.f_oneway/ttest_ind等 scipy 函数——那是统计代码的性能问题,不是引擎问题 - 比例比较:
two_proportion_z_test(x1, n1, x2, n2)返回z/p/diff(率差)+ 95% CI/rr/or_(各带 CI);multiple_proportions_test(counts, ns, labels)为 k 组(整体 χ² 显著才做两两,Holm/Bonferroni/Sidak,可传control=只比参照组)。率差区间默认 Newcombe score(Prism 口径,小样本优于 Wald),method="wald"可取对照。报告入口build_stats_report("proportion", prop_counts=..., prop_ns=..., prop_labels=...)。⚠️ 有 0 格时 OR 自动 Haldane-Anscombe 加 0.5 修正;reference=决定 RR 方向,报告必须写清谁比谁 - 报告层传参:
build_stats_report(..., anova_control="Ctrl", anova_posthoc="dunnett");error_type同时接受代码("sem")与文本("mean ± SD")
若用户提供了自己的统计结果(如已发表的 p 值表),直接使用并跳过本阶段计算,但要在图注中注明统计方法来源。
Phase 3:Prism 风格美化
导入 scripts/prism_theme.py,使用其中封装的主题和辅助函数:
from prism_theme import (apply_prism_theme, recommend_figsize, prism_boxplot,
prism_violin, add_significance_brackets,
add_pairwise_brackets)第一步:确定图形态(GraphPad 的 tall / square / wide 预设)
用 recommend_figsize(table_type, n_groups, shape) 确定画布,三级优先级(预设以 cm 为单位;函数内部已换算成 matplotlib 的 inch,调用方无需关心):
- 用户显式关键词最高优先——说"高/高瘦/tall"、"方/square"、"宽/wide"(中英文别名均可识别)→ 直接用该形态,覆盖一切默认建议;
- 数据表类型默认建议(用户没提形态时自动生效,不必询问)——
recommend_shape(table_type, n_groups):- Column / Grouped:按总分组数分档——<5 组 → tall、5–8 组 → square、>8 组 → wide;
- XY → square、Survival → square、Contingency → wide;
- 补充默认:Parts of whole → square、Multiple variables → wide、Nested → tall;
- 时间序列 / 轨迹类(
timeseries/series/timecourse/trajectory/repeatedmeasures)→ wide(横向更适合多时间点轨迹)。但wide本身只有 7×5 cm,时间点一多仍显局促,故prism_series/prism_spaghetti在ax=None时默认改走series_figsize()自动宽度阶梯(见下),不再用固定 wide;
- 兜底 tall:表类型未识别、或 Column/Grouped 缺分组数时回退 tall(与原
get_figsize默认一致)。
| 形态 | 画布尺寸(cm,宽×高) | 适合场景 |
|---|---|---|
| tall | 3 × 5 | 单面板期刊图、柱状图/箱线图等统计图,高瘦构图让数据主体突出 |
| square | 5 × 5 | 单图均衡展示、散点相关图 |
| wide | 7 × 5 | 多组对比、需要并排的多面板图;时间序列请改走 series_figsize() 自动宽度(见下) |
series_figsize(n_x=None, x_labels=None, long_label_cm=2.0) —— 时间序列专用自动宽度
高度恒定 5 cm,宽度按时间点数分档伸缩;x_labels 平均字符数 >4 时再加宽 long_label_cm(默认 2 cm)以免长标签被裁:
| 时间点数 n | 画布(cm,宽×高) |
|---|---|
| ≤3 | 4 × 5 |
| 4–6 | 7 × 5 |
| 7–9 | 10 × 5 |
| ≥10 | 13 × 5(封顶) |
from prism_theme import prism_series, prism_spaghetti, series_figsize
# 推荐写法:不传 ax,函数自动按时间点数选画布并返回 ax
res = prism_series(df=df, x_col="time", group_col="group", value_col="y")
ax = res["ax"]
# 懒人查询:先看会画多大
series_figsize(n_x=5) # (7.0/2.54, 5.0/2.54) 英寸
series_figsize(x_labels=["0h","6h","12h","24h","48h"]) # 同上,按标签数推断
# 显式覆盖仍然最高优先:figsize > shape > 自动阶梯
prism_series(df=df, figsize=(9, 5)) # 直接指定(inch)
prism_series(df=df, shape="wide") # 强制 7×5 cmfrom prism_theme import recommend_figsize
fig, ax = plt.subplots(figsize=recommend_figsize("column", n_groups=3)) # 3 组 → tall
fig, ax = plt.subplots(figsize=recommend_figsize("grouped", n_groups=6)) # 6 组 → square
fig, ax = plt.subplots(figsize=recommend_figsize("xy")) # XY → square
fig, ax = plt.subplots(figsize=recommend_figsize("contingency")) # → wide
fig, ax = plt.subplots(figsize=recommend_figsize("column", n_groups=3,
shape="wide")) # 关键词覆盖统一执行以下美化(这是 Prism 用户手册和期刊规范的交集):
- L 形坐标轴:隐藏上边框和右边框,只保留左、下
- 样本点布局(镜像均衡抖动,投稿强制规则):默认
layout="jitter"+balanced=True,左右各取n//2点镜像分布,初值采样就避开中线带保证左右对称不偏侧,n 奇数时 1 点随机偏侧(左右差 ≤1)。去规律化:min_sep 微调改用随机方向 + 随机步长(rng.choice([-1,1]) × rng.uniform(0.6,1.1)·d_x),删除等距排开兜底,物理放不下时接受轻微重叠 + 透明度补偿(_apply_overlap_alpha按组内最大重叠密度压低填充透明度至 ≥0.30,无重叠时原样返回)。布局硬约束:max_j ≤ width/2(点绝不溢出 box/柱外);画点前必先设 xlim/ylim(prism_bars/boxplot/violin 内部已预设;手写多组循环务必set_xlim(-0.5, n-0.5)+set_ylim(...)置于画点前,否则点径换算d_x/d_y漂移导致前几组挤在一起)。点密集或审稿敏感可切layout="beeswarm"多列展开。实现级细节(mid_b/rng.uniform 公式、_apply_overlap_alpha判据与公式、jitter_ratio取值、接口签名):见references/scatter-layout.md。已覆盖所有叠加散点的图型:prism_bars/prism_boxplot/prism_violin/add_individual_dots - 散点布局必须固定种子(可复现):抖动走
numpy随机流,不传seed时同一脚本每次出图点的位置都不同——所谓"可复现脚本"就名不副实(实测:同一脚本连跑两次,PNG 在绘图区内像素不一致)。交付脚本 / 模板一律显式传seed=int(PARAMS.get("seed") or 0);seed=0即确定性(同数据同参数 → 逐像素一致)。GUI 语义同此(留空按 0);引擎直接调用时seed=None仍为随机(供刻意需要重排的场合)。 - 散点大小与透明度(同图所有样本点同大,期刊规范):
common_point_size(total_n, n_groups)自适应——第一个参数是全图样本点总数(不是每组 n),公式pt = clip(5·√(4/avg_n)·(3/组数)^0.25, 2.0, 5.0)pt,其中avg_n = total_n / n_groups(窗口 2.0–5.0;组数指数 0.25 < 样本量指数 0.5,样本量主导)。关键档位(3 组):
| total_n(全图总点数) | avg_n(每组) | pt | 场景 |
|---|---|---|---|
| 12 | 4 | 5.0 | 小样本满点(上限) |
| 24 | 8 | 3.54 | 敏感段(明显缩小) |
| 48 | 16 | 2.5 | 常规中等样本 |
| ≥75 | ≥25 | 2.0 | 大样本(下限) |
⚠️ 别把 avg_n 当成第一个参数传:实测 common_point_size(8, 3) 返回 5.0(而不是表里的 3.54)——散点会偏大 41%。表里的数值一律以 total_n 为准;avg_n 只在表里作为对照列出现。
窗口为何是 [2.0, 5.0]:点更克制,多组图不易糊成一片;上限 5.0pt 与柱体宽度(默认 0.62)比例协调,下限 2.0pt 在 300 dpi 下直径约 8 px、缩到单栏图(~8.5 cm)仍可辨,并非噪点。本口径与 neuroresearch 随包副本一致(同一批数据在技能内与 neuroresearch 内出图点径相同);若某期刊要求散点更醒目,显式传 max_size=6.0 放宽上限即可,无需改默认。
透明度:填充与描边统一;auto_dot_alpha 按 n 自适应(n 大→越透明防重叠掩盖),含 _apply_overlap_alpha 同步压低。
- 配色:色盲安全色板——默认 Okabe-Ito(8 色,分类图首选);印刷场景可切 Paul Tol Bright(7 色);多组/复杂图可选 Paul Tol Muted(9 色);高对比屏幕图可选 IBM Design Library(5 色);单组时优先灰度系。期刊风扩展色板:npg(10)/ aaas(10)/ lancet(9)/ jama(7)/ set2 / dark2(各 8)为审美向、非严格色盲安全;tol_light(9)/ tol_vibrant(7)仍色盲安全。色盲安全要求严格时用 okabe_ito/paul_tol 系;组数超色板数会自动 UserWarning 提示撞色(
palette_cycle统一实现),此时换多色色板或改用形状区分。用get_palette("okabe_ito")/get_palette("paul_tol_bright")/get_palette("paul_tol_muted")/get_palette("ibm_design")/get_palette("npg")等获取,palette_sequence()取分组配色序列。用户指定配色方案时按需切换。接口提示:get_palette(name)返回 dict(颜色名→hex,如{"blue": "#0072B2", ...}),不能直接[:n]切片;要按期刊常用顺序取颜色序列,用palette_sequence(name)(返回 list,如palette_sequence("okabe_ito")[:2]取前两组色)。给prism_bars/prism_boxplot/prism_survival/prism_xy_fit的palette=参数传色板名字符串或颜色列表均可 - 字体:Arial/Helvetica 类无衬线,坐标轴标题 8pt,刻度数字 6pt(整体 −4)
- 误差线:柱状图默认 Mean±SEM(error bar 单独绘制且 zorder=6 > 散点 5,防散点遮盖);箱线图展示中位数/四分位距,须注明。"误差线永远最顶层"全局规则(覆盖所有图型):柱状图误差棒 zorder=6 > 散点 5;箱线图 median/whisker/cap zorder=4 > 散点 3 > 箱体填充 2;箱体边框也重绘到 zorder=4(seaborn boxprops 同时控制填充与边框无法单独提层,方案是填充留 2 + 边框独立线框重绘到 4)——任何图型的误差范围线/中心线/箱体边框都不被样本点覆盖
- 误差线类型(5 种 Prism 口径):
error_type=支持"sem"(mean ± SEM,默认)、"sd"(mean ± SD)、"ci95"(95% CI)、"range"(min–max)、"iqr"(四分位距)。error_bars(values, error_type)单独取误差量(对称类返回标量;range/iqr返回[上须偏移, 下须偏移]长度 2 的数组——注意是相对均值的偏移量、按 matplotlibyerr约定上须在前,不是须线的绝对坐标),error_type_label(error_type)取规范图注文字(如mean with 95% CI)——误差线口径与图注必须一致。prism_bars/prism_grouped_bars/prism_horizontal_bars/prism_paired_lines均支持该参数(默认"sem")。 - 图注口径的唯一真相源(v2.16.3):
resolve_data_caption(chart, error_type)决定图注里 "Data are …" 那一句写什么,GUI 与交付脚本共用此函数(预览 = 复现)。铁律:图上画了什么,图注就写什么——- 画误差线的图型(柱 / 水平柱 / 分组柱 / 序列 / 配对)→
error_type必须是 5 个口径代码之一,图注取error_type_label(code)("sem"→mean ± SEM); - 不画误差线的图型(箱线 / 小提琴)→ 根本没有误差线,绝不可写 "mean ± SEM":图注改用该图型的默认描述(箱线:
median with IQR shown as box; whiskers extend to the most extreme value within 1.5 × IQR; individual points overlaid),或用户自己写的自由文本描述数据呈现方式(如individual values (dots) with box = median with IQR …)。 - 交付脚本里
error_type写自由文本(图注描述)只对不画误差线的图型成立;画误差线的图型写自由文本时,微调台回落"sem"并在警告区明说"已按 Mean±SEM 处理"(不抛错——抛错会让微调台整体打不开,用户无从自救),交付脚本侧则由引擎error_bars()硬拒 - 不要跨图型复用图注文字:
error_type是共享键,箱线图的描述切到柱状图上会与图不符。微调台按「继承判据」自动换成当前图型的口径(值逐字等于交付脚本原值且图型已变 → 判为被动带入);用户自己改过的文字一律保留不覆盖
- 画误差线的图型(柱 / 水平柱 / 分组柱 / 序列 / 配对)→
- 对数坐标轴:
prism_bars/prism_grouped_bars传logy=True、prism_horizontal_bars传logx=True即按 log 轴绘制。set_nice_ylim/finish_axes遇 log 轴自动跳过线性"取整收尾",否则会把 log 轴顶端改坏 - 刻度闭合:
set_nice_ylim/finish_axes遇 log 轴是直接 return——log 轴刻度完全交给 matplotlib 默认 locator,不做任何闭合;线性轴也只对齐上界、不管下界。所以手动set_ylim+set_yticks极易出现两端悬空(实测ylim(0.02, 260)配ticks [0.03 … 100]:轴底↔首刻度、末刻度↔轴顶各截掉半步)。画完图后调一次set_closed_ticks(ax, "y")即可:
from prism_theme import set_closed_ticks
ax.set_yscale("log"); ax.set_ylim(0.02, 200) # 先给目标区间(留好标注空间)
res = set_closed_ticks(ax, "y", log_subdivisions=(1, 2, 5))
# res = {"ticks": [...], "lower":…, "upper":…, "step":…, "scale":…}
# → ticks [0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100, 200],ylim 收到 (0.02, 200)闭合的三条判据:① 首刻度 == 轴起点、末刻度 == 轴终点(不悬空、不半步截断);② 刻度属统一序列(线性等差 / log 尾数同一 mantissa 集合,如 1-2-5 或 1-3-10);③ 只向外扩、不向内切(覆盖是硬约束,绝不因闭合丢数据)。另自动关闭 log 次刻度(matplotlib 的 LogLocator 会在主刻度间插无标签小刻度线,视觉上让读者以为"没闭合")。which="x" 同样支持(水平柱状图收尾)。
- 零基准线:跨零柱状图自动补 y=0 灰色虚线(
zero_baseline=True,默认)。仅在 0 严格落在轴内时画;全正(下限=0)与全负(上限=0)不画——0 即轴边界、脊柱已标示,再画属冗余 - 柱内样本量:
show_n=True标注各组 n。prism_bars(Column)用n_position控制位置:n_position="tick"(默认)走刻度标签换行如"Controln(n=6)";n_position="bar"标在柱体内(柱高 45% + 白底半透 bbox)——后者刻度标签只留组名单行,窄画布下旋转角更缓、可读性更好。分组柱/水平柱仍固定标在柱内。 - CLD 字母标注:
add_cld_letters(ax, comparisons=res["pairwise"], labels=labels)——按"差异不显著"关系求极大团,给每组分配共享字母(a/ab/b),多组两两结果的最紧凑标注法;也接受p_value=传完整 p 值方阵 - 断轴:
add_axis_break(ax1, ax2=None, which="y")在两段轴交界画断口斜线 - 长标签折行:
wrap_xticklabels(ax, width=12);rotate=可显式指定旋转角(默认按重叠自动判断 45°) - 显著性标注(混合规则):星号/精确 p 值 + 连接线(bracket)标注组间比较。规则:显著 p<0.05 → 星号;边缘显著 [0.05,0.1) → 精确 p 值(
p=0.073);p≥0.1 → 不标。bracket 自动分层防重叠(层高 LH 0.055),标注底部分类型偏移(星号 -0.03 span 骑线、p 值 0 在横线),ylim 顶部收尾 ≤ ~1.3×数据最大值(_nice_ylim_topmax_ticks=9 防刻度拥挤)。ylim<1 值域:刻度按量级分级(≥1 保持 0.5 倍数;<1 用 0.05/0.005/10^k 网格),0.1/0.2/0.25 等细刻度可用、不再被粗化成 0.5 顶高轴顶;bracket 数据最大值收集优先get_bbox()(Rectangle 单位矩形坑,勿用get_path().get_extents()对柱子取值) - 输出:PNG(300 dpi)+ PDF(矢量)双格式;默认
save_figure(tight=False)成图精确等于 figsize(v2.16.0 起),tight=True才按bbox_inches='tight'按内容裁边
画布尺寸与成图实际大小(⚠️ 必读,实测澄清)
tight 决定 figsize 是否等于成图物理尺寸。save_figure 默认 tight=False(v2.16.0 起)→ 成图精确等于 figsize;仅当传 tight=True 时才按rcParams["savefig.bbox"]="tight" 裁边,物理尺寸既可能小于、也可能大于请求的figsize,取决于刻度标签是否溢出画布。下表为 tight=True 实测(Column 图,dpi=300):
| 请求 figsize | 成图实际 | 占比 | 说明 |
|---|---|---|---|
| 3.0 × 2.5 in | 2.71 × 2.27 in | 90% / 91% | 裁掉四周留白 |
| 5.0 × 4.0 in | 4.26 × 3.43 in | 85% / 86% | 裁掉四周留白 |
| 8.0 × 6.0 in | 6.58 × 4.97 in | 82% / 83% | 裁掉四周留白 |
| 1.18 × 1.97 in(tall 3×5 cm) | 1.30 × 2.03 in | 110% / 103% | ⚠️ 反而变大 |
⚠️ 窄画布会反向:画布小到刻度标签物理溢出画布时,tight 会把溢出部分一并计入成图 → 成图比请求的 figsize 更宽(上表最后一行实测 110%)。所以"成图总比 figsize 小"是错的;要精确控宽只有 tight=False 一条路。
Axes 本身不会被拉伸变形,裁的只是四周空白,故字号比例不受影响。
若需要严格控成图尺寸(期刊规定"图宽 85mm"之类),用 tight=False:
fig, ax = plt.subplots(figsize=P.get_figsize("square")) # 5×5 cm
P.save_figure(fig, "name", out_dir, tight=False) # 成图严格 5×5 cm⚠️ 仅传 bbox_inches=None 无效(会被全局 rcParams 覆盖,实测产出完全相同),必须走 save_figure(..., tight=False)。
要核实成图到底多宽,别看 fig.get_size_inches()(那是画布宽,check_submission 量的是它,不是成图文件宽)——直接量文件:
from PIL import Image
with Image.open("name.png") as im:
px_w = im.size[0]; dpi = im.info.get("dpi", (300, 300))[0]
print(f"成图宽 = {px_w / dpi * 25.4:.2f} mm") # PNG 走 pHYs
# PDF 用 MediaBox:fitz.open("name.pdf")[0].rect → × 25.4 得 mm内置的画布预设(未改动):tall 3×5 cm · square 5×5 cm · wide 7×5 cm。时间序列的自动宽度阶梯见上方 series_figsize() 分档表(唯一真源,不在此重复)。
图型模板速查(按表型列出,完整模板在 references/recipes/)
画图前先读对应模板文件(含完整代码、参数细节、统计写法);下表为速查:
| 表型 → 图型 | 主函数(关键参数) | 统计 | 模板 |
|---|---|---|---|
| Column → 带散点柱状图 | prism_bars(ax, groups, labels)(error_type= 5 种:sem/sd/ci95/range/iqr) | ≥3 组 oneway_anova_tukey;k≥3 非正态 → build_stats_report("kruskal", ...);2 组 ttest_two_groups | recipes/bar.md |
| Column/Grouped → 箱线图+散点 | prism_boxplot(ax, groups, labels);小提琴 prism_violin(...) | 同上 | recipes/box.md |
| Column → 水平柱状图(组名长/标签挤时) | prism_horizontal_bars(ax, groups, labels)(logx= / show_n=) | 同柱状图 | recipes/bar.md |
| Grouped → 分组柱状图 | prism_grouped_bars(ax, df, x_col, group_col, value_col) | twoway_posthoc(交互显著→简单效应;否则主效应) | recipes/grouped.md |
| Grouped(重复测量)→ 意大利面条图 | prism_spaghetti(ax=None, df, x_col, group_col, value_col, subject_col)(个体轨迹+均值±SEM;ax=None 时自建画布,宽度按时间点数自动伸缩) | build_stats_report("twoway_rm", df=..., rm_subject=..., rm_within=..., rm_between=..., value=...)(混合设计 RM two-way,GG 球形性校正+Subjects(matching)+简单效应 Sidak) | recipes/repeated_measures.md |
| 配对前后(Before/After)→ 配对连线图 | prism_paired_lines(ax, before, after, group=None)(个体连线 + 均值±SEM) | 配对 t / Wilcoxon 符号秩 | recipes/repeated_measures.md |
| 重复测量 k≥3 条件 → 配对秩检验 | 同上按数据形态选图 | 配对且非正态 → build_stats_report("friedman", ...)(各组长度须相同) | recipes/repeated_measures.md |
| Grouped/XY(长表)→ 时间序列折线 + 误差带 | prism_series(ax=None, df, x_col, group_col, value_col, error_type="sem")(fill_between 阴影带 + 组均值折线 + 可选 show_points/show_n;ax=None 时自建画布,series_figsize() 按时间点数自动选宽) | 逐时间点组间比较:各点 ttest_two_groups / mann_whitney_u | recipes/series.md |
| Survival → Kaplan-Meier / 累积发生率 | prism_survival(ax, time, event, group, cumulative=False)(cumulative=:True → y = 1−KM,ylabel 自动切 "Cumulative incidence",中位生存线自动不画) | log-rank + Cox HR(可选) | recipes/survival.md |
| 效应量汇总 → 森林图 | prism_forest(ax, cox=cr) 或 prism_forest(ax, labels=, estimates=, ci_low=, ci_high=)(点 + 95% CI + 无效线;CI 不跨无效线即显著) | 直接消费 cox_regression() 的 comparisons | recipes/series.md §森林图 |
| 钙成像/电生理 → 多通道平铺轨迹 | prism_traces(ax=None, series=..., x=..., groups=..., scale_bar="auto")(逐条纵向错位平铺、互不遮挡;offset 自动 = 最大峰-谷 ×1.25、y 刻度默认隐藏) | 原始记录图通常不做统计(要聚合成组趋势改走 prism_series/prism_spaghetti) | recipes/traces.md |
| 电生理/事件 → 栅格图(+ PSTH) | prism_raster(ax=None, spike_times=..., trial_ids=..., t_range=..., bin_size=..., psth=True)(每 trial 一行短竖线;psth=True 必须 ax=None,自动出上 raster 下 PSTH 两行画布) | PSTH 计数闭合(counts.sum() = 范围内事件数);对发放率做统计需另调相应检验 | recipes/traces.md |
| XY → 剂量-响应/标准曲线 | prism_xy_fit(ax, x, y, model="4pl")(linear/4pl<sup>†</sup>/3pl/5pl/4pl_free<sup>‡</sup>/one_phase_decay/one_phase_assoc/exponential/michaelis_menten/power/deming) | 拟合 R²、IC50 | recipes/xy_fit.md |
| XY → 相关性散点(两变量共变) | prism_corr(ax, x, y, group=None, method="pearson")(图内自动标 r/p/95%CI/n + 回归线与置信带;method="spearman" 秩相关) | Pearson(Fisher z 95%CI)/ Spearman;方法选择属判断 | — |
| 正态性诊断 → Q-Q 图 | prism_qq(ax, data, labels)(多组叠加一点一线,图例带 Shapiro-Wilk p) | Shapiro-Wilk | — |
| 矩阵 → 热图 | prism_heatmap(df, cmap="RdBu_r", standardize="row", cluster="both")(standardize= none/zscore/row/col;cluster= 层次聚类叶子序重排;推荐 cmap 见 HEATMAP_CMAPS) | 无(描述性);聚类为 average linkage | — |
| 效应量标注(任意图型追加) | annotate_effect_size(ax, x1, x2, a, b)(Hedges g + 95% CI 一行小字;hedges_g() 可单独取值) | 与主检验并列报告 | — |
| Contingency → 分组计数柱状图 | prism_bars(计数)+ χ² 检验 | Fisher/χ² | recipes/contingency.md |
| 两组比率(存活率/命中率)→ 比例 z 检验 | 柱状图或点图 | 率差 Δ+CI、RR、OR:build_stats_report("proportion", prop_counts=(x1,x2), prop_ns=(n1,n2)) | recipes/contingency.md |
| k 组比率 → 多组比例比较 | prism_stacked_bars(构成比)或柱状图 | 整体 χ² + 两两(Holm):prop_counts=[...] | recipes/contingency.md |
| Nested → 嵌套柱状图 | prism_nested_bars(ax, df, outer, inner, value) | nested_anova(单元为分析单元防假重复) | recipes/nested.md |
| Parts of whole → 饼图/环形图 | prism_pie / prism_donut | — | recipes/parts_of_whole.md |
| Contingency / Parts of whole → 堆叠柱(含百分比堆积) | prism_stacked_bars(ax, df, x_col, group_col, value_col, percent=True)(可 show_totals=True) | 计数表用 χ²/Fisher | recipes/parts_of_whole.md |
| Multiple variables → 散点矩阵/分面箱线 | prism_pairplot(df, upper="mirror") / prism_facet_boxplot(...)(upper="mirror" 上三角镜像散点、"off" 保留旧空白) | 按需 | recipes/multiple_variables.md |
| 时间序列宽度查询(不画图) | series_figsize(n_x=5) / series_figsize(x_labels=[...])(高 5cm,宽按时间点数分档;长标签再 +2cm) | — | recipes/series.md |
<sup>†</sup> model="4pl"(默认):约束四参数 logistic,Top=100/Bottom=0 固定、只自由估计 logEC50 与 Hill,仅适配百分比活力数据(对照=100%、完全抑制=0%),等价于 R drc::LL.4(fixed=c(NA,0,100,NA)),IC50 即 EC50 参数。<sup>‡</sup> model="4pl_free":四参数全自由,用于非百分比响应(RLU、吸光度、荧光)——平台 100/0 不成立时必须用它,否则会静默给出错误 IC50 并打印警告。
通用骨架(所有图型一致,差异只在主函数与统计调用):
import sys, pathlib
sys.path.insert(0, str(pathlib.Path(r"<技能根目录>") / "scripts")) # ← 唯一需改的一行
import matplotlib.pyplot as plt
import numpy as np
from prism_theme import (apply_prism_theme, recommend_figsize, prism_bars,
oneway_anova_tukey, add_pairwise_brackets,
build_stats_report, write_report, save_figure)
apply_prism_theme()
fig, ax = plt.subplots(figsize=recommend_figsize("column", n_groups=len(groups)))
prism_bars(ax, groups, labels) # 主函数按图型替换
res = oneway_anova_tukey(groups, labels) # 统计按图型替换
add_pairwise_brackets(ax, res["pairwise"], labels=labels)
save_figure(fig, name, out_dir)
write_report(name, build_stats_report("anova_tukey", description="细胞活力",
groups=groups, labels=labels), out_dir)⚠️
description=必须给真实字符串——传...(Ellipsis)会在生成图注时抛
AttributeError: 'ellipsis' object has no attribute 'startswith'。它是**纯图内容描述**(如"细胞活力"),统计方法文字由引擎自动补,写进 description 会导致图注重复。
save_figure 默认 pad_inches=0.04,给轴外显著性 bracket 留安全边距。
跨图型硬规则(模板里不再重复,此处统一):
- 点大小全图统一:
dot_size=common_point_size(total_n, n_groups)**2——柱/箱/小提琴函数默认已统一,手画散点才需显式传 - box/violin 必须显式
palette=才上色(函数已内置,勿后置set_facecolor会全蓝) - 显著性 bracket:
add_pairwise_brackets(ax, res["pairwise"], labels=labels)自动分层防重叠 - 统计选择:≥3 组
oneway_anova_tukey;2 组ttest_two_groups(自动 shapiro→非参数/Welch) - 退化数据防御:组内零方差(归一化全同值)→ 确定性 p 值不崩;n=1 组 / 配对长度不等 → 明确报错提示
Phase 4:输出交付
完整细节见 references/output-guide.md(输出目录与命名口径、图注 p 值规则、build_stats_report 的 test 参数表、HTML 总览、合并面板图、投稿合规自检、五件套配对模板——交付时按需读取)。核心要点:
- 输出:PNG(300 dpi 预览)+ PDF(矢量投稿)双格式。落到
output/(out_dir不传时由_default_out_dir()决定;Windows 上切勿显式传/sandbox/...,否则会真的生成C:sandbox意外目录)。同一个{name}驱动图/报告/脚本,五件套同前缀。 - 图注:用
format_legend(),必须含每组比较的精确 p 值。报告 p 值一律用精确值或科学计数法,禁止p<0.001这类阈值简写——阈值写法丢失量级信息,读者无法区分 0.0009 与 0.0000001,而这两个数在生物学意义上可能完全不同。正确写法:p=8.17e-4、p=3.71e-9、p=0.049(用format_p()即可,它已按 p≥0.001 用 3 位小数、p<0.001 用科学计数法 3 位有效数字输出)。- ⚠️ 自写 p 值简写是最常见的踩坑点:调用方常自己写
"p<0.001" if p < 0.001 else f"p={p:.4f}"—— 这会绕开引擎已有的format_p,导致报告里出现几十处p<0.001。任何自写的 p 值格式化都应删掉,改为调用format_p()。 - 例外(保留阈值写法):①星号图例本身(
*p<0.05, **p<0.01, ***p<0.001, ****p<0.0001)——那是在解释符号含义,不是报告某个具体检验的结果,期刊通用写法;②方法学描述(如"Shapiro p<0.05 提示偏离正态")——讲的是判定规则而非具体结果。
- ⚠️ 自写 p 值简写是最常见的踩坑点:调用方常自己写
- 2 组图必须传
test="ttest"——否则默认回退"One-way ANOVA + Tukey",把 t 检验误写成 ANOVA(学术误导);更稳妥是直接复用build_stats_report自动生成的图注。 - 统计报告:
build_stats_report(test, ...)+write_report(name, rep, out_dir)→{name}_report.md;write_report默认另生成自包含 HTML 总览{name}.html(内嵌 300dpi 图 + 描述统计/前提检验/事后比较/图注表格),这是交付给用户查看的首选物,与 PNG 一起用 present_files 呈现。 - 多数据集合并面板图(用户说"把多张图画在一起"/"像 Nature 那样 A/B/C")→
compose_panel_figure(...)+build_master_report(...),完整契约见references/panel-figures.md。 - 可复现(必须模板交付):交付脚本一律以
templates/<表型>_template.py为底稿——复制后只改顶部PARAMS一个字典与数据(GROUPS/LABELS或data_path),禁止照references/recipes/片段手写交付脚本(recipes 是引擎用法参考,不含 GUI 微调台所需的PARAMS块)。脚本末尾emit_run_script(NAME, out_dir=OUT, with_launcher=True, skill_root=str(SKILL))自动落盘五件套 + 微调启动器(见 Phase 4.5)。v2.17.2 起引擎硬校验:with_launcher=True而脚本缺顶层PARAMS块时直接拒绝落盘并报错(附三条可行动出口),错误在出图当场暴露,不再延迟到用户双击启动器时;REPL 无__file__时要求显式传source=,不会静默失败。 - 投稿合规自检:
check_submission(fig, journal="nature")把字号下限 / 线宽下限 / 位图分辨率 / 版面宽度 / 字体嵌入一次查完,返回{"ok", "problems", "warnings", "stats", "report"}。这类"印刷才暴露"的硬指标在屏幕上永远看不出来(如 0.2pt 的线印刷时直接丢失、字体未嵌入会被出版社排版系统替换成乱码)。自测可用min_font_pt=/min_lw_pt=/min_dpi=调阈值。注意:屏幕预览 dpi ≠ 投稿 dpi,函数按导出分辨率口径判定;linewidth=0(不描边)与不可见 artist 不计入违规 - 导出格式硬规则:矢量导出字体默认 TrueType 嵌入(
pdf.fonttype=42/ps.fonttype=42,主题已内置,check_submission连同字号/线宽一并检查);SVG 默认文字转曲(部分期刊要求防乱码;save_figure(text_as_path=False)可保留可编辑文本);TIFF 默认 LZW 无损压缩(未压缩 TIFF 动辄上百 MB,易被投稿系统拒收;tiff_compression=""可关闭)。 - 用户要求改图型/误差线/配色/画布形态 → 回到对应 Phase 调整后重新出图。
8 种表型模板在 templates/(column / grouped / xy / survival / contingency / parts_of_whole / multiple_variables / nested _template.py),复制后只改顶部 PARAMS 一个字典即可;模板自动定位技能根目录(可移植),默认输出到 output/。
Phase 4.5:GUI 微调台(交付后微调——视觉迭代 GUI 吸收,判断迭代留给你)
定位(不可颠倒):本技能是 AI 驱动作图出报告,GUI 只做微调——用户拿到交付物后想改标题、配色、画布形态、误差线类型这类视觉参数时,不必再回对话反复描述,双击启动器即可在浏览器里调、实时看、自选格式保存;而统计方法、检验族、显著性解读等判断权永远在 AI 与用户手里,GUI 不接管。
用户说"微调"时你怎么做
- 交付脚本(
{name}.py)旁已自动生成微调_{name}.bat(Windows;POSIX 为微调_{name}.sh)——由emit_run_script(..., with_launcher=True)产生,无需你额外操作。告诉用户双击它:自动配环境 → 起本地服务 → 浏览器打开并自动加载该.py(1:1 复现 AI 的图与参数,且保存微调直接回写原文件)。这是"直达需要微调的 py 文件"的首选入口(启动器在本地进程里能拿到真实绝对路径,GUI 浏览器侧拿不到)。 - 用户在 GUI 里还有三种「打开交付脚本」方式(解决"手敲路径 / 只能传数据"的老痛点):
- 文件选择器:点「打开交付脚本」→ 浏览器文件框(仅收
.py)→ 选中即作为项目打开; - 拖拽
.py:把交付脚本拖进左侧数据卡,直接当项目打开(数据文件仍走上传通道); - 浏览文件夹:点「浏览文件夹」→ 先选文件夹、再在其中的
.py里选一个(Chromium 走 File System Access API;不支持时自动回退文件选择器)。 - ⚠️ 浏览器安全策略不暴露用户选中文件的绝对路径,故这三种方式以临时副本形式加载项目;保存微调写入副本后,GUI 会自动下载更新后的脚本供覆盖本地原文件。若脚本用
data_path引用了外部 CSV 且临时副本旁找不到,会提示改用微调_*.bat启动器(保留真实路径、能找到相邻 CSV)。
- 文件选择器:点「打开交付脚本」→ 浏览器文件框(仅收
- 用户没有启动器(如在仅 POSIX 环境或启动器被删)→ 由平台托管 GUI 启动,或在 GUI 里上传数据走 fallback 通道。
(注:launch.vbs/launch.bat 仅开发者本机"无项目时手动起 GUI"的便利入口,不随技能包发布,SkillHub 也禁止上传可执行脚本;正常用法依赖 emit_run_script 生成的 微调_*.bat,不受影响。)
- 用户在 GUI 里保存 → 参数手术式回写进原脚本的
PARAMS块(其余源码逐字节保留),脚本仍是唯一真相源,随时可重跑复现
双模式
| 模式 | 入口 | 说明 |
|---|---|---|
| 项目模式(主) | 打开 AI 交付脚本 | ast.literal_eval 无损抽取顶部 PARAMS 字面量 + 内嵌/引用数据,不执行脚本;1:1 复现交付现场。入口有四种:① 双击 微调_{name}.bat(自动加载且保存回写原文件,首选)② GUI「打开交付脚本」文件选择器 ③ 拖拽 .py 进数据卡 ④「浏览文件夹」选文件夹再选 .py(后三种为上传式临时副本,保存后下载回写) |
| 上传模式(fallback) | GUI 里上传 CSV/Excel | 无交付脚本时的备用通道;自动识别表型,不支持的表型明确拒绝并提示走脚本工作流 |
参数三级分层(GUI 的行为宪法)
| 层级 | 例子 | GUI 行为 |
|---|---|---|
| 🔓 free | title / palette / figsize / dpi / 保存格式 | 直接可调,实时更新 |
| ⚠️ careful | error_type(改图注文字)/ show_points | 可调,但写入评审履历,每次改动在 warnings 里逐条可见("error_type 由 AI 交付值 'sem' 改为 'sd'") |
| 🔒 locked | var_equal / posthoc / control / paired | 只读——展示 AI 依据数据做的选择;要改只能回对话(表单传值也被忽略,纵深防御) |
调参适用性与「改了没变化」排查
- 按图型置灰:调参控件只对真正接线了的图型可调——
line_width(线条粗细)与legend_loc(图例位置)对除 Q-Q 诊断图外的全部图型有效(v2.16.3 起:柱 / 箱 / 小提琴 / 水平柱 / 分组柱的边框、中位线、须线、误差棒、端帽都跟随line_width;单系列图的图例默认不画,显式选方位才生成);marker_size仅时间序列折线(其余图型用point_size);capsize仅柱 / 分组柱 / 水平柱;bar_alpha仅柱 / 箱 / 小提琴 / 分组柱;Q-Q 图无可调结构元素,全部置灰。不适用时控件置灰且渲染警告明示,不会出现"改了参数图却没反应"。 - 四条独立的线宽(v2.17.0):
line_width(结构线:柱/箱轮廓、中位线、折线、拟合线、配对连线)、errorbar_width(只改误差棒:误差竖线 + 端帽;箱线图对应须线 + 帽线)、sig_line_width(显著性括号的连接线与两端竖线)、axis_width(轴线 + 刻度线)。作用域:errorbar_width仅柱 / 水平柱 / 分组柱 / 箱线 / 配对(序列图用误差带、小提琴/生存/XY/Q-Q 无误差棒);sig_line_width仅柱 / 箱 / 小提琴 / 分组柱(水平柱图不画图内标注);axis_width全图型。三者留空 = 不动作(引擎默认 0.75),且errorbar_width留空时误差棒跟随line_width——老脚本、老交付图的像素级行为不变。引擎侧对应err_lw=形参与apply_axis_width(ax, width=…)(后处理函数,交付脚本同样调用它 → 预览 = 复现)。 - 显著性标注:
sig_style(星号 / 字母 CLD / 不标)、sig_compare(多选:开关式标签,点一次选中并高亮、再点一次取消,无需按住 Ctrl/Cmd;全部=全部比较,一项不选也=全部)、sig_y_offset(整体抬升/下移括号基线,单位 = y 轴可见跨度的比例)——column 家族(柱 / 箱 / 小提琴)与分组柱均生效;预览与导出脚本同一套过滤语义,不会出现"预览只画一条、脚本画全部"。 error_type的两副面孔(v2.16.3):画误差线的图型(柱 / 水平柱 / 分组柱 / 序列 / 配对)里它是口径代码下拉(sem/sd/ci95/range/iqr);箱线 / 小提琴图没有误差线,该图型下同一控件自动变成自由文本框「数据呈现描述(写入图注)」——键仍是error_type(不另起第二个参数,回写脚本后老脚本build_stats_report(error_type=…)照样生效,预览 = 复现)。图注口径由引擎resolve_data_caption(chart, error_type)统一裁决:图上画了什么,图注就写什么——箱线图永远不会被写成 "mean ± SEM"。sig_compare/sig_y_offset(v2.16.3 起对 column 家族同样可用):Column / 箱线 / 小提琴图可在下拉里只保留需要的比较对(比较项形如Control vs A,全选 =all),并用「标注位置微调」整体抬升/下移标注基线(单位 = y 轴可见跨度的比例);两项在 GUI 预览与导出的复现脚本里同一口径。水平柱状图不在此列——竖版括号的几何假设不适用于横版布局。- 留空 = 自动:清空任一"自动"档输入框(线宽 / 标记 / 端帽 / 透明度 / 图例位置 / 散点大小 / 画布尺寸 / 字体缩放 / 各文字标签)即恢复引擎默认——空值会显式上报服务端执行清空。
- 版本自检:右上角徽标显示运行中服务的版本;变红 = 浏览器缓存了旧页面(按 Ctrl+F5)。徽标比对的是「页面资源版本」与「服务端版本」——不一致说明缓存或服务是旧的,重双击
微调_{name}.bat即可(启动器会先杀旧进程再起)。技能目录自身的版本号以SKILL.mdfrontmatter 为唯一真源,微调台不读取它——别拿徽标去核对技能目录版本。
红线(写进 GUI 的硬约束)
- GUI 不实现任何绘图/统计算法:全部调用
scripts/prism_theme.py,不发明第二套口径(一致性测试逐位比对 GUI 渲染与直调引擎) - GUI 不做学术判断:换检验方法、改显著性口径、解读结果——这些是对话里的事;GUI 里改了 careful 参数只代表"用户知情地要改",不代表"这样更科学"
check_submission自检通过 ≠ 可投稿:合规自检只查字号/线宽/dpi/版面宽度/字体嵌入等硬指标,不代替期刊指南核对与学术判断- 覆盖表型:column / grouped / xy / survival;其余表型请在对话里走脚本工作流
- 中文标签预警:主题字体链不含中文字形,GUI 检测到中文即警告导出会显示方框——期刊图用英文,确需中文回对话让 AI 以
apply_prism_theme(font_family=[中文字体, "DejaVu Sans"])重新交付
已知边界
show_cox默认关闭(lifelines 自动安装有副作用,且 GUI 无法复现 Cox 标注);需要 HR 时走脚本工作流cox_regression()- GUI 是本地服务(127.0.0.1),不开网络端口;保存只写交付脚本同目录与
output/
Phase 5:自我优化复盘(每次任务后自动执行)
每次完成 Phase 4 交付后,无条件进入本阶段:复盘本轮使用过程,把踩过的坑、被纠正点、能力缺口固化回技能自身。完整规则见 references/self-optimization.md(执行本阶段前应读它),要点如下:
复盘信号源(逐项回顾本轮):① 脚本是否报错 / Warning ② 用户是否纠正过 ③ 是否手写临时补丁绕过坑④ 用户想要但脚本没有的能力 ⑤ 是否因文档不清而误解。
分类与处置(本模式 = 安全修复自动改):
bug/convention/doc:满足"安全修复闸门"(可复现、局部、可验证、非破坏性、可回退)则自动改并记日志;gap:清晰低风险的加法(新函数)自动加,否则记日志待确认;ambiguous:不确定是否为真问题 / 怎么修 → 只写日志、不动文件、向用户展示待确认。
⚠️ 执行顺序:交付优先,复盘在后(用户明确要求)
复盘是交付之后的动作,绝不能让它挡住出图。上一轮实测:修 3 个 bug +跑两轮全量回归(各 30s+)耗时可观,而用户在等的其实只是一张图。因此本阶段严格按下列顺序执行,不许反过来:
Phase 0-4 出图 → present_files 交付图 + 结论 → Phase 5 复盘/修bug/跑回归具体要求:
- 先出图、先
present_files、先给统计结论,再谈复盘。用户拿到图之前
不应被"我正在修技能"这类过程信息拖住。
- 发现 bug 时先判断它是否影响本次交付物的正确性:
- 影响本次出图(如 bracket 叠层导致结论误读、坐标轴刻度错)→ 属于
交付缺陷,必须当场修完并重出图,再一起交付;不能把有缺陷的图先交出去,也不能拖着不修。
- 不影响本次出图(如复现脚本落盘、字体 fallback、文档措辞)→ 交付完
再说,在回复末尾用一两句"顺带修了什么"交代即可,不必让用户等。
- 回归验证不阻塞回复:全量 pytest 可用
run_in_background跑,或在图与
结论都给出之后再补跑并报告结果。只要没把带病产物交出去,慢一点无妨。
- 复盘产出(
learnings.jsonl/CHANGELOG)属技能内部维护,放最后,
不进正文主叙述。
一句话判据:先让用户拿到图,再让技能变好。但"图本身有错"不在此豁免范围内——
那属于交付质量,必须修好再交。
闭环动作:
- 用
scripts/log_learning.py把每条发现记入技能根目录learnings.jsonl(append-only,含 type/status/files_changed/verified_by); - 决定自动改的条目 → 改
SKILL.md/scripts/references→ 立刻回归验证(先python -c "import prism_theme"确保不崩,再跑一个 verify 脚本确认图形/统计正确); - 回归通过记
status=applied;回归失败立即还原改动并记status=reverted,绝不带病交付; - 改动后务必 Read 复查确实落盘(trust-but-verify);用户级与项目副本保持同步。
验证分级策略、verify 档位表与耗时基线:见 references/self-optimization.md「验证分级与档位」节(按改动类型选档,不必次次全套)。日常改动默认只跑 pytest 全套。
护栏:公开 API 稳定优先——新能力用"加法"(新函数)而非改旧签名;日志只追加、可还原; 超过约 50 条时按 self-optimization.md 瘦身协议归档:旧条目移入 references/learnings-archive-<日期>.jsonl,learnings.jsonl 保留最近 20 条,并同步更新历史案例区。
工作流示例
示例 1:三组药物剂量实验(Column 表)
用户说:"我有个 Control、低剂量、高剂量三组的细胞活力数据,想画 Prism 那种带散点的柱状图,标显著性。"
- 识别:一个分组变量、三组独立样本 → Column 表
- 选图:默认带散点柱状图(Mean±SEM),确认箱线图/小提琴图备选
- 统计:3 组独立样本 → One-way ANOVA + Tukey 事后检验;生成 Control vs 低剂量、Control vs 高剂量、低 vs 高的星号
- 美化:L 形坐标轴 + 色盲友好配色 + 散点叠加 + 星号 bracket
- 交付:PNG + PDF + 图注 "Data are mean ± SEM, n=6 per group. One-way ANOVA with Tukey's post hoc test."
示例 2:时间×药物双因素实验(Grouped 表)
用户说:"有 0h/24h/48h 三个时间点和 Control/Drug 两组,要做分组柱状图。"
- 识别:两个分组变量(时间、药物)→ Grouped 表
- 选图:分组柱状图(X 轴=时间,分组=药物),叠加散点
- 统计:Two-way ANOVA,报告时间主效应、药物主效应、交互项;图内标注药物组间显著性
- 美化:同示例 1,分组用色盲友好两色
- 交付:图注 "Data are mean ± SEM, n=4. Two-way ANOVA; *p<0.05, **p<0.01 vs Control at same time point."
示例 3:小鼠生存实验(Survival 表)
用户说:"两组小鼠的生存天数,Conventional vs Experimental,画生存曲线。"
- 识别:时间 + 事件/删失 → Survival 表
- 选图:Kaplan-Meier 曲线,阶梯状,删失标记为短竖线
- 统计:log-rank 检验,P 值标在图上
- 美化:两组对比色、风险表(number at risk)可选
- 交付:图注 "Kaplan-Meier survival curves; log-rank test, p=0.03, n=10 per group."
反例黑名单(不要做什么)
第 0 条(总纲):凡可能损害图或统计正确性的改动,无论收益多大都不做——文档润色、性能优化、布局调整一律让位于此。
以下为全文禁令的集中清单(每条后括号是权威出处)。正文中对应位置保留完整展开与实测依据,本清单是速查索引——两者口径一致,正文为准。新增禁令时必须同时补到本清单,避免散落各处无从对照。
- 严禁多组设计用逐对 t 检验替代 ANOVA——3 组以上两两比较必须 ANOVA + Tukey(或对应非参数路线),逐对 t 检验会膨胀假阳性(Phase 2「报告纪律」)。
- 禁止
p<0.001这类阈值简写——只写精确值或科学计数法,阈值写法丢失量级信息(Phase 4 图注规则)。 - 图注必须含星号约定、n、统计方法——只标星号不写图注约定 = 审稿退回(Phase 4)。
- 不要虚构数据、不要做商业图表——本技能只服务真实实验数据的科研图(frontmatter「Not for」)。
- 不要用基础/anaconda 解释器跑交付脚本——venv 优先级与解释器解析链见「环境准备」;写错解释器是可移植性事故的头号根因。
- 勿在
apply_prism_theme()之后set_facecolor——会破坏主题底色;要改底色先改主题参数。 - 不要静默剔除 NaN/Inf——引擎统一告警并提示核对图注 n,自行写剔除代码前先确认告警逻辑已覆盖。
- 不要给 GUI 放开统计方法族——统计方法族在 GUI 是 🔒 只读,方法选择属判断须回对话定夺(Phase 4.5)。
- 不要拿
check_submission通过当"可投稿"——它只查硬指标,不代替期刊指南核对与学术判断(Phase 4 第 7 条)。 - 不要过度设防——小样本下参数与非参数检验不一致属正常稳健性对照,不因此推翻主检验;一次比较只给一个主 p 值(Phase 2)。
- 🔴 严禁引用完全分离数据下的 Cox Wald p 值——HR>10⁶ 或 95% CI 含 inf 即判完全分离(MLE 不存在),此时 Wald p 是假性 p,实测出现过 log-rank p=1.79e-4 而 Wald p=0.995、方向完全相反。只采信 log-rank p 与似然比整体
overall_p(Phase 2)。 - 🔴 配对/重复测量禁用 Kruskal-Wallis——必须走
friedman_test(配对简单效应 Wilcoxon + Holm);Kruskal 的全局排名在受试者间方差大时损失大量检验力(实测同数据 p 大约 160 倍),且 Dunn 的 z 近似对配对数据不成立(Phase 2)。 - 严禁静默吞掉
read_table的notes——被忽略的列可能意味着数据没被完整使用,属学术严谨性范畴;notes必须原样转述给用户(Phase 0)。 - Windows 上切勿显式传
/sandbox/...路径——会真的生成C:sandbox意外目录(Phase 4)。 - 不要为同一个 p 跑两套统计——2×2 时 z² ≡ χ²,显著性判断与卡方完全相同;比"多跑一套"更重要的是只报一个主 p 值(Phase 2)。
- 不要依赖
var_equal="auto"自动检出方差不齐——小样本时 Levene 检验力极低(实测 3 组、方差比 9:1 时,n=3 检出率 0%、n=6 约 34%、n=10 约 78%);实验设计提示方差不齐时必须显式var_equal="welch",不要依赖 auto 自动判定(Phase 2)。 - 勿用
get_path().get_extents()取柱子的数据最大值——Rectangle 的 path 是单位矩形,取值必错;显著性 bracket 收集最大值要优先用get_bbox()(Phase 3)。 - 不要漏 import
matplotlib.pyplot as plt——引擎的__all__不含plt/np,from prism_theme import *拿不到它们(显式具名导入虽能取到,但依赖引擎内部 import 顺序,不该依赖);漏 import 直接NameError(见「引擎导入」节)。 - 不要把
common_point_size的第一个参数当avg_n——它是全图总点数total_n;传错会得到偏大的散点(实测common_point_size(8, 3)= 5.0 而非表里的 3.54)(Phase 3)。 - 不要把
description=写成...——Ellipsis 不是字符串,生成图注时抛AttributeError: 'ellipsis' object has no attribute 'startswith';且 description 只写图内容,统计方法文字引擎会自动补(写进去会重复)。
可移植性 / 分发一致性(v2.17.3 起强化,v2.17.5 补齐坐标轴/版面/环境层)
技能分发给别人后「同默认、出图却不一样」的根因几乎都在环境/默认值没锁死,不在统计与图形正确性(正确性由 Phase 0–2 保证,此处只动可复现层)。分发前请确认:
- 依赖版本钉死:
scripts/ensure_env.py的REQUIRED已把
matplotlib/scipy/numpy/pandas/seaborn/statsmodels/openpyxl 钉成精确版本(锁版本,而非无约束/范围约束)。matplotlib 小版本之间默认字体、bbox 裁边、线宽渲染都有差异——这是「换机出图不同」的头号来源。升级任一依赖时必须同步更新 REQUIRED并跑 pytest 回归;不要恢复成无约束。
- 字体:
apply_prism_theme默认首选 Arial(作者偏好、国际期刊通用);
Arial/Helvetica 在部分系统(多数 Linux / 清洁 Windows)缺失时静默回退 DejaVu Sans(matplotlib 自带),任何机器都能出图、不报 findfont 警告。注意:这保留作者外观,但「有 Arial 的机器」与「缺 Arial 的机器」字体仍可能不同——若需完全跨机逐像素一致,显式apply_prism_theme(font_family=["DejaVu Sans"])(或 ["Arial","DejaVu Sans"])。中文:自动并入本机可用中文字体(Microsoft YaHei/SimHei/Noto Sans CJK 等),缺中文字体时warn_if_cjk_unsupported(text) 会显式告警而非静默出方框。中文字体顺序已固定(v2.17.5):_available_cjk_fonts 早先用 set 差集拼列表,而字符串哈希每进程随机 → 同机两次运行可能一次 YaHei、一次 KaiTi,字形/字宽/行高全变。现按固定偏好序(YaHei → SimHei → PingFang SC → Noto Sans CJK → … → SimSun/KaiTi/FangSong)选取,未列出的按名称排序追加,任何机器任何运行都一致。回退链只并入最多 3 个中文字体,且优先有 bold 变体的(v2.17.5):SimHei / SimSun /KaiTi / FangSong 等传统中文字体只注册 weight=400,而 matplotlib 的逐字形回退会对链上每个字体尝试当前 weight——链越长,每个粗体文本就多刷几行Failed to find font weight bold, now using 400(实测 25 项链 → 每处 17 行);更糟的是若这类无 bold 字体排前面,中文粗体会静默不粗,同一张图里中英粗细不一致。想给中文加字体,改 _CJK_PREFERENCE 顺序即可,不要改成全量并入。
- 散点/置换检验可复现:
seed=None在 v2.17.5 起等价于 0,且按
_derive_seed(seed, k) 为各组/各子图派生稳定子种子(各组抖动形状仍不同,但每次运行完全相同)。手写脚本显式传 seed=0 亦可,效果等同;需要换一组抖动样式才传其它值。
- 主题不再要求手动调用(v2.17.5):模块 import 时已自动
apply_prism_theme(),且
所有公开绘图函数入口自动 _ensure_theme(ax) 并把主题写到 Axes 实例上。原因是rcParams 只在创建 Axes 那一刻被快照——先 plt.subplots() 再画图时改 rcParams 不会影响脊柱线宽/刻度几何/上右边框/轴底色(实测出图脊柱 0.8 而非 0.75、tick 3.5 而非 2.0、上边框还在)。所以保留 apply_prism_theme() 调用无害、删掉也不会出裸图。需要完全接管 rcParams(如自定义期刊格式)时:环境变量 PRISM_NO_AUTO_THEME=1,或prism_theme.AUTO_THEME = False 后再自行调用。另:主题已把约 45 个影响外观的 rcParams 钉死(底色/前景/刻度方向与几何/text.usetex/ axes.unicode_minus/axes.formatter.use_locale/constrained_layout/subplot.*/prop_cycle=Okabe-Ito/legend.*/savefig.pad_inches 等),宿主 matplotlibrc、seaborn.set_theme()、plt.style.use("ggplot") 不会再渗进成图。
- 画布形态透明可锁定:形态由
recommend_figsize按表型/分组数分档——
Column:组数 <5→tall, 5–8→square, >8→wide;Grouped 按 n_cells=x水平数×组数 计(不是你数的「组数」,这是「3 组却变 wide」的唯一常见原因);时间序列宽度按时间点数自适应。渲染结果 meta 含 figsize_rationale(计数口径+阈值,可核对);导出脚本头注释也写明了规则。想锁定形态:传 shape:"tall"/"square"/"wide" 或 fig_w_cm/fig_h_cm 覆盖自动判断。
tight尺寸:save_figure默认tight=False(v2.16.0 起)→ 成图物理尺寸**精确等于
figsize**(实测 tall 3×5 cm → 精确 3.00×5.00 cm @300dpi)。传 tight=True 才按bbox_inches="tight" 裁边,成图随内容变化(通常更小;窄画布下刻度溢出时也可能更宽)。投稿精确栏宽时保持默认 tight=False,用 pad_inches 微调即可。
- GUI 缓存已击穿:
gui/app.py在返回首页时把静态资源?v=重写为app.version
并设 no-cache,浏览器不会拿旧 JS 导致参数「没生效」;若仍怀疑,Ctrl+F5 清缓存或重双击自动生成的 微调_{name}.bat(它会先杀旧进程)。
坐标轴与版面不变量(v2.17.5,出图前自检)
用户端"图会失控"的问题绝大多数不是统计错,而是坐标轴与版面在边界数据上失效。以下不变量由引擎保证,手写脚本时也必须遵守(对应工具函数可直接调用):
- 刻度不得落在轴范围外。matplotlib 的
AutoLocator/MaxNLocator允许刻度略微越界
(xlim=(0,0.005) 会给 0.006);且 ax.set_xscale()/set_yscale() 会把定位器整体换回 AutoLocator——所以顺序必须是 先设 scale、再设 ticks,否则刻度被静默丢弃。收尾时调 _prune_ticks_to_limits(ax)(或 finish_axes/finish_axes_axis,它们已内置)。
- 刻度网格锚定在步长的整数倍上。
finish_axes会把下限 floor 到 step 倍数;
否则出现 0.7/1.2/1.7…、3.5/8.5/13.5 这类"步长对、网格错位"的刻度。
- 整数数据给整数刻度:计数/个数(全为整数)的轴用
_snap_integer_ticks(ax, which, data),
消除 0.5/1.5/2.5、2.5/7.5/12.5。箱线/小提琴/柱状/分组柱/水平柱/堆叠柱/连线/QQ 已内置。
- 轴范围必须装下误差棒,不只是数据点:
ci95/sd在 n 小时上须可远高于柱顶。
三个柱类图已按"含误差棒的绘制外沿"取范围。
- 柱状类图的数值轴必须含 0(Prism 规范),负值数据向下堆叠(
prism_stacked_bars支持
正/负两条堆叠链;全负数据不再出空白图)。
- 精确 cm 模式下,任何元素都不许被裁。
save_figure(tight=False)不做bbox_inches="tight",
而 tight_layout 不统计轴外注释。因此:
- 轴外图例 →
_accommodate_outside_legend(右侧扩画布,主图尺寸不变); - 轴外注释(森林图数值列)→
_reserve_right_for_texts(同样向右扩画布,不要缩轴:
注释锚在 axes fraction 1.02,缩轴上锚点跟着移,永远腾不出空间);
- 标签比画布还宽(窄画布 + 长组名)→
_wrap_overflow_labels折行。
以上三步都在 _finalize_exact_layout(fig) 里,save_figure 自动调用;自己写保存流程时必须复刻这三步,否则导出的图会缺角。
- log 轴的刻度用 1-2-5 闭合(
set_closed_ticks(ax, which, mode="log")):默认
LogLocator 在窄画布会挤出 4×10⁻¹ 6×10⁻¹ 10⁰ 互相压字的标签。
- 拟合前检查可拟合性:单点或 x 全相同要给出明确报错(
prism_xy_fit已前移校验),
不要让 numpy 抛 LinAlgError: SVD did not converge。
- 主题不可依赖调用顺序(v2.17.5)。rcParams 只在建 Axes 那一刻快照,所以
plt.subplots() 之后再 apply_prism_theme() 对脊柱/刻度/边框/底色无效。引擎的做法是双保险:import 时自动应用 + 每个公开绘图函数入口 _ensure_theme(ax) 把主题写到 Axes 实例。手写脚本若绕开引擎自建 Axes,收尾前必须自己调_apply_theme_to_axes(ax)(内部函数,可直接 import)。别在收尾处 ax.grid(False):用户可能在画图后显式开了网格(GUI 的"显示网格"就是这条路径),强制关会把它抹掉;宿主 axes.grid 造成的"新轴自带网格"交给 rcParams 钉死解决。
- 可复现性三件套:字体链顺序固定(不随 hash 种子变)、
seed=None ≡ 0且按组派生
子种子、fig.dpi 在布局前归一到 figure.dpi。任何"同一脚本两次出图不一样"的怀疑,先查这三项。
- 多面板图的间距由 gridspec 管,tight_layout 不介入(v2.17.5)。`add_gridspec(…,
wspace=, hspace=) 会让 GridSpec 的 subplot params "被局部修改",matplotlib 遂把每个Axes 的 subplotspec 记为 None 并警告 not compatible with tight_layout——**然后直接返回空字典,版式一点不动**。引擎已用 _tight_layout_void() 识别并跳过(混合情况仍执行)。自己拼多面板时别再指望 tight_layout 调间距,用 wspace/hspace 或fig.subplots_adjust`。
- 长驻进程(GUI)不泄漏 rcParams:实测同进程连续渲染 8 次 PNG 逐字节相同、渲染前后
rcParams 改动 0 项。自己写"一次算多张图"的脚本时,也不要在渲染之间改全局 rcParams;要改就改完还原,或每张图前重新 apply_prism_theme()。
资源目录
scripts/prism_theme.py— 引擎单文件:__all__锁定全部公开 API(整图封装 / 统计检验 / 标注与坐标轴 / 画布自适应 / 读表与组合五类)。按需 import 即可,无需加载全文。references/table-mapping.md— Phase 0 判表依据:8 种表型的判别规则、每型的推荐图型与统计入口。画图前必读(判错表型后面全错)。references/statistics-guide.md— Phase 2 统计决策树:检验族选择、前提检验、事后比较方案、比例与生存分析的完整口径说明。做统计前必读。references/output-guide.md— Phase 4 交付细则:输出目录与命名口径、图注 p 值规则、build_stats_report的 test 参数表、投稿合规自检、五件套配对模板。交付前按需读。references/panel-figures.md— 多面板合并图契约:compose_panel_figure()+build_master_report()的用法。合并面板图时读。references/scatter-layout.md— 散点布局实现细节:镜像均衡抖动的mid_b/rng.uniform公式、_apply_overlap_alpha判据、jitter_ratio取值、接口签名。手写散点布局或调点径时读。references/self-optimization.md— Phase 5 完整规则:复盘信号源分类、安全修复闸门五条、验证分级与档位、log_learning 用法、日志瘦身协议。执行 Phase 5 前必读。references/user-guide.md— 面向终端科研用户的完整使用说明(12 节,编号 0–11:这是什么 / 如何触发 / 三步出图 / 上传数据的 3 种方式 / 8 种表速查 / 可选参数 / 输出物 / 完整示例 / 迭代修改 / 进阶玩法 / FAQ / 避坑清单)。给人看,不给 agent 看——agent 走的是本 SKILL.md 的工作流;需要向用户解释"这个技能怎么用"或把使用手册交给用户时读它。references/recipes/— 12 个图型模板(bar / box / grouped / repeated_measures / series / survival / traces / xy_fit / contingency / nested / parts_of_whole / multiple_variables)。画该图型前读对应那一个,不要通读。templates/— 8 种表型的完整可运行脚本(column / grouped / xy / survival / contingency / parts_of_whole / multiple_variables / nested)。复制到工作目录改PARAMS即可,路径定位自适应。零摩擦入口。gui/— GUI 微调台(见 Phase 4.5):FastAPI + 原生 HTML/JS;gui/core/project.py(PARAMS 交接协议:ast 无损抽取 + 手术回写)、renderer.py(渲染,全部调引擎)、exporter.py(五格式导出 / 报告 / 复现脚本)、schema.py(参数三级分层)。引擎复用铁律:GUI 不实现任何绘图/统计算法。launch.vbs/launch.bat— GUI 手动启动器(Windows 双击 VBS;VBS 只转发,编码逻辑在 bat)。用户说"微调"时优先用交付脚本旁自动生成的微调_{name}.bat。references/implementation-notes.md— 实现细节索引:公开 API 分类清单、verify 各脚本覆盖点、tests 各文件例数、ensure_env 四条防线、pack_skill 排除清单。要改代码 / 跑回归 / 打包上传时读它,日常画图不需要。
PY=$(python scripts/ensure_env.py --dev) # --dev 装 pytest(基础 venv 默认没有)
$PY -m pytest tests/ -v # 全量:期望 489 passed(23 文件)
$PY -m pytest tests/ --fast # 快跑:跳过 slow 用例,461 passed, 28 skipped
$PY -m pytest tests/ --collect-only -q # 用例数以本命令实测为准(数字随用例增删变动,别照抄旧数)
$PY -m pytest tests/test_gui_api.py # GUI 微调台专项(协议/一致性/路由/复现)
$PY -m pytest tests/test_plot_series.py # 只跑改动相关的主题文件(最省时)测试已可从任意目录运行(路径由
__file__推导)。默认跑满全部用例,不静默跳过slow——统计类技能的失败模式是"算错了却没人发现",故要快必须显式加--fast。并行(pytest-xdist)对本套件无效:耗时以一次性 import 为主,实测 4 worker 反而更慢(66s vs 串行 40s),未采用。
scripts/log_learning.py— 学习日志辅助脚本:把每条复盘发现以结构化 JSON 行追加进技能根目录learnings.jsonl,支持--list N查看最近条目、--check全文件体检(报告坏行)。scripts/ensure_env.py— 环境引导器,每次任务第一步(PY=$(python scripts/ensure_env.py),跑测试加--dev)。四条防重复装包防线与已修缺陷见references/implementation-notes.md。scripts/pack_skill.py— 打包上传 skillhub(生成dist/*.zip)。用这个,别用 skill-creator 自带的package_skill.py(那份会把__pycache__/.pytest_cache/learnings.jsonl打进包)。--check只校验不写文件,--exclude-dev出精简版;校验规则见references/implementation-notes.md。CHANGELOG.md— 版本变更史(随包分发):按版本倒序记录引擎与文档的每次变更(口径调整、新增 API、修的真实 bug、修订理由);SKILL.md 正文只描述当前规则,历史叙事一律在此。使用者可据此判断"手上这份是什么版本、相对上一版改了什么"。learnings.jsonl— 学习日志(append-only),由scripts/log_learning.py按需创建;技能目录只读时自动降级到~/.workbuddy/skill-logs/prism-style-plot/。随包分发,故使用者侧的 Phase 5 复盘能真正落盘。
变更日志
历次版本变更史见随包分发的 CHANGELOG.md。正文只描述当前规则,不保留历史版本号。