mcpField
S
statistical-significance-annotation

statistical-significance-annotation

github.com/jaechang-hits数据与数据库38471 次安装1 周前更新
代码仓库

关于 statistical-significance-annotation

本指南介绍如何用括号和星号标注比较图中的统计检验结果,适用于准备科研图表的研究者。内容涵盖比较对象选择、多重检验校正,以及可复用的 matplotlib 实现。

主要功能

  1. 01将 p 值映射为星号标记
  2. 02选择需要标注的比较
  3. 03处理多重检验校正
  4. 04放置并堆叠图表括号
  5. 05使用 matplotlib 添加标注
  6. 06报告检验方法与样本信息

适用场景

  1. 01标注处理组与对照组的比较
  2. 02展示选定的事后比较结果
  3. 03在图中呈现校正后的 p 值
  4. 04避免多组图表标注过密

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill statistical-significance-annotation

安装后在 Claude Code 中输入/statistical-significance-annotation确认已加载。

详细介绍

图表上的统计显著性标注

概述

统计显著性标注(星号表示法)是放置在比较图上的视觉标记,用于指示组间假设检验的结果。它们由连接两组的括号和表示 p 值范围的星号组成。恰当的标注可确保图表中的视觉结论与定量证据一致,使图表达到可发表的标准并符合科学严谨性。本指南介绍标准惯例、何时以及如何进行标注,并提供可复用的 matplotlib 实现。

核心概念

标准星号表示法

广泛采用的惯例是将 p 值范围映射为星号:

符号P 值范围含义
nsp > 0.05不显著
*p <= 0.05显著
**p <= 0.01高度显著
***p <= 0.001极显著
****p <= 0.0001极其显著

转换函数:

def pvalue_to_asterisk(p: float) -> str:
    """Convert a p-value to standard asterisk notation."""
    if p <= 0.0001:
        return "****"
    elif p <= 0.001:
        return "***"
    elif p <= 0.01:
        return "**"
    elif p <= 0.05:
        return "*"
    else:
        return "ns"
校正后的 p 值与原始 p 值
  • 单次比较(一次 t 检验):使用原始 p 值。
  • 多重比较(针对 3 组或更多组进行成对检验、比较多个基因):使用校正后的 p 值(FDR/Benjamini-Hochberg 或 Bonferroni)。使用原始 p 值进行标注会夸大显著性。
  • 预先计算的结果(DESeq2 的 padj、ANOVA 事后检验):使用已提供的校正值。
选择比较对象

并非每一对组都需要标注。选择符合以下条件的比较:

  • 直接支持分析文本中提出的结论
  • 具有生物学意义(例如,处理组与对照组,而非对照组 A 与对照组 B)
  • 数量有限,以保持图表易读(通常每个面板 1-5 组比较)

决策框架

Does the plot compare groups?
├── No (scatter, heatmap, PCA, line trend) → Do NOT annotate
└── Yes (box, violin, bar, strip)
    ├── Does the analysis claim significance? → Annotate the claimed comparisons
    ├── Exploratory (no specific claim) → Annotate vs control only, or skip
    └── Too many groups (>6 pairwise) → Annotate key comparisons only
场景是否标注?比较对象
DEG 箱线图:处理组与对照组是处理组与对照组
多组 ANOVA,带事后检验是仅标注显著的事后检验比较
10 种细胞类型的基因表达有选择地标注仅与参考细胞类型比较
PCA 或 UMAP否N/A
热图或火山图否N/A
相关性散点图否在正文/图例中报告 r 和 p
探索性柱状图,无假设可选若适用,与对照组比较

