c17_paired_slope 实验前后对比 (Thesis Special) 国标学位论文 Lancet Cell

实验前后测配对样本斜率连线图 (Before-After Paired Slope)

理学、医学与教育实验前后测对比图,展示每个独立样本的干预演变斜率与均值变化显著性。

🔬 矢量预览 (Vector Preview) 600 DPI Ready
实验前后测配对样本斜率连线图 (Before-After Paired Slope)

📊 统计学特性 (Statistical Features)

  • 配对样本 t 检验 (Paired t-test)
  • 显著性水平标记 (*p<0.05)
  • 个体演变轨迹线
  • 组均值与标准误 (Mean ± SEM)

🛡️ QC 质检要点 (QC Highlights)

  • 前后测横坐标清晰居中
  • 显著性横括号高度自适应
  • 个体连线透明度适中
  • 附带均值误差棒显眼标注
🎨 顶刊色彩提取器 (Palette Extractor)Nature

Crisp sans-serif typography, clean borderless spines, high-contrast palette with soft muted secondary accents.

🛡️ 无障碍评分:AAA (Deuteranopia & Protanopia Compliant)

📋 Data Contract 数据契约规范 Strict Schema

输入数据必须完全符合下列列名与数据类型规范,方可通过自动化数据前检 (Pre-flight Validation)。

📥 下载规范示例 CSV 数据
字段名称 (Column) 数据类型 (Type) 约束性 语义说明与值域约束
subject_id categorical 必需 (Required) 被试/样本唯一编号
group categorical 必需 (Required) 实验分组(如对照组/干预组)
pre_score continuous 必需 (Required) 前测/干预前测量值
post_score continuous 必需 (Required) 后测/干预后测量值
组内最小样本量: n ≥ 4
最大允许缺失率: 0%

🐍 独立可复现 Python 绘图源码 Stand-alone Script

完全可复现的 Python 脚本,支持 CLI 参数 `--data`, `--style`, `--output-dir` 与模块化 `render()` 调用。

#!/usr/bin/env python3
"""FigureCraft Chart Engine: c17_paired_slope (实验前后测配对样本斜率连线图)."""

import os
import sys
import argparse
from pathlib import Path
from typing import List, Dict, Optional
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats

try:
    from engines.styles import apply_style, get_palette
except ImportError:
    sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
    from engines.styles import apply_style, get_palette

CHART_ID = "c17_paired_slope"


def render(
    data_path: Optional[str] = None,
    style: str = "thesis_cn",
    output_dir: str = "output",
    formats: Optional[List[str]] = None,
    dpi: int = 600,
) -> Dict[str, str]:
    if formats is None:
        formats = ["svg", "pdf", "png", "tiff"]

    apply_style(style)
    palette = get_palette(style)

    if data_path is None:
        data_path = str(Path(__file__).resolve().parents[1] / "data" / f"{CHART_ID}.csv")

    df = pd.read_csv(data_path)
    os.makedirs(output_dir, exist_ok=True)

    groups = df['group'].unique()
    n_groups = len(groups)

    fig, axes = plt.subplots(1, n_groups, figsize=(3.8 * n_groups, 4.0), sharey=True, dpi=dpi)
    if n_groups == 1:
        axes = [axes]

    for idx, (grp, ax) in enumerate(zip(groups, axes)):
        sub = df[df['group'] == grp]
        col = palette[idx % len(palette)]

        for _, row in sub.iterrows():
            ax.plot([0, 1], [row['pre_score'], row['post_score']], color=col, alpha=0.35, linewidth=0.9)
            ax.scatter([0, 1], [row['pre_score'], row['post_score']], color=col, alpha=0.5, s=18)

        pre_m, pre_sem = sub['pre_score'].mean(), stats.sem(sub['pre_score'])
        post_m, post_sem = sub['post_score'].mean(), stats.sem(sub['post_score'])

        ax.errorbar([0, 1], [pre_m, post_m], yerr=[pre_sem, post_sem], color='#111111', lw=2.0,
                    capsize=3.5, capthick=1.2, zorder=5, label='组均值 ± SEM')
        ax.scatter([0, 1], [pre_m, post_m], color='#111111', s=35, zorder=6)

        t_stat, p_val = stats.ttest_rel(sub['pre_score'], sub['post_score'])
        p_text = f"p < 0.001 (***)" if p_val < 0.001 else (f"p = {p_val:.3f} (**)" if p_val < 0.01 else f"p = {p_val:.3f}")

        y_max = max(sub['pre_score'].max(), sub['post_score'].max()) + 4
        ax.plot([0, 0, 1, 1], [y_max, y_max + 1.5, y_max + 1.5, y_max], color='#333333', lw=0.8)
        ax.text(0.5, y_max + 2.2, p_text, ha='center', va='bottom', fontsize=7.5, fontweight='bold')

        ax.set_xticks([0, 1])
        ax.set_xticklabels(['前测 (Pre)', '后测 (Post)'], fontsize=8.5, fontweight='semibold')
        ax.set_title(f"{grp} (N={len(sub)})", fontsize=9.0, fontweight='bold', pad=8)
        if idx == 0:
            ax.set_ylabel('测量指标评分 (Score)', fontsize=8.5, fontweight='semibold')
            ax.legend(loc='lower left', frameon=False, fontsize=7.5)

    fig.suptitle('实验干预前后配对样本变化轨迹与统计显著性', y=1.02, fontsize=10.5, fontweight='bold')
    plt.tight_layout()

    out_paths = {}
    base_name = f"{CHART_ID}_{style}"
    for fmt in formats:
        p = os.path.join(output_dir, f"{base_name}.{fmt}")
        fig.savefig(p, format=fmt, dpi=dpi, bbox_inches='tight')
        out_paths[fmt] = p

    plt.close(fig)
    return out_paths


def main():
    parser = argparse.ArgumentParser(description="Render Before-After Paired Slope Chart")
    parser.add_argument("--data", type=str, default=None)
    parser.add_argument("--style", type=str, default="thesis_cn")
    parser.add_argument("--output-dir", type=str, default="output")
    parser.add_argument("--formats", type=str, default="svg,pdf,png,tiff")
    parser.add_argument("--dpi", type=int, default=600)
    args = parser.parse_args()

    fmts = [f.strip() for f in args.formats.split(",")]
    render(args.data, args.style, args.output_dir, fmts, args.dpi)


if __name__ == "__main__":
    main()