跳到正文
百沐一下百沐一下
本节目录

柱状图加显著性星号:规范做法

跟完这篇,您能把一张分组数据画成一张规范的「均值柱 + 误差棒 + 显著性星号」图:柱高和误差棒各表示什么、星号来自哪种检验、怎么用几行代码把星号加上去,以及一个更重要的判断——什么时候这张柱状图根本不该画,该换成箱线图或散点图。

「均值柱配一对星号」是论文里最常见、也最容易画错的一张图。画错通常不在代码,而在没想清楚:误差棒到底代表什么、星号背后是哪种检验、这批数据适不适合用柱子概括。这篇按这三件事一步步走。

先备一张整洁数据表

无论最后画柱、画箱还是画点,起点都是同一种「整洁」格式:每行一个样本,一列放分组,一列放测量值

samplegroupvalue
s1Ctrl12.4
s2Ctrl10.9
s3Treat18.2
s4Treat16.7

只要是这个形状,后面所有代码都能直接套。若您手上是「一组一列」的宽表,先转成上面这种长表再画。整洁数据的更多细节见 统计方法怎么选

柱高与误差棒各画什么

一根「均值柱 + 误差棒」其实叠了两层信息:柱子的高度是各组的均值,误差棒是围绕均值的一段波动范围

library(dplyr)
library(ggplot2)

# df:整洁长表,group 为分组列,value 为测量列
stat <- df %>%
  group_by(group) %>%
  summarise(
    mean = mean(value),
    sd   = sd(value),
    n    = n(),
    sem  = sd / sqrt(n)      # 均值标准误 = 标准差 ÷ 根号 n
  )

ggplot(stat, aes(group, mean)) +
  geom_col(width = 0.6, fill = "grey80") +
  geom_errorbar(aes(ymin = mean - sd, ymax = mean + sd), width = 0.2) +
  theme_classic()

误差棒可以画三种东西,含义完全不同,别混:

误差棒表示什么与样本量 n 的关系
SD(标准差)数据本身有多散,反映个体间差异与 n 无系统关系,样本再多也不会自动变短
SEM(均值标准误)「样本均值」估计总体均值的精度= SD ÷ √n,n 越大越短
95% CI(置信区间)对均值的区间估计,约等于均值 ± 2×SEMn 越大越窄

误差棒选 SD、SEM 还是 CI

选哪种误差棒,取决于您想让读者看什么,不取决于哪种画出来好看:

  • 想说明「这批数据本身有多散」——用 SD
  • 想说明「我对这个均值估得有多准」——用 SEM95% CI
  • 要做组间比较、让读者据图判断差异——95% CI 通常信息量最大。

一个常见的不诚实做法:为了让误差棒显得短、组间差异显得「更干净」,明明想表达离散却选了 SEM。 SEM 恒比 SD 小(除非 n=1),这样选是拿视觉效果换了信息的真实性。请按「想表达什么」来选,不是按「哪种短」来选。

无论选哪种,图注里都必须写清两件事:误差棒代表的是 SD、SEM 还是 CI,以及每组的 n。少了这两条,一根误差棒是读不懂的。

还有一条要记牢,能避开一大类误读:

误差棒重叠不代表不显著,误差棒不重叠也不代表显著。 是否显著由检验的 p 值决定,不能靠肉眼比误差棒长短。所以「显著性」得单独做检验,也就是下一节的星号。

星号对应哪种检验

星号本身不是一种检验,它只是把某个检验算出来的 p 值换成符号显示。最常用的对应惯例是:

符号p 值范围
nsp ≥ 0.05(不显著)
*p < 0.05
**p < 0.01
***p < 0.001
****p < 0.0001

这套对应是常用惯例、非铁律。四颗星(p<0.0001)是部分绘图软件的默认,不少期刊只用到三颗星。用几级、界怎么划,请在图注里写明。

关键在于:星号背后的 p 值必须来自和您数据匹配的检验。选错检验,星号画得再多也没意义。按「几组、是否配对、是否近似正态」分岔:

场景常用检验
两组、独立、近似正态t 检验(方差不齐用 Welch 版)
两组、配对、近似正态配对 t 检验
两组、明显偏离正态Mann-Whitney U / Wilcoxon 符号秩(配对)
三组及以上、近似正态单因素方差分析(ANOVA)+ 事后两两比较(如 Tukey HSD)
三组及以上、偏离正态Kruskal-Wallis + 事后比较(如 Dunn,含校正)