最佳实践

  1. 让标注与文本结论一致:图上的每个星号都必须对应分析中描述的统计检验。未经计算检验,切勿进行标注。
  2. 多重比较时使用校正后的 p 值:检验不止一对时,务必使用经过 FDR 或 Bonferroni 校正的 p 值。在图例中注明校正方法。
  3. 限制标注的比较数量:仅标注与分析结论相关的比较。过度标注会使图表显得杂乱并分散重点。
  4. 清晰放置括号:将括号放在数据范围上方,并留出足够的垂直间距,避免与数据点、误差线或其他括号重叠。多个括号应以一致的间距堆叠。
  5. 注明统计检验:务必在图表标题、说明或图例中注明所用检验(t 检验、Mann-Whitney U、Wilcoxon、ANOVA + Tukey HSD 等)。
  6. 注明样本量:在坐标轴标签(例如,“对照组(n=30)”)或图例中显示每组的 n 值。
  7. 使用粗体标题:将图表标题设为 fontweight='bold',以符合发表要求。

常见陷阱

  1. 在多组图中标注所有成对比较

    • 如何避免:只选择由假设驱动的比较。对于 k 个组,k*(k-1)/2 个比较很快就会让图变得难以阅读。只显示与对照组的比较或特定对比。
  2. 进行多重比较时使用原始 p 值

    • 如何避免:使用 statsmodels.stats.multitest.multipletests(pvals, method='fdr_bh'),或使用上游工具提供的调整后 p 值(DESeq2 的 padj)。
  3. 括号与数据或其他括号重叠

    • 如何避免:为堆叠的括号使用递增的垂直偏移量。将第一个括号放在数据最大值加误差线的上方,然后为每个后续括号增加固定偏移量。
  4. 只标星号,却不说明使用了哪种检验

    • 如何避免:始终在图标题或注释中注明检验名称(例如,“Mann-Whitney U 检验”或“Tukey HSD 事后检验”)。
  5. 不同图中的标注方式不一致

    • 如何避免:在整个分析中使用相同的 pvalue_to_asterisk() 函数。定义一次并重复使用。
  6. 为每组不显著的比较都标注“ns”

    • 如何避免:只有当不显著本身是值得关注的发现时才显示“ns”(例如,显示两种处理之间没有差异)。对于未进行比较的组对,不要标注 ns。
  7. 将注释放在数据下方

    • 如何避免:始终将括号和星号放在被比较组的上方,绝不放在下方。

工作流

步骤 1:执行统计检验

在绘图前运行适当的检验并收集 p 值:

from scipy import stats

# Two-group comparison
stat, pval = stats.mannwhitneyu(group_a, group_b, alternative='two-sided')
# or for normal data:
stat, pval = stats.ttest_ind(group_a, group_b)

# Multi-group: ANOVA + post-hoc
from scipy.stats import f_oneway
stat, pval_anova = f_oneway(group_a, group_b, group_c)

# Post-hoc pairwise (if ANOVA significant)
from itertools import combinations
from statsmodels.stats.multitest import multipletests

pairs = list(combinations(["A", "B", "C"], 2))
groups = {"A": group_a, "B": group_b, "C": group_c}
raw_pvals = []
for g1, g2 in pairs:
    _, p = stats.mannwhitneyu(groups[g1], groups[g2], alternative='two-sided')
    raw_pvals.append(p)

# Adjust for multiple comparisons
rejected, adj_pvals, _, _ = multipletests(raw_pvals, method='fdr_bh')
步骤 2:在图中添加括号注释

使用此辅助函数在任意 matplotlib 坐标轴上绘制带星号的括号:

def add_significance_bracket(ax, x1, x2, y, p_value, dh=0.02, barh=0.015, fontsize=11):
    """Draw a significance bracket with asterisk notation between two x positions.

    Args:
        ax: matplotlib Axes object.
        x1, x2: x-axis positions of the two groups (0-indexed).
        y: y-coordinate for the bracket (top of bracket line).
        p_value: p-value for the comparison.
        dh: vertical offset above bracket for the text (in axes fraction).
        barh: height of the bracket tips (in axes fraction).
        fontsize: font size for the asterisk text.
    """
    asterisk = pvalue_to_asterisk(p_value)

    # Draw bracket: two tips and a connecting line
    ax.plot([x1, x1, x2, x2], [y - barh, y, y, y - barh],
            lw=1.2, color='black')
    # Place asterisk text centered above the bracket
    ax.text((x1 + x2) / 2, y + dh, asterisk,
            ha='center', va='bottom', fontsize=fontsize, fontweight='bold')
步骤 3:与 Seaborn 图集成
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# Example: box plot with significance annotation
fig, ax = plt.subplots(figsize=(6, 5))
sns.boxplot(data=df, x="group", y="value", ax=ax, palette="Set2")
sns.stripplot(data=df, x="group", y="value", ax=ax,
              color="black", alpha=0.4, size=3, jitter=True)

# Determine bracket y-position from data
y_max = df["value"].max()
y_range = df["value"].max() - df["value"].min()
offset = y_range * 0.08  # spacing between brackets

# Add brackets for each significant comparison
# pairs_with_pvals: list of (group1_idx, group2_idx, p_value)
pairs_with_pvals = [(0, 1, 0.003), (0, 2, 0.042)]

for i, (x1, x2, pval) in enumerate(pairs_with_pvals):
    bracket_y = y_max + offset * (i + 1)
    add_significance_bracket(ax, x1, x2, bracket_y, pval)

ax.set_title("Gene Expression by Treatment", fontweight='bold', fontsize=14)
ax.set_ylabel("Expression (log2 CPM)")
# Extend y-axis to fit brackets
ax.set_ylim(top=y_max + offset * (len(pairs_with_pvals) + 1.5))

plt.tight_layout()
plt.savefig("expression_comparison.png", dpi=150, bbox_inches='tight')
步骤 4:为分组柱状图添加注释

对于带误差线的柱状图,将括号放在误差线上方:

fig, ax = plt.subplots(figsize=(7, 5))
bar_plot = sns.barplot(data=df, x="gene", y="fold_change", hue="condition",
                       ax=ax, palette="Set2", ci="sd", capsize=0.05)

# For grouped bars, calculate x positions manually
# Each gene has multiple bars offset by group
n_groups = df["condition"].nunique()
n_genes = df["gene"].nunique()
bar_width = 0.8 / n_groups

for gene_idx in range(n_genes):
    # x positions of the two bars within this gene group
    x1 = gene_idx - bar_width / 2
    x2 = gene_idx + bar_width / 2
    # Get the max value + error for this gene
    gene_data = df[df["gene"] == df["gene"].unique()[gene_idx]]
    y_top = gene_data["fold_change"].mean() + gene_data["fold_change"].std()
    p_val = pvals_per_gene[gene_idx]  # pre-computed

    if p_val <= 0.05:  # only annotate significant results
        add_significance_bracket(ax, x1, x2, y_top + 0.1, p_val)

ax.set_title("Fold Change by Condition", fontweight='bold', fontsize=14)
plt.tight_layout()
plt.savefig("fold_change_comparison.png", dpi=150, bbox_inches='tight')

操作指南

  1. 始终先执行检验再绘图:应先运行统计检验并保存结果,再编写任何绘图代码。不要在绘图代码块内计算 p 值。
  2. 使用一致的样式:在同一分析的所有图中使用相同的 add_significance_bracket 函数和 pvalue_to_asterisk 转换方式。
  3. 在解读文字中报告检验详情:展示图表时,说明所用检验、每组的样本数,以及 p 值是否经过调整。
  4. 调整 y 轴范围:添加括号后,扩大 y 轴上限以避免内容被裁切。使用 ax.set_ylim(top=...) 或 ax.margins(y=0.15)。
  5. 对于 DESeq2/edgeR 结果:直接使用 padj(调整后 p 值)。不要对原始计数重新进行检验。

延伸阅读

相关技能

  • seaborn-statistical-plots — Seaborn 绘图基础;可在 Seaborn 图表上使用本指南中的注释工作流
  • scientific-visualization — 通用科学图表设计原则

评论

支持 Markdown