三组以上别直接两两跑 t 检验。 每比一次都有一次犯假阳性的机会,比得越多假阳性越多。规范做法是先做 ANOVA(或 Kruskal-Wallis)这类总体检验,再做自带多重比较校正的事后检验。原理见 多重检验校正怎么选。检验怎么挑,见 t 检验还是非参数检验

把星号加到图上

代码层面,ggpubr 能把检验和标注一步做完:stat_compare_means() 负责跑检验、画括号、标星号。

library(ggpubr)

# 指定要比较的组对;两组时只列一对即可
my_comparisons <- list(c("Ctrl", "Treat"))

ggplot(df, aes(group, value)) +
  stat_summary(fun = mean, geom = "col",
               width = 0.6, fill = "grey85") +               # 柱高=均值
  stat_summary(fun.data = mean_se, geom = "errorbar",
               width = 0.2) +                                # 误差棒=均值 ± 标准误
  geom_jitter(width = 0.1, size = 1, alpha = 0.6) +          # 把每个原始数据点也画出来
  stat_compare_means(comparisons = my_comparisons,
                     method = "t.test",                      # 两组用 t 检验
                     label  = "p.signif") +                  # 显示星号而非 p 数值
  theme_classic()

几个要点:

  1. label = "p.signif" 显示星号;想直接标 p 值改成 label = "p.format"
  2. method 要和上一节的判断一致:偏离正态改 "wilcox.test",三组以上的总体 p 用 method = "anova""kruskal.test"
  3. stat_compare_means() 对您列出的多对比较是各自独立跑的,默认不跨比较校正。 一次列了好几对时,请回到 ANOVA + 事后检验的路子,或自行对 p 值做校正,别把一堆未校正的星号直接堆上去。

注意上面已经用 geom_jitter() 把每个数据点叠在柱子上了——这不是装饰,而是下一节的重点。

什么时候别用柱状图

这是全篇最该记住的一节。柱状图适合表示「计数、比例、频次」这类从 0 起算、有明确总量的数据;用它去概括一批连续测量值,常常会骗人。

问题在于:一根「均值柱 + 误差棒」只压缩出两个数(均值、离散),而很多形状差别很大的数据,画出来的柱子几乎一模一样。同一根柱子背后,可能是均匀分布,也可能是两簇分开的双峰、一个离群点拉高的均值、或两组样本量悬殊。柱子把这些都藏起来了。已有被广泛引用的方法学讨论专门指出这一点:小样本的连续数据,应当把每个原始点都画出来,而不是只画柱和误差棒。

按数据类型选图:

数据 / 场景更合适的图
计数、比例、频次(从 0 起的量)柱状图,合适
连续测量、每组样本不多散点 / 抖动点,把每个点都画出来
连续测量、样本较多箱线图 / 小提琴图(可叠加点)
想同时看均值与离散点 + 均值线 + 误差棒,别用实心柱

一个稳妥的折中,是在柱子(或箱子)上叠加原始点,既保留了熟悉的观感,又让读者看到真实分布——上一节的 geom_jitter() 正是干这个的。样本很小时(比如每组只有三五个),干脆别画柱,直接把点画出来、旁边标一条均值线即可。

常见错误

错误后果怎么避免
图注没写误差棒是 SD/SEM/CI读者无法判断误差棒含义,图不可读图注写清误差棒类型 + 每组 n
为显得「差异干净」而选 SEM用视觉短棒掩盖真实离散按想表达什么选,不按哪种短选
靠误差棒是否重叠判断显著结论出错显著与否看检验 p 值,不看误差棒
三组以上直接两两跑 t 检验假阳性膨胀先 ANOVA/Kruskal-Wallis,再做带校正的事后检验
小样本连续数据硬画柱双峰、离群点、n 不等被藏起来画散点/箱线,或在柱上叠加原始点
星号和检验对不上(如配对当独立)p 值本身就错,星号无意义先按设计选对检验,再谈星号

在平台上跑一遍

不想写 R 代码也能得到同样规范的图:在 百沐一下 传入一张整洁数据表(每行一个样本、一列分组、一列测量值),用一句话说清「按组画均值柱、误差棒用标准误、两组做 t 检验并标星号」,它会自动核对检验前提、跑出结果、把星号画上去,并附可复现的 R 代码;若数据更适合箱线或散点,它也会提示您换。用法见 智能绘图使用指南

相关